defmodule Towerops.PreseemTest do
  use Towerops.DataCase, async: true

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

  alias Towerops.Preseem
  alias Towerops.Preseem.AccessPoint
  alias Towerops.Preseem.SubscriberMetric

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

  defp insert_access_point!(org, attrs \\ %{}) do
    default = %{
      organization_id: org.id,
      preseem_id: "ap-#{System.unique_integer([:positive])}",
      name: "Test AP"
    }

    {:ok, ap} =
      %AccessPoint{}
      |> AccessPoint.changeset(Map.merge(default, attrs))
      |> Repo.insert()

    ap
  end

  defp insert_metric!(ap, attrs) do
    default = %{
      preseem_access_point_id: ap.id,
      recorded_at: DateTime.truncate(DateTime.utc_now(), :second)
    }

    {:ok, metric} =
      %SubscriberMetric{}
      |> SubscriberMetric.changeset(Map.merge(default, attrs))
      |> Repo.insert()

    metric
  end

  describe "get_access_point_for_device/1" do
    test "returns nil when no AP linked to device" do
      assert is_nil(Preseem.get_access_point_for_device(Ecto.UUID.generate()))
    end

    test "returns AP when linked to device", %{organization: org} do
      device = device_fixture(%{organization_id: org.id})

      ap =
        insert_access_point!(org, %{
          device_id: device.id,
          match_confidence: "auto_ip"
        })

      result = Preseem.get_access_point_for_device(device.id)
      assert result.id == ap.id
    end
  end

  describe "list_access_points/1" do
    test "returns all APs for an organization ordered by name", %{organization: org} do
      insert_access_point!(org, %{name: "Bravo AP"})
      insert_access_point!(org, %{name: "Alpha AP"})

      aps = Preseem.list_access_points(org.id)
      assert length(aps) == 2
      assert Enum.map(aps, & &1.name) == ["Alpha AP", "Bravo AP"]
    end

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

      insert_access_point!(org, %{name: "Our AP"})
      insert_access_point!(other_org, %{name: "Their AP"})

      aps = Preseem.list_access_points(org.id)
      assert length(aps) == 1
      assert hd(aps).name == "Our AP"
    end
  end

  describe "list_unmatched_access_points/1" do
    test "returns only unmatched and ambiguous APs", %{organization: org} do
      insert_access_point!(org, %{name: "Matched", match_confidence: "auto_mac"})
      insert_access_point!(org, %{name: "Unmatched", match_confidence: "unmatched"})
      insert_access_point!(org, %{name: "Ambiguous", match_confidence: "ambiguous"})
      insert_access_point!(org, %{name: "Manual", match_confidence: "manual"})

      unmatched = Preseem.list_unmatched_access_points(org.id)
      assert length(unmatched) == 2
      names = unmatched |> Enum.map(& &1.name) |> Enum.sort()
      assert names == ["Ambiguous", "Unmatched"]
    end
  end

  describe "list_subscriber_metrics/2" do
    test "returns metrics ordered by recorded_at desc", %{organization: org} do
      ap = insert_access_point!(org)
      now = DateTime.truncate(DateTime.utc_now(), :second)
      old = DateTime.add(now, -3600, :second)

      insert_metric!(ap, %{recorded_at: old, avg_latency: 10.0})
      insert_metric!(ap, %{recorded_at: now, avg_latency: 20.0})

      metrics = Preseem.list_subscriber_metrics(ap.id)
      assert length(metrics) == 2
      assert hd(metrics).avg_latency == 20.0
    end

    test "respects limit option", %{organization: org} do
      ap = insert_access_point!(org)
      now = DateTime.truncate(DateTime.utc_now(), :second)

      for i <- 1..5 do
        insert_metric!(ap, %{
          recorded_at: DateTime.add(now, -i, :second),
          avg_latency: i * 1.0
        })
      end

      metrics = Preseem.list_subscriber_metrics(ap.id, limit: 2)
      assert length(metrics) == 2
    end

    test "defaults to 100 limit", %{organization: org} do
      ap = insert_access_point!(org)
      # Just verify the function works with default
      metrics = Preseem.list_subscriber_metrics(ap.id)
      assert is_list(metrics)
    end
  end

  describe "link_access_point/2" do
    test "links AP to device with manual confidence", %{organization: org} do
      ap = insert_access_point!(org, %{match_confidence: "unmatched"})
      device = device_fixture(%{organization_id: org.id})

      assert {:ok, linked} = Preseem.link_access_point(ap.id, device.id)
      assert linked.device_id == device.id
      assert linked.match_confidence == "manual"
    end

    test "returns error for non-existent AP" do
      assert {:error, :not_found} = Preseem.link_access_point(Ecto.UUID.generate(), Ecto.UUID.generate())
    end
  end

  describe "unlink_access_point/1" do
    test "unlinks AP and resets to unmatched", %{organization: org} do
      device = device_fixture(%{organization_id: org.id})

      ap =
        insert_access_point!(org, %{
          device_id: device.id,
          match_confidence: "manual"
        })

      assert {:ok, unlinked} = Preseem.unlink_access_point(ap.id)
      assert is_nil(unlinked.device_id)
      assert unlinked.match_confidence == "unmatched"
    end

    test "returns error for non-existent AP" do
      assert {:error, :not_found} = Preseem.unlink_access_point(Ecto.UUID.generate())
    end
  end
end
