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

  import Towerops.AccountsFixtures

  alias Towerops.Snmp.Device
  alias Towerops.Snmp.Vlan

  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 Switch",
        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-switch",
        sys_descr: "Test Switch"
      })
      |> 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,
        vlan_id: 100
      }

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

    test "valid changeset with all fields", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        vlan_id: 100,
        vlan_name: "Production",
        vlan_type: "ethernet",
        status: "active",
        last_checked_at: DateTime.utc_now(),
        metadata: %{"source" => "q-bridge"}
      }

      changeset = Vlan.changeset(%Vlan{}, attrs)
      assert changeset.valid?
      assert get_field(changeset, :vlan_name) == "Production"
      assert get_field(changeset, :vlan_type) == "ethernet"
      assert get_field(changeset, :status) == "active"
    end

    test "invalid changeset without snmp_device_id" do
      attrs = %{
        vlan_id: 100
      }

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

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

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

    test "invalid status value", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        vlan_id: 100,
        status: "invalid_status"
      }

      changeset = Vlan.changeset(%Vlan{}, 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(active suspended) do
        attrs = %{
          snmp_device_id: snmp_device.id,
          vlan_id: Enum.random(100..999),
          status: status
        }

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

    test "default status is active", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        vlan_id: 100
      }

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

    test "validates vlan_id range", %{snmp_device: snmp_device} do
      # VLAN IDs must be between 1 and 4094
      for invalid_id <- [0, -1, 4095, 5000] do
        attrs = %{
          snmp_device_id: snmp_device.id,
          vlan_id: invalid_id
        }

        changeset = Vlan.changeset(%Vlan{}, attrs)
        refute changeset.valid?, "vlan_id #{invalid_id} should be invalid"
        assert "must be between 1 and 4094" in errors_on(changeset).vlan_id
      end
    end

    test "accepts valid vlan_id range", %{snmp_device: snmp_device} do
      for valid_id <- [1, 100, 1000, 4094] do
        attrs = %{
          snmp_device_id: snmp_device.id,
          vlan_id: valid_id
        }

        changeset = Vlan.changeset(%Vlan{}, attrs)
        assert changeset.valid?, "vlan_id #{valid_id} should be valid"
      end
    end
  end

  describe "unique constraint" do
    test "prevents duplicate VLANs with same device and vlan_id", %{snmp_device: snmp_device} do
      attrs = %{
        snmp_device_id: snmp_device.id,
        vlan_id: 100,
        vlan_name: "Production"
      }

      # Insert first VLAN
      changeset = Vlan.changeset(%Vlan{}, attrs)
      assert {:ok, _vlan} = Repo.insert(changeset)

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

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

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

    test "allows same vlan_id for different devices", %{device: device_schema} do
      # Create second device
      {:ok, device2} =
        Towerops.Devices.create_device(%{
          name: "Test Router",
          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-router",
          sys_descr: "Test Router"
        })
        |> Repo.insert!()

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

      attrs1 = %{
        snmp_device_id: snmp_device1.id,
        vlan_id: 100,
        vlan_name: "Production"
      }

      attrs2 = %{
        snmp_device_id: snmp_device2.id,
        vlan_id: 100,
        vlan_name: "Production"
      }

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

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