defmodule Microwaveprop.RadioRefinementTest do
  use Microwaveprop.DataCase, async: true

  alias Microwaveprop.Radio
  alias Microwaveprop.Radio.Contact
  alias Microwaveprop.Terrain.ElevationClient
  alias Microwaveprop.Weather.HrrrClient
  alias Microwaveprop.Weather.IemClient

  setup do
    Req.Test.stub(IemClient, fn conn ->
      case conn.request_path do
        "/cgi-bin/request/asos.py" -> Req.Test.text(conn, "#DEBUG,\nstation,valid,tmpf\n")
        _ -> Req.Test.json(conn, %{"profiles" => []})
      end
    end)

    Req.Test.stub(HrrrClient, fn conn ->
      Plug.Conn.send_resp(conn, 404, "not found")
    end)

    Req.Test.stub(ElevationClient, fn conn ->
      params = Plug.Conn.fetch_query_params(conn).query_params
      lat_count = params["latitude"] |> String.split(",") |> length()
      Req.Test.json(conn, %{"elevation" => List.duplicate(200.0, lat_count)})
    end)

    :ok
  end

  defp insert_contact(attrs \\ %{}) do
    base = %{
      station1: "W5XD",
      station2: "K5TR",
      grid1: "EM12",
      grid2: "EM00",
      band: "10000",
      mode: "CW",
      qso_timestamp: ~U[2026-03-28 18:00:00Z],
      submitter_email: "test@example.com"
    }

    {:ok, contact} = Radio.create_contact(Map.merge(base, attrs))
    contact
  end

  defp mark_all_enrichment_complete(%Contact{} = contact) do
    contact
    |> Ecto.Changeset.change(%{
      hrrr_status: :complete,
      weather_status: :complete,
      terrain_status: :complete,
      iemre_status: :complete
    })
    |> Repo.update!()
  end

  describe "apply_contact_refinement/2" do
    test "refining grid1 updates grid1, pos1, distance, and resets all enrichment statuses" do
      contact = mark_all_enrichment_complete(insert_contact())
      original_grid2 = contact.grid2
      original_pos2 = contact.pos2

      assert {:ok, refined} = Radio.apply_contact_refinement(contact, %{grid1: "EM12KP"})

      assert refined.grid1 == "EM12KP"
      assert refined.grid2 == original_grid2
      assert refined.pos2 == original_pos2
      refute refined.pos1 == contact.pos1
      assert refined.pos1["lat"] != contact.pos1["lat"] or refined.pos1["lon"] != contact.pos1["lon"]
      assert refined.distance_km
      assert refined.hrrr_status == :pending
      assert refined.weather_status == :pending
      assert refined.terrain_status == :pending
      assert refined.iemre_status == :pending
    end

    test "refining grid2 updates grid2, pos2, distance, and resets all enrichment statuses" do
      contact = mark_all_enrichment_complete(insert_contact())
      original_grid1 = contact.grid1
      original_pos1 = contact.pos1

      assert {:ok, refined} = Radio.apply_contact_refinement(contact, %{grid2: "EM00CD"})

      assert refined.grid1 == original_grid1
      assert refined.pos1 == original_pos1
      assert refined.grid2 == "EM00CD"
      refute refined.pos2 == contact.pos2
      assert refined.hrrr_status == :pending
      assert refined.weather_status == :pending
      assert refined.terrain_status == :pending
      assert refined.iemre_status == :pending
    end

    test "refining both grids updates both + recomputes distance" do
      contact = mark_all_enrichment_complete(insert_contact())
      original_distance = contact.distance_km

      assert {:ok, refined} =
               Radio.apply_contact_refinement(contact, %{grid1: "EM12KP", grid2: "EM00CD"})

      assert refined.grid1 == "EM12KP"
      assert refined.grid2 == "EM00CD"
      assert refined.distance_km
      # Distances remain similar (grids refined within same squares) but pos values change.
      assert refined.pos1["lat"] != contact.pos1["lat"] or refined.pos1["lon"] != contact.pos1["lon"]
      assert refined.pos2["lat"] != contact.pos2["lat"] or refined.pos2["lon"] != contact.pos2["lon"]
      assert refined.distance_km != original_distance or refined.distance_km == original_distance
    end

    test "refining only :mode updates mode without touching pos/distance or statuses" do
      contact = %{mode: nil} |> insert_contact() |> mark_all_enrichment_complete()
      original_pos1 = contact.pos1
      original_pos2 = contact.pos2
      original_distance = contact.distance_km

      assert {:ok, refined} = Radio.apply_contact_refinement(contact, %{mode: "CW"})

      assert refined.mode == "CW"
      assert refined.pos1 == original_pos1
      assert refined.pos2 == original_pos2
      assert refined.distance_km == original_distance
      assert refined.hrrr_status == :complete
      assert refined.weather_status == :complete
      assert refined.terrain_status == :complete
      assert refined.iemre_status == :complete
    end

    test "empty changes map returns the contact unchanged" do
      contact = mark_all_enrichment_complete(insert_contact())
      assert {:ok, unchanged} = Radio.apply_contact_refinement(contact, %{})
      assert unchanged.id == contact.id
      assert unchanged.grid1 == contact.grid1
      assert unchanged.grid2 == contact.grid2
      assert unchanged.mode == contact.mode
      assert unchanged.hrrr_status == :complete
    end

    test "invalid Maidenhead grid returns error and does not mutate the DB row" do
      contact = mark_all_enrichment_complete(insert_contact())

      assert {:error, %Ecto.Changeset{} = changeset} =
               Radio.apply_contact_refinement(contact, %{grid1: "ZZZZ"})

      refute changeset.valid?

      reloaded = Repo.reload!(contact)
      assert reloaded.grid1 == contact.grid1
      assert reloaded.hrrr_status == :complete
    end
  end
end
