defmodule Microwaveprop.Workers.ContactPositionBackfillWorkerTest do
  use Microwaveprop.DataCase, async: true

  alias Microwaveprop.Radio.Contact
  alias Microwaveprop.Repo
  alias Microwaveprop.Workers.ContactPositionBackfillWorker

  defp insert_raw_contact!(attrs) do
    defaults = %{
      station1: "W5XD",
      station2: "K5TR",
      band: Decimal.new(10_000),
      qso_timestamp: ~U[2026-03-28 18:00:00Z],
      submitter_email: "t@example.com",
      user_submitted: true
    }

    %Contact{}
    |> Map.merge(defaults)
    |> Map.merge(attrs)
    |> Repo.insert!()
  end

  describe "perform/1" do
    test "backfills pos1 from grid1 when pos1 is null" do
      contact =
        insert_raw_contact!(%{grid1: "EM12", grid2: "EM00", pos1: nil, pos2: nil, distance_km: nil})

      assert {:ok, %{updated: 1}} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}})

      reloaded = Repo.reload!(contact)
      assert %{"lat" => _, "lon" => _} = reloaded.pos1
      assert %{"lat" => _, "lon" => _} = reloaded.pos2
      assert reloaded.distance_km
    end

    test "backfills only pos1 when pos2 is already set" do
      pos2 = %{"lat" => 40.0, "lon" => -100.0}

      contact =
        insert_raw_contact!(%{grid1: "EM12", grid2: "EM00", pos1: nil, pos2: pos2, distance_km: nil})

      assert {:ok, %{updated: 1}} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}})

      reloaded = Repo.reload!(contact)
      assert %{"lat" => _, "lon" => _} = reloaded.pos1
      assert reloaded.pos2 == pos2
      assert reloaded.distance_km
    end

    test "is a no-op for contacts that already have both positions" do
      pos1 = %{"lat" => 32.0, "lon" => -97.0}
      pos2 = %{"lat" => 40.0, "lon" => -100.0}

      contact =
        insert_raw_contact!(%{
          grid1: "EM12",
          grid2: "EM00",
          pos1: pos1,
          pos2: pos2,
          distance_km: Decimal.new(1234)
        })

      assert {:ok, %{updated: 0}} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}})

      reloaded = Repo.reload!(contact)
      assert reloaded.pos1 == pos1
      assert reloaded.pos2 == pos2
      assert Decimal.equal?(reloaded.distance_km, Decimal.new(1234))
    end

    test "skips contact with null pos and null grid" do
      contact = insert_raw_contact!(%{grid1: nil, grid2: nil, pos1: nil, pos2: nil, distance_km: nil})

      assert {:ok, %{updated: 0}} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}})

      reloaded = Repo.reload!(contact)
      assert is_nil(reloaded.pos1)
    end

    test "fills only the resolvable side when one grid is invalid" do
      contact =
        insert_raw_contact!(%{grid1: "ZZZZ99", grid2: "EM00", pos1: nil, pos2: nil, distance_km: nil})

      assert {:ok, %{updated: 1}} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}})

      reloaded = Repo.reload!(contact)
      assert is_nil(reloaded.pos1)
      assert %{"lat" => _, "lon" => _} = reloaded.pos2
      assert is_nil(reloaded.distance_km)
    end

    test "skips contact where both grids are unresolvable" do
      contact =
        insert_raw_contact!(%{grid1: "ZZZZ99", grid2: "!!!!!!", pos1: nil, pos2: nil, distance_km: nil})

      assert {:ok, %{updated: 0}} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}})

      reloaded = Repo.reload!(contact)
      assert is_nil(reloaded.pos1)
      assert is_nil(reloaded.pos2)
    end

    test "respects limit arg" do
      for i <- 1..3 do
        insert_raw_contact!(%{
          grid1: "EM12",
          grid2: "EM00",
          pos1: nil,
          pos2: nil,
          distance_km: nil,
          qso_timestamp: DateTime.add(~U[2026-03-28 18:00:00Z], i * 3600, :second)
        })
      end

      assert {:ok, %{updated: 2}} =
               ContactPositionBackfillWorker.perform(%Oban.Job{args: %{"limit" => 2}})

      remaining =
        Contact
        |> Ecto.Query.from(where: [grid1: "EM12"])
        |> Ecto.Query.where([c], is_nil(c.pos1))
        |> Repo.aggregate(:count)

      assert remaining == 1
    end

    test "computes distance_km from both grids" do
      contact =
        insert_raw_contact!(%{grid1: "EM12", grid2: "EM00", pos1: nil, pos2: nil, distance_km: nil})

      assert {:ok, _} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}})

      reloaded = Repo.reload!(contact)
      # EM12 and EM00 span ~333 km in the Central US
      distance = Decimal.to_integer(reloaded.distance_km)
      assert distance > 0 and distance < 1000
    end

    # Scenario: user updates grid1 to higher precision via direct DB write and
    # nulls pos1 but leaves distance_km at the old (less-precise) value. Worker
    # must re-resolve pos1 AND overwrite the stale distance_km.
    test "overrides stale distance_km when filling a missing pos" do
      pos2 = %{"lat" => 30.3, "lon" => -97.7}
      stale_distance = Decimal.new(9999)

      contact =
        insert_raw_contact!(%{
          grid1: "EM12",
          grid2: "EM00",
          pos1: nil,
          pos2: pos2,
          distance_km: stale_distance
        })

      assert {:ok, %{updated: 1}} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}})

      reloaded = Repo.reload!(contact)
      assert %{"lat" => _, "lon" => _} = reloaded.pos1

      refute Decimal.equal?(reloaded.distance_km, stale_distance),
             "expected distance_km to be recomputed from refined grid, got #{reloaded.distance_km}"

      # EM12 <-> EM00 is ~333 km; sanity check the new value is in that ballpark.
      new_distance = Decimal.to_integer(reloaded.distance_km)
      assert new_distance > 0 and new_distance < 1000
    end
  end
end
