defmodule Microwaveprop.Workers.HrdpsGridWorker do
  @moduledoc """
  Cycle seed worker for the HRDPS Canadian propagation chain. Mirrors
  `Microwaveprop.Workers.PropagationGridWorker` but seeds `grid_tasks`
  rows with `source: "hrdps"` so the Rust worker routes them through
  the HRDPS fetch path.

  ## Cadence

  HRDPS runs 4×/day at 00/06/12/18Z and publishes f000 ~3-4h after each
  cycle. This worker is intended to fire at HH+5 of each cycle hour
  (05:35Z, 11:35Z, 17:35Z, 23:35Z) — see plan stage 6 for the cron entry.

  ## Activation

  Active. The Rust prop-grid-rs worker handles `source: "hrdps"` rows via
  `pipeline::run_chain_step_hrdps`, fetching from MSC Datamart, writing
  scores to `<base>/<band>/<iso>.hrdps.prop` (sibling of the HRRR
  `.prop` file for the same cycle). Cron entry lives in `runtime.exs`.

  ## Scope

  Seeds exactly one forecast row per fire — the one whose valid_time
  matches the current hour. The Canadian propagation/weather UI only
  surfaces the present moment, so producing 18 forecast hours per cycle
  was wasted wgrib2 work (~20 min/chain step at production point
  density). If forecast hours come back into scope later, swap the
  enqueuer call back to `GridTaskEnqueuer.seed_with_analysis/2`.
  """

  use Oban.Worker,
    queue: :hrdps,
    priority: 0,
    max_attempts: 5,
    # Deduplicate seed jobs in a 6-hour window matching the cycle cadence.
    unique: [period: 6 * 3600, states: [:available, :scheduled, :executing, :retryable]]

  alias Microwaveprop.Instrument
  alias Microwaveprop.Propagation.GridTaskEnqueuer
  alias Microwaveprop.Weather.HrdpsClient

  require Logger

  @impl Oban.Worker
  def perform(%Oban.Job{args: args}) when args == %{} do
    Instrument.span([:hrdps, :grid_worker, :perform], %{}, fn ->
      seed_chain()
    end)
  end

  defp seed_chain do
    # HRDPS shares grid_tasks with HRRR; the same reclaim sweep covers
    # both because reclaim_stale_running/0 doesn't filter on source.
    _ = GridTaskEnqueuer.reclaim_stale_running()

    run_time = pick_run_time(DateTime.utc_now())

    Logger.info("HrdpsGrid: seeding current-hour task run_time=#{run_time} source=hrdps")

    case GridTaskEnqueuer.seed_current_hour(run_time, source: "hrdps") do
      {:ok, count} ->
        :telemetry.execute(
          [:microwaveprop, :hrdps, :grid_worker, :seeded],
          %{queued_tasks: count},
          %{run_time: run_time}
        )

        :ok

      {:error, reason} ->
        :telemetry.execute(
          [:microwaveprop, :hrdps, :grid_worker, :exception],
          %{},
          %{run_time: run_time, reason: inspect(reason)}
        )

        {:error, reason}
    end
  end

  @doc """
  Pick the freshest published HRDPS cycle. Probes `now-4h` (just past
  typical publish latency) first; falls back to `now-10h` (one cycle
  earlier) if the fresher cycle isn't yet on the datamart.

  Public so tests can exercise selection logic without going through Oban.
  """
  @spec pick_run_time(DateTime.t()) :: DateTime.t()
  def pick_run_time(%DateTime{} = now) do
    fresh = now |> DateTime.add(-4, :hour) |> HrdpsClient.nearest_hrdps_cycle() |> DateTime.truncate(:second)

    if HrdpsClient.cycle_available?(fresh) do
      fresh
    else
      now |> DateTime.add(-10, :hour) |> HrdpsClient.nearest_hrdps_cycle() |> DateTime.truncate(:second)
    end
  end
end
