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

  import Towerops.AccountsFixtures

  alias Towerops.Snmp.Device
  alias Towerops.Snmp.PhysicalEntity

  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,
        entity_index: 1,
        entity_class: "chassis"
      }

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

    test "valid changeset with all fields", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        entity_index: 1,
        entity_class: "chassis",
        description: "Cisco 2960-X Switch",
        model_name: "WS-C2960X-24TS-L",
        serial_number: "FCW1234ABC",
        firmware_revision: "15.2(4)E7",
        hardware_revision: "V01",
        manufacturer_name: "Cisco",
        position: 0,
        last_checked_at: DateTime.utc_now(),
        metadata: %{"source" => "entity-mib"}
      }

      changeset = PhysicalEntity.changeset(%PhysicalEntity{}, attrs)
      assert changeset.valid?
      assert get_field(changeset, :entity_class) == "chassis"
      assert get_field(changeset, :model_name) == "WS-C2960X-24TS-L"
      assert get_field(changeset, :serial_number) == "FCW1234ABC"
    end

    test "invalid changeset without snmp_device_id" do
      attrs = %{
        entity_index: 1,
        entity_class: "chassis"
      }

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

    test "invalid changeset without entity_index", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        entity_class: "chassis"
      }

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

    test "invalid changeset without entity_class", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        entity_index: 1
      }

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

    test "invalid entity_class value", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        entity_index: 1,
        entity_class: "invalid"
      }

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

    test "valid entity_class values", %{snmp_device: snmp_device} do
      for {entity_class, idx} <-
            Enum.with_index(~w(other unknown chassis backplane container power fan sensor module port stack cpu memory)) do
        attrs = %{
          snmp_device_id: snmp_device.id,
          entity_index: idx + 1,
          entity_class: entity_class
        }

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

  describe "parent relationship" do
    test "entity can have a parent entity", %{snmp_device: snmp_device} do
      # Create parent chassis entity
      chassis_attrs = %{
        snmp_device_id: snmp_device.id,
        entity_index: 1,
        entity_class: "chassis",
        description: "Main Chassis"
      }

      chassis_changeset = PhysicalEntity.changeset(%PhysicalEntity{}, chassis_attrs)
      assert {:ok, chassis} = Repo.insert(chassis_changeset)

      # Create child module entity
      module_attrs = %{
        snmp_device_id: snmp_device.id,
        entity_index: 2,
        entity_class: "module",
        description: "Line Card",
        parent_entity_id: chassis.id
      }

      module_changeset = PhysicalEntity.changeset(%PhysicalEntity{}, module_attrs)
      assert {:ok, module} = Repo.insert(module_changeset)

      # Verify relationship
      loaded_module = Repo.preload(module, :parent_entity)
      assert loaded_module.parent_entity_id == chassis.id
      assert loaded_module.parent_entity.description == "Main Chassis"
    end

    test "entity can have child entities", %{snmp_device: snmp_device} do
      # Create parent chassis entity
      chassis_attrs = %{
        snmp_device_id: snmp_device.id,
        entity_index: 1,
        entity_class: "chassis"
      }

      chassis_changeset = PhysicalEntity.changeset(%PhysicalEntity{}, chassis_attrs)
      assert {:ok, chassis} = Repo.insert(chassis_changeset)

      # Create multiple child PSU entities
      for idx <- 1..2 do
        psu_attrs = %{
          snmp_device_id: snmp_device.id,
          entity_index: 100 + idx,
          entity_class: "power",
          description: "PSU #{idx}",
          parent_entity_id: chassis.id
        }

        psu_changeset = PhysicalEntity.changeset(%PhysicalEntity{}, psu_attrs)
        assert {:ok, _psu} = Repo.insert(psu_changeset)
      end

      # Verify relationship
      loaded_chassis = Repo.preload(chassis, :child_entities)
      assert length(loaded_chassis.child_entities) == 2
    end
  end

  describe "unique constraint" do
    test "prevents duplicate entity_index on same device", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        entity_index: 1,
        entity_class: "chassis"
      }

      # Insert first entity
      changeset = PhysicalEntity.changeset(%PhysicalEntity{}, attrs)
      assert {:ok, _entity} = Repo.insert(changeset)

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

      assert {:error, changeset} = Repo.insert(changeset2)
      errors = errors_on(changeset)

      assert "has already been taken" in Map.get(errors, :entity_index, []) or
               "has already been taken" in Map.get(errors, :snmp_device_id, [])
    end
  end
end
