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

  import Towerops.AccountsFixtures

  alias Towerops.Snmp.Device
  alias Towerops.Snmp.Interface
  alias Towerops.Snmp.IpAddress

  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!()

    interface =
      %Interface{}
      |> Interface.changeset(%{
        snmp_device_id: snmp_device.id,
        if_index: 1,
        if_descr: "eth0",
        if_type: 6,
        if_admin_status: "up",
        if_oper_status: "up"
      })
      |> Repo.insert!()

    %{device: device_schema, snmp_device: snmp_device, interface: interface}
  end

  describe "changeset/2" do
    test "valid changeset with all required fields", %{interface: interface} do
      attrs = %{
        snmp_interface_id: interface.id,
        ip_address: "192.168.1.100",
        ip_type: "ipv4"
      }

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

    test "valid changeset with all fields", %{interface: interface} do
      attrs = %{
        snmp_interface_id: interface.id,
        ip_address: "192.168.1.100",
        subnet_mask: "255.255.255.0",
        prefix_length: 24,
        ip_type: "ipv4",
        is_primary: true,
        last_checked_at: DateTime.utc_now(),
        metadata: %{"source" => "ip-mib"}
      }

      changeset = IpAddress.changeset(%IpAddress{}, attrs)
      assert changeset.valid?
      assert get_field(changeset, :ip_address) == "192.168.1.100"
      assert get_field(changeset, :subnet_mask) == "255.255.255.0"
      assert get_field(changeset, :prefix_length) == 24
      assert get_field(changeset, :is_primary) == true
    end

    test "valid changeset with IPv6 address", %{interface: interface} do
      attrs = %{
        snmp_interface_id: interface.id,
        ip_address: "2001:db8::1",
        prefix_length: 64,
        ip_type: "ipv6"
      }

      changeset = IpAddress.changeset(%IpAddress{}, attrs)
      assert changeset.valid?
      assert get_field(changeset, :ip_type) == "ipv6"
    end

    test "invalid changeset without snmp_interface_id" do
      attrs = %{
        ip_address: "192.168.1.100",
        ip_type: "ipv4"
      }

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

    test "invalid changeset without ip_address", %{interface: interface} do
      attrs = %{
        snmp_interface_id: interface.id,
        ip_type: "ipv4"
      }

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

    test "invalid changeset without ip_type", %{interface: interface} do
      attrs = %{
        snmp_interface_id: interface.id,
        ip_address: "192.168.1.100"
      }

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

    test "invalid ip_type value", %{interface: interface} do
      attrs = %{
        snmp_interface_id: interface.id,
        ip_address: "192.168.1.100",
        ip_type: "invalid"
      }

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

    test "valid ip_type values", %{interface: interface} do
      for ip_type <- ~w(ipv4 ipv6) do
        attrs = %{
          snmp_interface_id: interface.id,
          ip_address:
            if(ip_type == "ipv4", do: "192.168.1.#{:rand.uniform(254)}", else: "2001:db8::#{:rand.uniform(100)}"),
          ip_type: ip_type
        }

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

    test "validates prefix_length range for IPv4", %{interface: interface} do
      for invalid_prefix <- [-1, 33, 128] do
        attrs = %{
          snmp_interface_id: interface.id,
          ip_address: "192.168.1.100",
          ip_type: "ipv4",
          prefix_length: invalid_prefix
        }

        changeset = IpAddress.changeset(%IpAddress{}, attrs)
        refute changeset.valid?, "prefix_length #{invalid_prefix} should be invalid for IPv4"
      end
    end

    test "accepts valid prefix_length for IPv4", %{interface: interface} do
      for valid_prefix <- [0, 8, 16, 24, 32] do
        attrs = %{
          snmp_interface_id: interface.id,
          ip_address: "192.168.1.100",
          ip_type: "ipv4",
          prefix_length: valid_prefix
        }

        changeset = IpAddress.changeset(%IpAddress{}, attrs)
        assert changeset.valid?, "prefix_length #{valid_prefix} should be valid for IPv4"
      end
    end
  end

  describe "unique constraint" do
    test "prevents duplicate IP addresses on same interface", %{interface: interface} do
      attrs = %{
        snmp_interface_id: interface.id,
        ip_address: "192.168.1.100",
        ip_type: "ipv4"
      }

      # Insert first IP
      changeset = IpAddress.changeset(%IpAddress{}, attrs)
      assert {:ok, _ip} = Repo.insert(changeset)

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

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

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

    test "allows same IP address on different interfaces", %{snmp_device: snmp_device, interface: interface} do
      # Create second interface
      interface2 =
        %Interface{}
        |> Interface.changeset(%{
          snmp_device_id: snmp_device.id,
          if_index: 2,
          if_descr: "eth1",
          if_type: 6,
          if_admin_status: "up",
          if_oper_status: "up"
        })
        |> Repo.insert!()

      attrs1 = %{
        snmp_interface_id: interface.id,
        ip_address: "192.168.1.100",
        ip_type: "ipv4"
      }

      attrs2 = %{
        snmp_interface_id: interface2.id,
        ip_address: "192.168.1.100",
        ip_type: "ipv4"
      }

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

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