defmodule Towerops.Sites.SiteTest do
  use Towerops.DataCase

  import Towerops.AccountsFixtures
  import Towerops.OrganizationsFixtures

  alias Towerops.Sites.Site

  describe "changeset/2" do
    setup do
      user = user_fixture()
      organization = organization_fixture(user.id)
      %{organization: organization}
    end

    test "valid changeset with required fields", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "Test Site",
          organization_id: organization.id
        })

      assert changeset.valid?
    end

    test "valid changeset with all fields", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "Test Site",
          description: "A test site",
          location: "123 Main St",
          organization_id: organization.id
        })

      assert changeset.valid?
    end

    test "invalid changeset without name", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          organization_id: organization.id
        })

      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).name
    end

    test "invalid changeset without organization_id" do
      changeset =
        Site.changeset(%Site{}, %{
          name: "Test Site"
        })

      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).organization_id
    end

    test "invalid changeset with short name", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "A",
          organization_id: organization.id
        })

      refute changeset.valid?
      assert "should be at least 2 character(s)" in errors_on(changeset).name
    end

    test "invalid changeset with long name", %{organization: organization} do
      long_name = String.duplicate("a", 201)

      changeset =
        Site.changeset(%Site{}, %{
          name: long_name,
          organization_id: organization.id
        })

      refute changeset.valid?
      assert "should be at most 200 character(s)" in errors_on(changeset).name
    end

    test "invalid changeset with long description", %{organization: organization} do
      long_description = String.duplicate("a", 1001)

      changeset =
        Site.changeset(%Site{}, %{
          name: "Test Site",
          description: long_description,
          organization_id: organization.id
        })

      refute changeset.valid?
      assert "should be at most 1000 character(s)" in errors_on(changeset).description
    end

    test "invalid changeset with long location", %{organization: organization} do
      long_location = String.duplicate("a", 201)

      changeset =
        Site.changeset(%Site{}, %{
          name: "Test Site",
          location: long_location,
          organization_id: organization.id
        })

      refute changeset.valid?
      assert "should be at most 200 character(s)" in errors_on(changeset).location
    end

    test "valid changeset with parent site", %{organization: organization} do
      {:ok, parent_site} =
        Towerops.Sites.create_site(%{
          name: "Parent Site",
          organization_id: organization.id
        })

      changeset =
        Site.changeset(%Site{}, %{
          name: "Child Site",
          organization_id: organization.id,
          parent_site_id: parent_site.id
        })

      assert changeset.valid?
    end

    test "invalid changeset with circular parent reference", %{organization: organization} do
      {:ok, site} =
        Towerops.Sites.create_site(%{
          name: "Test Site",
          organization_id: organization.id
        })

      changeset =
        Site.changeset(site, %{
          parent_site_id: site.id
        })

      refute changeset.valid?
      assert "cannot be the same as the site itself" in errors_on(changeset).parent_site_id
    end

    test "changeset allows nil parent_site_id", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "Test Site",
          organization_id: organization.id,
          parent_site_id: nil
        })

      assert changeset.valid?
    end
  end

  describe "changeset/2 SNMPv3 validation" do
    setup do
      user = user_fixture()
      organization = organization_fixture(user.id)
      %{organization: organization}
    end

    test "valid SNMPv3 config with all fields", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMPv3 Site",
          organization_id: organization.id,
          snmp_version: "3",
          snmpv3_username: "snmpuser",
          snmpv3_auth_protocol: "SHA-256",
          snmpv3_auth_password: "authpass1234",
          snmpv3_priv_protocol: "AES",
          snmpv3_priv_password: "privpass1234"
        })

      assert changeset.valid?
    end

    test "derives noAuthNoPriv when no auth protocol", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMPv3 Site",
          organization_id: organization.id,
          snmp_version: "3",
          snmpv3_username: "snmpuser"
        })

      assert changeset.valid?
      assert Ecto.Changeset.get_field(changeset, :snmpv3_security_level) == "noAuthNoPriv"
    end

    test "derives authNoPriv when auth protocol but no priv", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMPv3 Site",
          organization_id: organization.id,
          snmp_version: "3",
          snmpv3_username: "snmpuser",
          snmpv3_auth_protocol: "SHA-256",
          snmpv3_auth_password: "authpass1234"
        })

      assert changeset.valid?
      assert Ecto.Changeset.get_field(changeset, :snmpv3_security_level) == "authNoPriv"
    end

    test "derives authPriv when both auth and priv protocols", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMPv3 Site",
          organization_id: organization.id,
          snmp_version: "3",
          snmpv3_username: "snmpuser",
          snmpv3_auth_protocol: "SHA-256",
          snmpv3_auth_password: "authpass1234",
          snmpv3_priv_protocol: "AES",
          snmpv3_priv_password: "privpass1234"
        })

      assert changeset.valid?
      assert Ecto.Changeset.get_field(changeset, :snmpv3_security_level) == "authPriv"
    end

    test "requires auth_password when auth_protocol set", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMPv3 Site",
          organization_id: organization.id,
          snmp_version: "3",
          snmpv3_username: "snmpuser",
          snmpv3_auth_protocol: "SHA-256"
        })

      refute changeset.valid?
      assert "required when auth protocol is selected" in errors_on(changeset).snmpv3_auth_password
    end

    test "requires priv_password when priv_protocol set", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMPv3 Site",
          organization_id: organization.id,
          snmp_version: "3",
          snmpv3_username: "snmpuser",
          snmpv3_auth_protocol: "SHA-256",
          snmpv3_auth_password: "authpass1234",
          snmpv3_priv_protocol: "AES"
        })

      refute changeset.valid?

      assert "required when privacy protocol is selected" in errors_on(changeset).snmpv3_priv_password
    end

    test "rejects auth_password shorter than 8 chars", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMPv3 Site",
          organization_id: organization.id,
          snmp_version: "3",
          snmpv3_username: "snmpuser",
          snmpv3_auth_protocol: "SHA-256",
          snmpv3_auth_password: "short"
        })

      refute changeset.valid?
      assert "must be at least 8 characters" in errors_on(changeset).snmpv3_auth_password
    end

    test "rejects priv_password shorter than 8 chars", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMPv3 Site",
          organization_id: organization.id,
          snmp_version: "3",
          snmpv3_username: "snmpuser",
          snmpv3_auth_protocol: "SHA-256",
          snmpv3_auth_password: "authpass1234",
          snmpv3_priv_protocol: "AES",
          snmpv3_priv_password: "short"
        })

      refute changeset.valid?
      assert "must be at least 8 characters" in errors_on(changeset).snmpv3_priv_password
    end

    test "accepts valid SNMP versions", %{organization: organization} do
      for version <- ["1", "2c", "3"] do
        changeset =
          Site.changeset(%Site{}, %{
            name: "SNMP Site",
            organization_id: organization.id,
            snmp_version: version
          })

        assert changeset.valid?, "expected version #{version} to be valid"
      end
    end
  end

  describe "changeset/2 MikroTik validation" do
    setup do
      user = user_fixture()
      organization = organization_fixture(user.id)
      %{organization: organization}
    end

    test "requires mikrotik_username when enabled", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "MikroTik Site",
          organization_id: organization.id,
          mikrotik_enabled: true
        })

      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).mikrotik_username
    end

    test "does not require username when not enabled", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "MikroTik Site",
          organization_id: organization.id,
          mikrotik_enabled: false
        })

      assert changeset.valid?
    end

    test "validates mikrotik_port range", %{organization: organization} do
      changeset_zero =
        Site.changeset(%Site{}, %{
          name: "MikroTik Site",
          organization_id: organization.id,
          mikrotik_port: 0
        })

      refute changeset_zero.valid?
      assert errors_on(changeset_zero).mikrotik_port != []

      changeset_too_high =
        Site.changeset(%Site{}, %{
          name: "MikroTik Site",
          organization_id: organization.id,
          mikrotik_port: 65_536
        })

      refute changeset_too_high.valid?
      assert errors_on(changeset_too_high).mikrotik_port != []
    end

    test "accepts valid SNMP port", %{organization: organization} do
      changeset =
        Site.changeset(%Site{}, %{
          name: "SNMP Port Site",
          organization_id: organization.id,
          snmp_port: 161
        })

      assert changeset.valid?
    end
  end
end
