defmodule Towerops.SitesTest do
  use Towerops.DataCase

  alias Towerops.Sites
  alias Towerops.Sites.Site

  describe "sites" do
    import Towerops.AccountsFixtures

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

      %{organization: organization, user: user}
    end

    @valid_attrs %{
      name: "Main Office",
      description: "Primary location",
      location: "123 Main St",
      snmp_version: "2c",
      snmp_community: "public"
    }

    @invalid_attrs %{name: nil, organization_id: nil}

    test "list_organization_sites/1 returns all sites for an organization", %{organization: organization} do
      {:ok, site1} = Sites.create_site(Map.put(@valid_attrs, :organization_id, organization.id))
      {:ok, site2} = Sites.create_site(%{name: "Branch Office", organization_id: organization.id})

      sites = Sites.list_organization_sites(organization.id)
      assert length(sites) == 2
      assert Enum.any?(sites, &(&1.id == site1.id))
      assert Enum.any?(sites, &(&1.id == site2.id))
    end

    test "list_organization_sites/1 orders sites alphabetically", %{organization: organization} do
      {:ok, _zebra} = Sites.create_site(%{name: "Zebra Site", organization_id: organization.id})
      {:ok, _alpha} = Sites.create_site(%{name: "Alpha Site", organization_id: organization.id})

      sites = Sites.list_organization_sites(organization.id)
      assert List.first(sites).name == "Alpha Site"
      assert List.last(sites).name == "Zebra Site"
    end

    test "count_organization_sites/1 returns count of sites", %{organization: organization} do
      assert Sites.count_organization_sites(organization.id) == 0

      {:ok, _} = Sites.create_site(%{name: "Site 1", organization_id: organization.id})
      {:ok, _} = Sites.create_site(%{name: "Site 2", organization_id: organization.id})

      assert Sites.count_organization_sites(organization.id) == 2
    end

    test "list_root_sites/1 returns only sites without parents", %{organization: organization} do
      {:ok, root} = Sites.create_site(%{name: "Root Site", organization_id: organization.id})

      {:ok, _child} =
        Sites.create_site(%{
          name: "Child Site",
          organization_id: organization.id,
          parent_site_id: root.id
        })

      roots = Sites.list_root_sites(organization.id)
      assert length(roots) == 1
      assert hd(roots).id == root.id
    end

    test "list_child_sites/1 returns child sites of a parent", %{organization: organization} do
      {:ok, parent} = Sites.create_site(%{name: "Parent", organization_id: organization.id})

      {:ok, child1} =
        Sites.create_site(%{
          name: "Child 1",
          organization_id: organization.id,
          parent_site_id: parent.id
        })

      {:ok, child2} =
        Sites.create_site(%{
          name: "Child 2",
          organization_id: organization.id,
          parent_site_id: parent.id
        })

      children = Sites.list_child_sites(parent.id)
      assert length(children) == 2
      assert Enum.any?(children, &(&1.id == child1.id))
      assert Enum.any?(children, &(&1.id == child2.id))
    end

    test "get_site!/1 returns the site with given id", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: organization.id})
      fetched = Sites.get_site!(site.id)
      assert fetched.id == site.id
      assert fetched.name == "Test Site"
    end

    test "get_site!/1 preloads associations", %{organization: organization} do
      {:ok, parent} = Sites.create_site(%{name: "Parent", organization_id: organization.id})

      {:ok, site} =
        Sites.create_site(%{
          name: "Child",
          organization_id: organization.id,
          parent_site_id: parent.id
        })

      fetched = Sites.get_site!(site.id)
      assert Ecto.assoc_loaded?(fetched.parent_site)
      assert Ecto.assoc_loaded?(fetched.child_sites)
      assert Ecto.assoc_loaded?(fetched.device)
    end

    test "get_site!/1 raises when site doesn't exist" do
      assert_raise Ecto.NoResultsError, fn ->
        Sites.get_site!(Ecto.UUID.generate())
      end
    end

    test "get_organization_site!/2 returns site for specific organization", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: organization.id})
      fetched = Sites.get_organization_site!(organization.id, site.id)
      assert fetched.id == site.id
    end

    test "get_organization_site!/2 preloads associations", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: organization.id})
      fetched = Sites.get_organization_site!(organization.id, site.id)
      assert Ecto.assoc_loaded?(fetched.parent_site)
      assert Ecto.assoc_loaded?(fetched.child_sites)
      assert Ecto.assoc_loaded?(fetched.device)
    end

    test "get_organization_site!/2 raises when site doesn't exist" do
      user = user_fixture()
      {:ok, org} = Towerops.Organizations.create_organization(%{name: "Org"}, user.id)

      assert_raise Ecto.NoResultsError, fn ->
        Sites.get_organization_site!(org.id, Ecto.UUID.generate())
      end
    end

    test "create_site/1 with valid data creates a site", %{organization: organization} do
      attrs = Map.put(@valid_attrs, :organization_id, organization.id)
      assert {:ok, %Site{} = site} = Sites.create_site(attrs)
      assert site.name == "Main Office"
      assert site.description == "Primary location"
      assert site.location == "123 Main St"
      assert site.snmp_version == "2c"
      assert site.snmp_community == "public"
    end

    test "create_site/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = Sites.create_site(@invalid_attrs)
    end

    test "create_site/1 validates name length", %{organization: organization} do
      # Too short
      attrs = %{name: "A", organization_id: organization.id}
      assert {:error, changeset} = Sites.create_site(attrs)
      assert "should be at least 2 character(s)" in errors_on(changeset).name

      # Too long
      attrs = %{name: String.duplicate("a", 201), organization_id: organization.id}
      assert {:error, changeset} = Sites.create_site(attrs)
      assert "should be at most 200 character(s)" in errors_on(changeset).name
    end

    test "create_site/1 validates description length", %{organization: organization} do
      attrs = %{name: "Test Site", description: String.duplicate("a", 1001), organization_id: organization.id}
      assert {:error, changeset} = Sites.create_site(attrs)
      assert "should be at most 1000 character(s)" in errors_on(changeset).description
    end

    test "create_site/1 validates location length", %{organization: organization} do
      attrs = %{name: "Test Site", location: String.duplicate("a", 201), organization_id: organization.id}
      assert {:error, changeset} = Sites.create_site(attrs)
      assert "should be at most 200 character(s)" in errors_on(changeset).location
    end

    test "create_site/1 validates snmp_version inclusion", %{organization: organization} do
      attrs = %{name: "Test Site", snmp_version: "invalid", organization_id: organization.id}
      assert {:error, changeset} = Sites.create_site(attrs)
      assert "must be 1, 2c, or 3" in errors_on(changeset).snmp_version
    end

    test "create_site/1 prevents duplicate site names within same organization", %{
      organization: organization
    } do
      # Create first site
      {:ok, _site} = Sites.create_site(%{name: "Duplicate Site", organization_id: organization.id})

      # Try to create second site with same name in same organization
      assert {:error, changeset} =
               Sites.create_site(%{name: "Duplicate Site", organization_id: organization.id})

      assert "has already been taken for this organization" in errors_on(changeset).name
    end

    test "create_site/1 allows duplicate site names across different organizations" do
      user1 = user_fixture()
      user2 = user_fixture()
      {:ok, org1} = Towerops.Organizations.create_organization(%{name: "Org 1"}, user1.id)
      {:ok, org2} = Towerops.Organizations.create_organization(%{name: "Org 2"}, user2.id)

      # Create site in first org
      assert {:ok, site1} = Sites.create_site(%{name: "Main Office", organization_id: org1.id})

      # Should be able to create site with same name in different org
      assert {:ok, site2} = Sites.create_site(%{name: "Main Office", organization_id: org2.id})

      assert site1.name == site2.name
      assert site1.organization_id != site2.organization_id
    end

    test "create_site/1 prevents circular parent reference", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: organization.id})

      # Try to make site its own parent
      assert {:error, changeset} = Sites.update_site(site, %{parent_site_id: site.id, organization_id: organization.id})
      assert "cannot be the same as the site itself" in errors_on(changeset).parent_site_id
    end

    test "update_site/2 with valid data updates the site", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Original", organization_id: organization.id})
      assert {:ok, %Site{} = updated} = Sites.update_site(site, %{name: "Updated", description: "New description"})
      assert updated.name == "Updated"
      assert updated.description == "New description"
    end

    test "update_site/2 with invalid data returns error changeset", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Test", organization_id: organization.id})
      assert {:error, %Ecto.Changeset{}} = Sites.update_site(site, %{name: nil})
    end

    test "delete_site/1 deletes the site", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Test", organization_id: organization.id})
      assert {:ok, %Site{}} = Sites.delete_site(site)
      assert_raise Ecto.NoResultsError, fn -> Sites.get_site!(site.id) end
    end

    test "change_site/1 returns a site changeset", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Test", organization_id: organization.id})
      assert %Ecto.Changeset{} = Sites.change_site(site)
    end

    test "change_site/2 returns a site changeset with changes", %{organization: organization} do
      {:ok, site} = Sites.create_site(%{name: "Test", organization_id: organization.id})
      changeset = Sites.change_site(site, %{name: "New Name"})
      assert %Ecto.Changeset{} = changeset
      assert changeset.changes.name == "New Name"
    end

    test "build_site_tree/1 builds hierarchical structure", %{organization: organization} do
      {:ok, root1} = Sites.create_site(%{name: "Root 1", organization_id: organization.id})
      {:ok, root2} = Sites.create_site(%{name: "Root 2", organization_id: organization.id})

      {:ok, child1} =
        Sites.create_site(%{
          name: "Child 1",
          organization_id: organization.id,
          parent_site_id: root1.id
        })

      {:ok, grandchild} =
        Sites.create_site(%{
          name: "Grandchild",
          organization_id: organization.id,
          parent_site_id: child1.id
        })

      tree = Sites.build_site_tree(organization.id)
      assert length(tree) == 2

      root1_node = Enum.find(tree, &(&1.site.id == root1.id))
      assert length(root1_node.children) == 1
      assert hd(root1_node.children).site.id == child1.id
      assert length(hd(root1_node.children).children) == 1
      assert hd(hd(root1_node.children).children).site.id == grandchild.id

      root2_node = Enum.find(tree, &(&1.site.id == root2.id))
      assert root2_node.children == []
    end

    test "build_site_tree/1 returns empty list for organization with no sites", %{organization: organization} do
      tree = Sites.build_site_tree(organization.id)
      assert tree == []
    end

    test "apply_snmp_config_to_all_equipment/1 updates all devices at site", %{organization: organization} do
      {:ok, site} =
        Sites.create_site(%{
          name: "Test Site",
          organization_id: organization.id,
          snmp_version: "2c",
          snmp_community: "test-community"
        })

      {:ok, device1} =
        Towerops.Devices.create_device(%{
          name: "Device 1",
          ip_address: "192.168.1.1",
          site_id: site.id,
          organization_id: organization.id,
          snmp_version: "1",
          snmp_community: "old-community"
        })

      {:ok, device2} =
        Towerops.Devices.create_device(%{
          name: "Device 2",
          ip_address: "192.168.1.2",
          site_id: site.id,
          organization_id: organization.id,
          snmp_version: "1",
          snmp_community: "old-community"
        })

      {count, _} = Sites.apply_snmp_config_to_all_equipment(site.id)
      assert count == 2

      updated1 = Towerops.Devices.get_device!(device1.id)
      assert updated1.snmp_version == "2c"
      assert updated1.snmp_community == "test-community"

      updated2 = Towerops.Devices.get_device!(device2.id)
      assert updated2.snmp_version == "2c"
      assert updated2.snmp_community == "test-community"
    end

    test "apply_agent_to_all_equipment/1 creates agent assignments", %{organization: organization, user: _user} do
      {:ok, agent_token, _token} = Towerops.Agents.create_agent_token(organization.id, "Test Agent")

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

      {:ok, device1} =
        Towerops.Devices.create_device(%{
          name: "Device 1",
          ip_address: "192.168.1.1",
          site_id: site.id,
          organization_id: organization.id
        })

      {:ok, device2} =
        Towerops.Devices.create_device(%{
          name: "Device 2",
          ip_address: "192.168.1.2",
          site_id: site.id,
          organization_id: organization.id
        })

      {count, _} = Sites.apply_agent_to_all_equipment(site.id)
      assert count == 2

      assignment1 = Towerops.Agents.get_device_assignment(device1.id)
      assert assignment1.agent_token_id == agent_token.id

      assignment2 = Towerops.Agents.get_device_assignment(device2.id)
      assert assignment2.agent_token_id == agent_token.id
    end

    test "apply_agent_to_all_equipment/1 deletes assignments when no agent set", %{
      organization: organization,
      user: _user
    } do
      {:ok, agent_token, _token} = Towerops.Agents.create_agent_token(organization.id, "Test Agent")

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

      {:ok, device} =
        Towerops.Devices.create_device(%{
          name: "Device 1",
          ip_address: "192.168.1.1",
          site_id: site.id,
          organization_id: organization.id
        })

      # Create initial assignment
      Sites.apply_agent_to_all_equipment(site.id)
      assert Towerops.Agents.get_device_assignment(device.id)

      # Remove agent from site
      {:ok, site} = Sites.update_site(site, %{agent_token_id: nil})

      # Apply again - should delete assignments
      {count, _} = Sites.apply_agent_to_all_equipment(site.id)
      assert count == 1

      # Verify assignment was deleted
      assert is_nil(Towerops.Agents.get_device_assignment(device.id))
    end

    test "reorder_site/2 reorders site to first position", %{organization: organization} do
      {:ok, site1} = Sites.create_site(%{name: "Site 1", organization_id: organization.id})
      {:ok, site2} = Sites.create_site(%{name: "Site 2", organization_id: organization.id})
      {:ok, site3} = Sites.create_site(%{name: "Site 3", organization_id: organization.id})

      # Move site3 to first position
      {:ok, _updated} = Sites.reorder_site(site3.id, 1)

      # Verify order
      sites = Sites.list_organization_sites(organization.id)
      assert Enum.at(sites, 0).id == site3.id
      assert Enum.at(sites, 0).display_order == 1

      assert Enum.at(sites, 1).id == site1.id
      assert Enum.at(sites, 1).display_order == 2

      assert Enum.at(sites, 2).id == site2.id
      assert Enum.at(sites, 2).display_order == 3
    end

    test "reorder_site/2 reorders site to last position", %{organization: organization} do
      {:ok, site1} = Sites.create_site(%{name: "Site 1", organization_id: organization.id})
      {:ok, site2} = Sites.create_site(%{name: "Site 2", organization_id: organization.id})
      {:ok, site3} = Sites.create_site(%{name: "Site 3", organization_id: organization.id})

      # Move site1 to last position
      {:ok, _updated} = Sites.reorder_site(site1.id, 3)

      # Verify order
      sites = Sites.list_organization_sites(organization.id)
      assert Enum.at(sites, 0).id == site2.id
      assert Enum.at(sites, 0).display_order == 1

      assert Enum.at(sites, 1).id == site3.id
      assert Enum.at(sites, 1).display_order == 2

      assert Enum.at(sites, 2).id == site1.id
      assert Enum.at(sites, 2).display_order == 3
    end

    test "reorder_site/2 reorders site to middle position", %{organization: organization} do
      {:ok, site1} = Sites.create_site(%{name: "Site 1", organization_id: organization.id})
      {:ok, site2} = Sites.create_site(%{name: "Site 2", organization_id: organization.id})
      {:ok, site3} = Sites.create_site(%{name: "Site 3", organization_id: organization.id})

      # Move site3 to middle position
      {:ok, _updated} = Sites.reorder_site(site3.id, 2)

      # Verify order
      sites = Sites.list_organization_sites(organization.id)
      assert Enum.at(sites, 0).id == site1.id
      assert Enum.at(sites, 0).display_order == 1

      assert Enum.at(sites, 1).id == site3.id
      assert Enum.at(sites, 1).display_order == 2

      assert Enum.at(sites, 2).id == site2.id
      assert Enum.at(sites, 2).display_order == 3
    end

    test "reorder_site/2 maintains continuous numbering", %{organization: organization} do
      {:ok, _site1} = Sites.create_site(%{name: "Site 1", organization_id: organization.id})
      {:ok, site2} = Sites.create_site(%{name: "Site 2", organization_id: organization.id})
      {:ok, _site3} = Sites.create_site(%{name: "Site 3", organization_id: organization.id})
      {:ok, site4} = Sites.create_site(%{name: "Site 4", organization_id: organization.id})

      # Reorder multiple times
      {:ok, _} = Sites.reorder_site(site2.id, 1)
      {:ok, _} = Sites.reorder_site(site4.id, 2)

      # Verify all have continuous display_order values
      sites = Sites.list_organization_sites(organization.id)
      display_orders = Enum.map(sites, & &1.display_order)
      assert display_orders == [1, 2, 3, 4]
    end

    test "reset_site_order/1 clears all display_order values", %{organization: organization} do
      {:ok, site1} = Sites.create_site(%{name: "Zebra Site", organization_id: organization.id})
      {:ok, site2} = Sites.create_site(%{name: "Alpha Site", organization_id: organization.id})

      # Set custom order
      {:ok, _} = Sites.reorder_site(site1.id, 1)
      {:ok, _} = Sites.reorder_site(site2.id, 2)

      # Verify custom order is set
      sites_before = Sites.list_organization_sites(organization.id)
      assert List.first(sites_before).id == site1.id
      assert List.first(sites_before).display_order == 1

      # Reset order
      {count, _} = Sites.reset_site_order(organization.id)
      assert count == 2

      # Verify display_order is cleared and falls back to alphabetical
      sites_after = Sites.list_organization_sites(organization.id)
      assert List.first(sites_after).name == "Alpha Site"
      assert List.first(sites_after).display_order == nil
      assert List.last(sites_after).name == "Zebra Site"
      assert List.last(sites_after).display_order == nil
    end
  end
end
