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 "build_hrrr_jobs/1" do
    test "batches all path points into one job per HRRR hour" do
      contact = create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}})

      jobs = ContactWeatherEnqueueWorker.build_hrrr_jobs([contact])

      # All 3 path points share the same HRRR hour → 1 batch job
      assert length(jobs) == 1
      job = hd(jobs)
      points = job.changes.args["points"]
      # pos1, midpoint, pos2 = 3 distinct grid points in the batch
      assert length(points) == 3
      lats = Enum.map(points, & &1["lat"])
      assert 32.91 in lats
    end

    test "batch contains single point when pos2 is nil" do
      contact = create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}, pos2: nil})

      jobs = ContactWeatherEnqueueWorker.build_hrrr_jobs([contact])

      assert length(jobs) == 1
      job = hd(jobs)
      [point] = job.changes.args["points"]
      assert point["lat"] == 32.91
      assert point["lon"] == -97.04
    end

    test "rounds valid_time to nearest hour" do
      contact = create_contact(%{qso_timestamp: ~U[2026-03-28 18:45:00Z], pos2: nil})

      jobs = ContactWeatherEnqueueWorker.build_hrrr_jobs([contact])

      job = hd(jobs)
      assert job.changes.args["valid_time"] == "2026-03-28T19:00:00Z"
    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 18:20:00Z],
          pos1: %{"lat" => 32.90, "lon" => -97.04},
          pos2: nil
        })

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

      # Both round to same hour and same location → 1 job
      assert length(jobs) == 1
    end

    test "deduplicates path points that round to the same HRRR grid cell" do
      # pos1 and pos2 only 0.001° apart — all 3 points round to same HRRR cell
      contact =
        create_contact(%{
          pos1: %{"lat" => 32.901, "lon" => -97.041},
          pos2: %{"lat" => 32.902, "lon" => -97.042}
        })

      jobs = ContactWeatherEnqueueWorker.build_hrrr_jobs([contact])

      # All 3 path points round to {32.90, -97.04} → 1 job
      assert length(jobs) == 1
    end

    test "skips QSOs without pos1" do
      contact = create_contact(%{pos1: nil})

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

    # HRRR archive starts mid-2014; pre-coverage fetches always 404. Skipping
    # here stops the :queued ↔ :unavailable ping-pong in BackfillEnqueueWorker
    # where the reconcile flips pre-2014 rows to :unavailable and the same
    # run immediately flips them back to :queued from a build_hrrr_jobs call
    # that never had a chance of succeeding.
    test "skips pre-2014 contacts (outside HRRR coverage — NARR's job)" do
      contact = create_contact(%{qso_timestamp: ~U[2010-06-15 18:00:00Z]})

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

    test "excludes existing points from batch" do
      contact = create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}})

      # Insert a profile at the pos1 rounded grid point only
      Weather.upsert_hrrr_profile(%{
        valid_time: ~U[2026-03-28 18:00:00Z],
        lat: 32.91,
        lon: -97.04,
        run_time: ~U[2026-03-28 18:00:00Z],
        profile: [%{"pres" => 1000.0, "tmpc" => 25.0}],
        hpbl_m: 1500.0,
        pwat_mm: 25.0,
        surface_temp_c: 25.0,
        surface_dewpoint_c: 18.0,
        surface_pressure_mb: 1013.0
      })

      jobs = ContactWeatherEnqueueWorker.build_hrrr_jobs([contact])

      # 1 batch job, but pos1 excluded from points list
      assert length(jobs) == 1
      points = hd(jobs).changes.args["points"]
      assert length(points) == 2
      lats = Enum.map(points, & &1["lat"])
      refute 32.91 in lats
    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 "skips terrain for QSOs missing pos2" do
      _qso = create_contact(%{pos2: nil})

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

      all_contacts = Repo.all(Contact)
      refute Enum.any?(all_contacts, &(&1.terrain_status == :queued))
    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 "works for QSO without pos2 or grid2 (terrain stays pending)" 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
      # terrain requires pos2, so it can't be queued
      assert updated.terrain_status == :pending
      assert updated.iemre_status == :queued
    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
