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

  alias Microwaveprop.Radio.Contact
  alias Microwaveprop.Terrain.ElevationClient
  alias Microwaveprop.Weather
  alias Microwaveprop.Weather.HrrrClient
  alias Microwaveprop.Weather.IemClient
  alias Microwaveprop.Weather.NarrProfile
  alias Microwaveprop.Workers.ContactWeatherEnqueueWorker

  @qso_attrs %{
    station1: "W5XD",
    station2: "K5TR",
    qso_timestamp: ~U[2026-03-28 18:00:00Z],
    mode: "CW",
    band: Decimal.new("1296"),
    grid1: "EM12",
    grid2: "EM00",
    pos1: %{"lat" => 32.9, "lon" => -97.0},
    pos2: %{"lat" => 30.3, "lon" => -97.7},
    distance_km: Decimal.new("295")
  }

  defp create_contact(attrs \\ %{}) do
    {:ok, contact} =
      %Contact{}
      |> Contact.changeset(Map.merge(@qso_attrs, attrs))
      |> Repo.insert()

    contact
  end

  defp create_asos_station(code, lat, lon) do
    {:ok, station} =
      Weather.find_or_create_station(%{
        station_code: code,
        station_type: "asos",
        name: "#{code} Station",
        lat: lat,
        lon: lon
      })

    station
  end

  defp create_sounding_station(code, lat, lon) do
    {:ok, station} =
      Weather.find_or_create_station(%{
        station_code: code,
        station_type: "sounding",
        name: "#{code} Sounding",
        lat: lat,
        lon: lon
      })

    station
  end

  describe "build_weather_jobs/1" do
    test "builds ASOS jobs for nearby stations" do
      station = create_asos_station("KDFW", 32.90, -97.04)
      contact = create_contact()

      jobs = ContactWeatherEnqueueWorker.build_weather_jobs([contact])

      asos_jobs =
        Enum.filter(jobs, fn j ->
          j.changes.args["fetch_type"] == "asos"
        end)

      assert asos_jobs != []

      job = hd(asos_jobs)
      assert job.changes.args["station_id"] == station.id
      assert job.changes.args["station_code"] == "KDFW"
      assert job.changes.args["start_dt"]
      assert job.changes.args["end_dt"]
    end

    test "builds RAOB jobs for nearby sounding stations" do
      station = create_sounding_station("FWD", 32.83, -97.30)
      contact = create_contact()

      jobs = ContactWeatherEnqueueWorker.build_weather_jobs([contact])

      raob_jobs =
        Enum.filter(jobs, fn j ->
          j.changes.args["fetch_type"] == "raob"
        end)

      assert raob_jobs != []

      job = hd(raob_jobs)
      assert job.changes.args["station_id"] == station.id
      assert job.changes.args["station_code"] == "FWD"
      assert job.changes.args["sounding_time"]
    end

    test "deduplicates jobs with identical args" do
      _station = create_asos_station("KDFW", 32.90, -97.04)

      # Two QSOs at the exact same time produce identical args → deduplicated
      q1 = create_contact(%{station1: "A1", qso_timestamp: ~U[2026-03-28 18:00:00Z]})
      q2 = create_contact(%{station1: "A2", qso_timestamp: ~U[2026-03-28 18:00:00Z]})

      jobs = ContactWeatherEnqueueWorker.build_weather_jobs([q1, q2])

      asos_jobs =
        Enum.filter(jobs, fn j ->
          j.changes.args["fetch_type"] == "asos"
        end)

      assert length(asos_jobs) == 1
    end

    test "builds ASOS jobs for stations near pos2 and midpoint" do
      # Station near pos2 (30.3, -97.7) but NOT near pos1 (32.9, -97.0)
      station_near_pos2 = create_asos_station("KAUS", 30.20, -97.68)
      contact = create_contact()

      jobs = ContactWeatherEnqueueWorker.build_weather_jobs([contact])

      station_ids = Enum.map(jobs, & &1.changes.args["station_id"])
      assert station_near_pos2.id in station_ids
    end

    test "returns empty list when no stations nearby" do
      contact = create_contact(%{pos1: %{"lat" => 60.0, "lon" => -150.0}, pos2: nil})

      assert ContactWeatherEnqueueWorker.build_weather_jobs([contact]) == []
    end

    test "skips ASOS job when surface observations already exist for that station and time window" do
      station = create_asos_station("KDFW", 32.90, -97.04)
      contact = create_contact()

      # Insert a surface observation within the time window (contact ± 2 hours)
      Weather.upsert_surface_observation(station, %{
        observed_at: ~U[2026-03-28 18:00:00Z],
        temp_f: 75.0
      })

      jobs = ContactWeatherEnqueueWorker.build_weather_jobs([contact])

      asos_jobs =
        Enum.filter(jobs, fn j ->
          j.changes.args["fetch_type"] == "asos"
        end)

      assert asos_jobs == []
    end

    test "skips RAOB job when sounding already exists for that station and time" do
      station = create_sounding_station("FWD", 32.83, -97.30)
      contact = create_contact()

      # Insert soundings for both bracketing times (00Z and 12Z on 2026-03-28)
      for time <- Weather.sounding_times_around(contact.qso_timestamp) do
        Weather.upsert_sounding(station, %{
          observed_at: time,
          profile: [%{"pres" => 1013.0, "tmpc" => 25.0}],
          level_count: 1
        })
      end

      jobs = ContactWeatherEnqueueWorker.build_weather_jobs([contact])

      raob_jobs =
        Enum.filter(jobs, fn j ->
          j.changes.args["fetch_type"] == "raob"
        end)

      assert raob_jobs == []
    end
  end

  describe "perform/1" do
    setup do
      # Stub IEM client so inline Oban doesn't blow up when executing enqueued jobs
      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")

          "/iemre/" <> _ ->
            Req.Test.json(conn, %{"data" => [%{"utc_hour" => 0, "p01m_mm" => 0.0}]})

          _ ->
            Req.Test.json(conn, %{"profiles" => []})
        end
      end)

      # Stub HRRR client — return 404 so fetch_profile returns an error
      Req.Test.stub(HrrrClient, fn conn ->
        Plug.Conn.send_resp(conn, 404, "not found")
      end)

      # Stub elevation API for terrain profile workers
      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

    test "enqueues weather jobs and marks QSOs as queued" do
      _station = create_asos_station("KDFW", 32.90, -97.04)
      contact = create_contact()

      assert contact.weather_status == :pending

      assert :ok = ContactWeatherEnqueueWorker.perform(%Oban.Job{args: %{}})

      updated = Repo.get!(Contact, contact.id)
      assert updated.weather_status == :queued
    end

    test "returns :ok when no unprocessed QSOs exist" do
      assert :ok = ContactWeatherEnqueueWorker.perform(%Oban.Job{args: %{}})
    end

    test "backfills distance_km for QSOs missing it" do
      _station = create_asos_station("KDFW", 32.90, -97.04)
      contact = create_contact(%{distance_km: nil})

      assert :ok = ContactWeatherEnqueueWorker.perform(%Oban.Job{args: %{}})

      updated = Repo.get!(Contact, contact.id)
      assert updated.distance_km
      assert_in_delta Decimal.to_float(updated.distance_km), 295, 5
    end

    test "skips QSOs without positions" do
      _station = create_asos_station("KDFW", 32.90, -97.04)
      _qso = create_contact(%{pos1: nil})

      assert :ok = ContactWeatherEnqueueWorker.perform(%Oban.Job{args: %{}})

      # QSO without pos1 should not be marked as queued
      all_contacts = Repo.all(Contact)
      refute Enum.any?(all_contacts, &(&1.weather_status == :queued))
    end

    test "enqueues HRRR jobs and marks QSOs as hrrr_status" do
      contact = create_contact()
      assert contact.hrrr_status == :pending

      assert :ok = ContactWeatherEnqueueWorker.perform(%Oban.Job{args: %{}})

      updated = Repo.get!(Contact, contact.id)
      assert updated.hrrr_status == :queued
    end
  end

  describe "HRRR enrichment routing (Phase 3 Stream C → hrrr_fetch_tasks)" do
    alias Microwaveprop.Weather.HrrrPointEnqueuer

    test "enqueue_for_contacts writes one hrrr_fetch_tasks row per valid_time" do
      import Ecto.Query

      contact = create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}})
      {:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([contact])

      rows =
        Repo.all(from t in "hrrr_fetch_tasks", select: %{vt: t.valid_time, points: t.points, status: t.status})

      assert [row] = rows
      # pos1 + midpoint + pos2 → 3 distinct HRRR grid cells in the batch.
      assert length(row.points) == 3
      assert row.status == "queued"
    end

    test "enqueue_for_contacts skips pre-2014 contacts (NARR's territory)" do
      import Ecto.Query

      contact = create_contact(%{qso_timestamp: ~U[2010-06-15 18:00:00Z]})
      {:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([contact])

      assert [] = Repo.all(from t in "hrrr_fetch_tasks", select: t.id)
    end

    test "enqueue_for_contact marks hrrr_status :complete when all path points already have profiles" do
      # Regression: ~22k contacts stuck at :queued forever because
      # HrrrPointEnqueuer saw 0 missing points (data already landed)
      # and inserted nothing, but hrrr_placeholder_jobs always returned
      # [contact.id] so mark_hrrr_status!/3 re-flagged the row as
      # :queued every backfill cron tick. Contacts with full data must
      # flip to :complete.
      # grid2 = nil prevents Radio.ensure_positions! from deriving pos2,
      # so contact_path_points returns only the pos1 single-point form.
      contact =
        create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}, pos2: nil, grid2: nil})

      rounded = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp)

      # Seed an hrrr_profile at the exact HRRR grid cell for pos1
      {rlat, rlon} = Weather.round_to_hrrr_grid(32.907, -97.038)
      {:ok, _} = Weather.upsert_hrrr_profile(%{lat: rlat, lon: rlon, valid_time: rounded, profile: []})

      :ok = ContactWeatherEnqueueWorker.enqueue_for_contact(contact, [:hrrr])

      assert %Contact{hrrr_status: :complete} = Repo.get!(Contact, contact.id)
    end

    test "re-enqueuing the same valid_time UPSERT-unions the points" do
      import Ecto.Query

      c1 = create_contact(%{station1: "A1", pos1: %{"lat" => 32.90, "lon" => -97.04}, pos2: nil})
      c2 = create_contact(%{station1: "A2", pos1: %{"lat" => 33.50, "lon" => -96.50}, pos2: nil})

      {:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([c1])
      {:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([c2])

      rows = Repo.all(from t in "hrrr_fetch_tasks", select: %{vt: t.valid_time, points: t.points})
      assert [row] = rows
      assert length(row.points) == 2
    end

    test "re-enqueuing a 'done' row flips status back to queued" do
      import Ecto.Query

      contact = create_contact(%{pos1: %{"lat" => 32.90, "lon" => -97.04}, pos2: nil})
      {:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([contact])

      Repo.update_all(
        from(t in "hrrr_fetch_tasks"),
        set: [status: "done", completed_at: DateTime.utc_now()]
      )

      {:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([contact])
      assert [row] = Repo.all(from t in "hrrr_fetch_tasks", select: %{status: t.status})
      assert row.status == "queued"
    end
  end

  describe "build_terrain_jobs/1" do
    test "builds one terrain job per QSO" do
      contact = create_contact()

      jobs = ContactWeatherEnqueueWorker.build_terrain_jobs([contact])

      assert length(jobs) == 1
      job = hd(jobs)
      assert job.changes.args["contact_id"] == contact.id
    end

    test "deduplicates by contact_id" do
      contact = create_contact()

      jobs = ContactWeatherEnqueueWorker.build_terrain_jobs([contact, contact])
      assert length(jobs) == 1
    end
  end

  describe "perform/1 terrain enqueue" do
    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")

          "/iemre/" <> _ ->
            Req.Test.json(conn, %{"data" => [%{"utc_hour" => 0, "p01m_mm" => 0.0}]})

          _ ->
            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

    test "enqueues terrain jobs and marks QSOs as terrain_status" do
      contact = create_contact()
      assert contact.terrain_status == :pending

      assert :ok = ContactWeatherEnqueueWorker.perform(%Oban.Job{args: %{}})

      updated = Repo.get!(Contact, contact.id)
      assert updated.terrain_status == :queued
    end

    test "marks terrain :unavailable for QSOs missing pos2" do
      contact = create_contact(%{pos2: nil, grid2: nil})

      assert :ok = ContactWeatherEnqueueWorker.enqueue_for_contact(contact)

      updated = Repo.get!(Contact, contact.id)
      assert updated.terrain_status == :unavailable
    end
  end

  describe "build_iemre_jobs/1" do
    test "builds IEMRE jobs for all path points (pos1, midpoint, pos2)" do
      contact = create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}})

      jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([contact])

      # pos1, midpoint, pos2 may have distinct IEMRE grid cells
      assert length(jobs) >= 2
      lats = Enum.map(jobs, & &1.changes.args["lat"])
      assert 32.875 in lats
    end

    test "builds only one IEMRE job when pos2 is nil" do
      contact = create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}, pos2: nil})

      jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([contact])

      assert length(jobs) == 1
      job = hd(jobs)
      assert job.changes.args["lat"] == 32.875
      assert job.changes.args["lon"] == -97.0
      assert job.changes.args["date"] == "2026-03-28"
    end

    test "deduplicates jobs across QSOs with identical path points" do
      q1 =
        create_contact(%{
          station1: "A1",
          qso_timestamp: ~U[2026-03-28 18:10:00Z],
          pos1: %{"lat" => 32.90, "lon" => -97.04},
          pos2: nil
        })

      q2 =
        create_contact(%{
          station1: "A2",
          qso_timestamp: ~U[2026-03-28 20:20:00Z],
          pos1: %{"lat" => 32.90, "lon" => -97.04},
          pos2: nil
        })

      jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([q1, q2])

      # Same location and same date → 1 job
      assert length(jobs) == 1
    end

    test "creates separate jobs for different dates" do
      q1 =
        create_contact(%{
          station1: "A1",
          qso_timestamp: ~U[2026-03-28 23:00:00Z],
          pos1: %{"lat" => 32.90, "lon" => -97.04},
          pos2: nil
        })

      q2 =
        create_contact(%{
          station1: "A2",
          qso_timestamp: ~U[2026-03-29 01:00:00Z],
          pos1: %{"lat" => 32.90, "lon" => -97.04},
          pos2: nil
        })

      jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([q1, q2])
      assert length(jobs) == 2
    end

    test "skips QSOs without pos1" do
      contact = create_contact(%{pos1: nil})
      assert ContactWeatherEnqueueWorker.build_iemre_jobs([contact]) == []
    end

    test "skips only path points where IEMRE observation already exists" do
      contact = create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}})

      # Insert an IEMRE observation at the pos1 rounded grid point only
      Weather.upsert_iemre_observation(%{
        lat: 32.875,
        lon: -97.0,
        date: ~D[2026-03-28],
        hourly: [%{"hour" => 0, "p01m_mm" => 0.0}]
      })

      jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([contact])

      # pos1 skipped, midpoint + pos2 still need fetching
      assert jobs != []
      lats = Enum.map(jobs, & &1.changes.args["lat"])
      refute 32.875 in lats
    end
  end

  describe "perform/1 iemre enqueue" do
    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")

          "/iemre/" <> _ ->
            Req.Test.json(conn, %{"data" => [%{"utc_hour" => 0, "p01m_mm" => 0.0}]})

          _ ->
            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

    test "enqueues IEMRE jobs and marks QSOs as iemre_status" do
      contact = create_contact()
      assert contact.iemre_status == :pending

      assert :ok = ContactWeatherEnqueueWorker.perform(%Oban.Job{args: %{}})

      updated = Repo.get!(Contact, contact.id)
      assert updated.iemre_status == :queued
    end
  end

  describe "build_narr_jobs/1" do
    test "builds NarrFetchWorker jobs for each rounded path point" do
      contact =
        create_contact(%{
          qso_timestamp: ~U[2010-06-15 13:42:00Z],
          pos1: %{"lat" => 32.907, "lon" => -97.038},
          pos2: nil
        })

      jobs = ContactWeatherEnqueueWorker.build_narr_jobs([contact])

      assert length(jobs) == 1
      job = hd(jobs)
      assert job.changes.worker == "Microwaveprop.Workers.NarrFetchWorker"
      # 32.907 → 33.0 and -97.038 → -97.0 after 0.25° snap
      assert job.changes.args["lat"] == 33.0
      assert job.changes.args["lon"] == -97.0
    end

    test "snaps valid_time to the nearest 3-hourly NARR analysis slot" do
      contact =
        create_contact(%{
          qso_timestamp: ~U[2010-06-15 13:42:00Z],
          pos1: %{"lat" => 32.907, "lon" => -97.038},
          pos2: nil
        })

      jobs = ContactWeatherEnqueueWorker.build_narr_jobs([contact])

      job = hd(jobs)
      # 13:42 UTC snaps down to the 12:00 UTC NARR analysis slot.
      assert job.changes.args["valid_time"] == "2010-06-15T12:00:00Z"
    end

    test "builds one job per distinct rounded path point" do
      contact =
        create_contact(%{
          qso_timestamp: ~U[2010-06-15 12:00:00Z],
          pos1: %{"lat" => 32.907, "lon" => -97.038},
          pos2: %{"lat" => 30.3, "lon" => -97.7}
        })

      jobs = ContactWeatherEnqueueWorker.build_narr_jobs([contact])

      # pos1, midpoint, pos2 round to three distinct 0.25° grid cells.
      assert length(jobs) == 3
      assert Enum.all?(jobs, &(&1.changes.worker == "Microwaveprop.Workers.NarrFetchWorker"))
    end

    test "skips path points that already have a narr_profile row" do
      contact =
        create_contact(%{
          qso_timestamp: ~U[2010-06-15 13:42:00Z],
          pos1: %{"lat" => 32.907, "lon" => -97.038},
          pos2: nil
        })

      # pos1 (32.907, -97.038) snaps to the (33.0, -97.0) 0.25° grid cell
      %NarrProfile{}
      |> NarrProfile.changeset(%{
        lat: 33.0,
        lon: -97.0,
        valid_time: ~U[2010-06-15 12:00:00Z],
        profile: []
      })
      |> Repo.insert!()

      jobs = ContactWeatherEnqueueWorker.build_narr_jobs([contact])
      assert jobs == []
    end

    test "skips QSOs without pos1" do
      contact = create_contact(%{pos1: nil})
      assert ContactWeatherEnqueueWorker.build_narr_jobs([contact]) == []
    end

    test "skips post-2014 contacts (outside NARR coverage — HRRR's job)" do
      # NARR's NCEI archive stops at 2014-10-02; post-coverage fetches 404.
      contact =
        create_contact(%{
          qso_timestamp: ~U[2020-09-20 21:00:00Z],
          pos1: %{"lat" => 43.25, "lon" => -78.25},
          pos2: nil
        })

      assert ContactWeatherEnqueueWorker.build_narr_jobs([contact]) == []
    end

    test "skips pre-1979 contacts (below NARR coverage start)" do
      contact =
        create_contact(%{
          qso_timestamp: ~U[1975-06-15 12:00:00Z],
          pos1: %{"lat" => 32.9, "lon" => -97.0},
          pos2: nil
        })

      assert ContactWeatherEnqueueWorker.build_narr_jobs([contact]) == []
    end

    test "deduplicates jobs with identical args across contacts" do
      q1 =
        create_contact(%{
          station1: "A1",
          qso_timestamp: ~U[2010-06-15 12:10:00Z],
          pos1: %{"lat" => 32.907, "lon" => -97.038},
          pos2: nil
        })

      q2 =
        create_contact(%{
          station1: "A2",
          qso_timestamp: ~U[2010-06-15 13:42:00Z],
          pos1: %{"lat" => 32.907, "lon" => -97.038},
          pos2: nil
        })

      jobs = ContactWeatherEnqueueWorker.build_narr_jobs([q1, q2])

      # Both snap to 12:00Z and round to the same 0.25° grid cell → 1 job.
      assert length(jobs) == 1
      assert hd(jobs).changes.worker == "Microwaveprop.Workers.NarrFetchWorker"
    end
  end

  describe "enqueue_for_contact/1" do
    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")

          "/iemre/" <> _ ->
            Req.Test.json(conn, %{"data" => [%{"utc_hour" => 0, "p01m_mm" => 0.0}]})

          _ ->
            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

    test "enqueues all enrichment jobs and marks QSO as fully queued" do
      _station = create_asos_station("KDFW", 32.90, -97.04)
      contact = create_contact()

      assert contact.weather_status == :pending
      assert contact.hrrr_status == :pending
      assert contact.terrain_status == :pending
      assert contact.iemre_status == :pending

      assert :ok = ContactWeatherEnqueueWorker.enqueue_for_contact(contact)

      updated = Repo.get!(Contact, contact.id)
      assert updated.weather_status == :queued
      assert updated.hrrr_status == :queued
      assert updated.terrain_status == :queued
      assert updated.iemre_status == :queued
    end

    test "derives pos1/pos2 from grid1/grid2 when missing, then queues path enrichments" do
      _station = create_asos_station("KDFW", 32.90, -97.04)
      # Historical rows can have grids without lat/lon maps; ensure_positions!/1
      # must fill them in before path enrichments get a chance to mark :unavailable.
      contact = create_contact(%{pos1: nil, pos2: nil, grid1: "EM12", grid2: "EM00"})
      assert is_nil(contact.pos1)
      assert is_nil(contact.pos2)

      assert :ok = ContactWeatherEnqueueWorker.enqueue_for_contact(contact)

      updated = Repo.get!(Contact, contact.id)
      assert updated.pos1
      assert updated.pos2
      assert updated.terrain_status == :queued
      assert updated.radar_status == :queued
    end

    test "works for QSO without pos2 or grid2 (path statuses flip to :unavailable)" do
      _station = create_asos_station("KDFW", 32.90, -97.04)
      contact = create_contact(%{pos2: nil, grid2: nil})

      assert :ok = ContactWeatherEnqueueWorker.enqueue_for_contact(contact)

      updated = Repo.get!(Contact, contact.id)
      assert updated.weather_status == :queued
      assert updated.hrrr_status == :queued
      assert updated.iemre_status == :queued
      # Path-dependent enrichments need pos2 — marking :unavailable keeps
      # BackfillEnqueueWorker from re-scanning these contacts every cron.
      assert updated.terrain_status == :unavailable
      assert updated.radar_status == :unavailable
      assert updated.propagation_mechanism_status == :unavailable
    end

    # Pre-2014 contacts have no HRRR data ever; mark hrrr_status :unavailable
    # directly so BackfillEnqueueWorker's NARR filter picks them up and the
    # reconcile/mark_status ping-pong stops.
    test "pre-2014 contact: hrrr_status set to :unavailable and no HRRR jobs" do
      _station = create_asos_station("KDFW", 32.90, -97.04)
      contact = create_contact(%{qso_timestamp: ~U[2010-06-15 18:00:00Z]})

      assert :ok = ContactWeatherEnqueueWorker.enqueue_for_contact(contact)

      updated = Repo.get!(Contact, contact.id)
      assert updated.hrrr_status == :unavailable
    end
  end
end
