defmodule Towerops.Gaiia.MatchSuggestionsTest do
  use Towerops.DataCase, async: true

  import Towerops.AccountsFixtures
  import Towerops.DevicesFixtures
  import Towerops.OrganizationsFixtures

  alias Towerops.Gaiia
  alias Towerops.Sites

  setup do
    user = user_fixture()
    org = organization_fixture(user.id)
    %{org: org, user: user}
  end

  describe "suggest_site_matches/2" do
    test "returns empty list when no sites exist", %{org: org} do
      {:ok, gaiia_site} =
        Gaiia.upsert_network_site(org.id, %{
          gaiia_id: "gaiia-site-1",
          name: "Tower Alpha"
        })

      assert Gaiia.suggest_site_matches(org.id, gaiia_site) == []
    end

    test "returns empty list when no names match", %{org: org} do
      {:ok, _site} =
        Sites.create_site(%{
          name: "Completely Different",
          organization_id: org.id
        })

      {:ok, gaiia_site} =
        Gaiia.upsert_network_site(org.id, %{
          gaiia_id: "gaiia-site-1",
          name: "Tower Alpha"
        })

      assert Gaiia.suggest_site_matches(org.id, gaiia_site) == []
    end

    test "matches site by exact name case-insensitively", %{org: org} do
      {:ok, site} =
        Sites.create_site(%{
          name: "Tower Alpha",
          organization_id: org.id
        })

      {:ok, gaiia_site} =
        Gaiia.upsert_network_site(org.id, %{
          gaiia_id: "gaiia-site-1",
          name: "tower alpha"
        })

      results = Gaiia.suggest_site_matches(org.id, gaiia_site)

      assert length(results) == 1
      assert [%{entity: matched_site, match_type: "name", confidence: _}] = results
      assert matched_site.id == site.id
    end

    test "matches site by partial name (contains)", %{org: org} do
      {:ok, site} =
        Sites.create_site(%{
          name: "North Tower Alpha Sector",
          organization_id: org.id
        })

      {:ok, gaiia_site} =
        Gaiia.upsert_network_site(org.id, %{
          gaiia_id: "gaiia-site-1",
          name: "Tower Alpha"
        })

      results = Gaiia.suggest_site_matches(org.id, gaiia_site)

      assert length(results) == 1
      assert [%{entity: matched_site, match_type: "name"}] = results
      assert matched_site.id == site.id
    end

    test "matches when towerops site name is substring of gaiia name", %{org: org} do
      {:ok, site} =
        Sites.create_site(%{
          name: "Alpha",
          organization_id: org.id
        })

      {:ok, gaiia_site} =
        Gaiia.upsert_network_site(org.id, %{
          gaiia_id: "gaiia-site-1",
          name: "Tower Alpha Sector"
        })

      results = Gaiia.suggest_site_matches(org.id, gaiia_site)

      assert length(results) == 1
      assert [%{entity: matched_site, match_type: "name"}] = results
      assert matched_site.id == site.id
    end

    test "returns multiple matching sites", %{org: org} do
      {:ok, site1} =
        Sites.create_site(%{
          name: "Tower Alpha",
          organization_id: org.id
        })

      {:ok, site2} =
        Sites.create_site(%{
          name: "Tower Alpha East",
          organization_id: org.id
        })

      {:ok, gaiia_site} =
        Gaiia.upsert_network_site(org.id, %{
          gaiia_id: "gaiia-site-1",
          name: "Tower Alpha"
        })

      results = Gaiia.suggest_site_matches(org.id, gaiia_site)

      assert length(results) == 2
      matched_ids = Enum.map(results, fn %{entity: s} -> s.id end)
      assert site1.id in matched_ids
      assert site2.id in matched_ids
    end

    test "does not match sites from other organizations", %{org: org, user: _user} do
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)

      {:ok, _other_site} =
        Sites.create_site(%{
          name: "Tower Alpha",
          organization_id: other_org.id
        })

      {:ok, gaiia_site} =
        Gaiia.upsert_network_site(org.id, %{
          gaiia_id: "gaiia-site-1",
          name: "Tower Alpha"
        })

      assert Gaiia.suggest_site_matches(org.id, gaiia_site) == []
    end

    test "does not match when gaiia site has no name", %{org: org} do
      {:ok, _site} =
        Sites.create_site(%{
          name: "Tower Alpha",
          organization_id: org.id
        })

      {:ok, gaiia_site} =
        Gaiia.upsert_network_site(org.id, %{
          gaiia_id: "gaiia-site-1",
          name: nil
        })

      assert Gaiia.suggest_site_matches(org.id, gaiia_site) == []
    end
  end

  describe "suggest_device_matches/2" do
    test "returns empty list when no devices exist", %{org: org} do
      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      assert Gaiia.suggest_device_matches(org.id, gaiia_item) == []
    end

    test "returns empty list when nothing matches", %{org: org} do
      _device =
        device_fixture(%{
          organization_id: org.id,
          name: "Switch Bravo",
          ip_address: "192.168.1.1"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      assert Gaiia.suggest_device_matches(org.id, gaiia_item) == []
    end

    test "matches device by exact IP address", %{org: org} do
      device =
        device_fixture(%{
          organization_id: org.id,
          name: "Switch One",
          ip_address: "10.0.0.1"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "Totally Different Name",
          ip_address: "10.0.0.1"
        })

      results = Gaiia.suggest_device_matches(org.id, gaiia_item)

      assert length(results) == 1
      assert [%{entity: matched, match_type: "ip", confidence: confidence}] = results
      assert matched.id == device.id
      assert confidence in [:high, :medium]
    end

    test "matches device by name case-insensitively", %{org: org} do
      device =
        device_fixture(%{
          organization_id: org.id,
          name: "Router Alpha",
          ip_address: "192.168.1.1"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "router alpha",
          ip_address: "10.99.99.99"
        })

      results = Gaiia.suggest_device_matches(org.id, gaiia_item)

      assert length(results) == 1
      assert [%{entity: matched, match_type: "name", confidence: :low}] = results
      assert matched.id == device.id
    end

    test "matches device by partial name (contains)", %{org: org} do
      device =
        device_fixture(%{
          organization_id: org.id,
          name: "Core Router Alpha Main",
          ip_address: "192.168.1.1"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "Router Alpha",
          ip_address: "10.99.99.99"
        })

      results = Gaiia.suggest_device_matches(org.id, gaiia_item)

      assert length(results) == 1
      assert [%{entity: matched, match_type: "name"}] = results
      assert matched.id == device.id
    end

    test "returns multiple match types for same device sorted by confidence", %{org: org} do
      device =
        device_fixture(%{
          organization_id: org.id,
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      results = Gaiia.suggest_device_matches(org.id, gaiia_item)

      assert length(results) >= 2

      # All matches should reference the same device
      matched_ids = results |> Enum.map(fn %{entity: d} -> d.id end) |> Enum.uniq()
      assert matched_ids == [device.id]

      # IP match should come before name match (higher confidence first)
      match_types = Enum.map(results, fn %{match_type: t} -> t end)
      ip_index = Enum.find_index(match_types, &(&1 == "ip"))
      name_index = Enum.find_index(match_types, &(&1 == "name"))
      assert ip_index < name_index
    end

    test "does not match devices from other organizations", %{org: org} do
      other_user = user_fixture()
      other_org = organization_fixture(other_user.id)

      _other_device =
        device_fixture(%{
          organization_id: other_org.id,
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      assert Gaiia.suggest_device_matches(org.id, gaiia_item) == []
    end

    test "does not match when gaiia item has neither IP nor name", %{org: org} do
      _device =
        device_fixture(%{
          organization_id: org.id,
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1"
        })

      assert Gaiia.suggest_device_matches(org.id, gaiia_item) == []
    end

    test "IP match has higher confidence than name match", %{org: org} do
      device_ip =
        device_fixture(%{
          organization_id: org.id,
          name: "Unrelated Name",
          ip_address: "10.0.0.1"
        })

      device_name =
        device_fixture(%{
          organization_id: org.id,
          name: "Router Alpha",
          ip_address: "192.168.99.99"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      results = Gaiia.suggest_device_matches(org.id, gaiia_item)

      assert length(results) == 2

      # IP match should appear first (higher confidence)
      assert [first | _rest] = results
      assert first.entity.id == device_ip.id
      assert first.match_type == "ip"

      # Name match should be last
      name_match = Enum.find(results, fn %{match_type: t} -> t == "name" end)
      assert name_match.entity.id == device_name.id
      assert name_match.confidence == :low
    end

    test "does not produce duplicate matches for same device and match type", %{org: org} do
      device =
        device_fixture(%{
          organization_id: org.id,
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      {:ok, gaiia_item} =
        Gaiia.upsert_inventory_item(org.id, %{
          gaiia_id: "gaiia-item-1",
          name: "Router Alpha",
          ip_address: "10.0.0.1"
        })

      results = Gaiia.suggest_device_matches(org.id, gaiia_item)

      # Each match type should appear at most once per device
      type_device_pairs =
        Enum.map(results, fn %{entity: d, match_type: t} -> {d.id, t} end)

      assert type_device_pairs == Enum.uniq(type_device_pairs)

      # Verify the device is present
      assert Enum.any?(results, fn %{entity: d} -> d.id == device.id end)
    end
  end
end
