defmodule Towerops.Snmp.StateSensorTest do
  use Towerops.DataCase, async: true

  import Towerops.AccountsFixtures

  alias Towerops.Snmp.Device
  alias Towerops.Snmp.StateSensor

  setup do
    user = user_fixture()

    {:ok, organization} =
      Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)

    {:ok, site} =
      Towerops.Sites.create_site(%{
        name: "Test Site",
        organization_id: organization.id
      })

    {:ok, device_schema} =
      Towerops.Devices.create_device(%{
        name: "Test Router",
        ip_address: "192.168.1.1",
        snmp_enabled: true,
        snmp_version: "2c",
        snmp_community: "public",
        snmp_port: 161,
        site_id: site.id,
        organization_id: organization.id
      })

    snmp_device =
      %Device{}
      |> Device.changeset(%{
        device_id: device_schema.id,
        sys_name: "test-router",
        sys_descr: "Test Router"
      })
      |> Repo.insert!()

    %{device: device_schema, snmp_device: snmp_device}
  end

  describe "changeset/2" do
    test "valid changeset with all required fields", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1"
      }

      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      assert changeset.valid?
    end

    test "valid changeset with all fields", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1",
        sensor_descr: "Power Supply 1",
        entity_type: "power_supply",
        state_value: 1,
        state_descr: "normal",
        status: "ok",
        last_checked_at: DateTime.utc_now(),
        metadata: %{"vendor" => "cisco"}
      }

      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      assert changeset.valid?
      assert get_field(changeset, :sensor_descr) == "Power Supply 1"
      assert get_field(changeset, :entity_type) == "power_supply"
      assert get_field(changeset, :state_value) == 1
      assert get_field(changeset, :state_descr) == "normal"
      assert get_field(changeset, :status) == "ok"
    end

    test "invalid changeset without snmp_device_id" do
      attrs = %{
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1"
      }

      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).snmp_device_id
    end

    test "invalid changeset without sensor_index", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1"
      }

      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).sensor_index
    end

    test "invalid changeset without sensor_oid", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        sensor_index: "psu.1"
      }

      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).sensor_oid
    end

    test "invalid status value", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1",
        status: "invalid_status"
      }

      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      refute changeset.valid?
      assert "is invalid" in errors_on(changeset).status
    end

    test "valid status values", %{snmp_device: snmp_device} do
      for status <- ~w(ok warning critical unknown) do
        attrs = %{
          snmp_device_id: snmp_device.id,
          sensor_index: "psu.#{status}",
          sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1",
          status: status
        }

        changeset = StateSensor.changeset(%StateSensor{}, attrs)
        assert changeset.valid?, "status '#{status}' should be valid"
      end
    end

    test "default status is unknown", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1"
      }

      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      assert changeset.valid?
      # Default should come from schema, not changeset
    end

    test "default metadata is empty map", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1"
      }

      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      assert changeset.valid?
    end
  end

  describe "unique constraint" do
    test "prevents duplicate sensors with same device and index", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1"
      }

      # Insert first sensor
      changeset = StateSensor.changeset(%StateSensor{}, attrs)
      assert {:ok, _sensor} = Repo.insert(changeset)

      # Attempt to insert duplicate
      changeset2 = StateSensor.changeset(%StateSensor{}, attrs)

      assert {:error, changeset} = Repo.insert(changeset2)
      errors = errors_on(changeset)
      # The unique constraint may report on either field
      assert "has already been taken" in Map.get(errors, :sensor_index, []) or
               "has already been taken" in Map.get(errors, :snmp_device_id, [])
    end

    test "allows same sensor_index for different devices", %{device: device_schema} do
      # Create second device
      {:ok, device2} =
        Towerops.Devices.create_device(%{
          name: "Test Switch",
          ip_address: "192.168.1.2",
          snmp_enabled: true,
          snmp_version: "2c",
          snmp_community: "public",
          snmp_port: 161,
          site_id: device_schema.site_id,
          organization_id: device_schema.organization_id
        })

      snmp_device2 =
        %Device{}
        |> Device.changeset(%{
          device_id: device2.id,
          sys_name: "test-switch",
          sys_descr: "Test Switch"
        })
        |> Repo.insert!()

      snmp_device1 = Repo.get_by!(Device, device_id: device_schema.id)

      attrs1 = %{
        snmp_device_id: snmp_device1.id,
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1"
      }

      attrs2 = %{
        snmp_device_id: snmp_device2.id,
        sensor_index: "psu.1",
        sensor_oid: "1.3.6.1.4.1.9.9.91.1.1.1.1.5.1"
      }

      changeset1 = StateSensor.changeset(%StateSensor{}, attrs1)
      assert {:ok, _sensor1} = Repo.insert(changeset1)

      changeset2 = StateSensor.changeset(%StateSensor{}, attrs2)
      assert {:ok, _sensor2} = Repo.insert(changeset2)
    end
  end

  describe "entity_type values" do
    test "accepts common entity types", %{snmp_device: snmp_device} do
      entity_types = ~w(power_supply fan module chassis sensor port)

      for {entity_type, i} <- Enum.with_index(entity_types) do
        attrs = %{
          snmp_device_id: snmp_device.id,
          sensor_index: "entity.#{i}",
          sensor_oid: "1.3.6.1.2.1.131.1.1.1.#{i}",
          entity_type: entity_type
        }

        changeset = StateSensor.changeset(%StateSensor{}, attrs)
        assert changeset.valid?, "entity_type '#{entity_type}' should be valid"
      end
    end
  end
end
