defmodule Mix.Tasks.NexradBackfill do
  @shortdoc "Enqueue NexradWorker jobs for the top-N hours by contact count"
  @moduledoc """
  Backfills the `nexrad_observations` table by enqueueing one
  `NexradWorker` job per distinct `(year, month, day, hour, minute=0)`
  where we have contacts, prioritized by contact count so the most
  data-dense hours land first.

  Each n0q frame is 2-4 MB, so a 200-hour backfill is ~400-800 MB.

      mix nexrad_backfill --limit 200

  Jobs are deduplicated by Oban's unique constraint on
  `{year, month, day, hour, minute}` so running the task twice is safe.
  """
  use Mix.Task

  import Ecto.Query

  alias Microwaveprop.Radio.Contact
  alias Microwaveprop.Repo
  alias Microwaveprop.Workers.NexradWorker

  @impl Mix.Task
  def run(argv) do
    Mix.Task.run("app.start")
    Oban.pause_all_queues(Oban)

    {opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
    limit = Keyword.get(opts, :limit, 200)

    hours = top_hours_by_contact_count(limit)
    Mix.shell().info("Enqueueing #{length(hours)} NexradWorker jobs")

    Enum.each(hours, fn %{year: y, month: m, day: d, hour: h, contacts: n} ->
      args = %{"year" => y, "month" => m, "day" => d, "hour" => h, "minute" => 0}

      case Oban.insert(NexradWorker.new(args)) do
        {:ok, _job} ->
          Mix.shell().info("  #{y}-#{pad(m)}-#{pad(d)} #{pad(h)}Z  (#{n} contacts)")

        {:error, reason} ->
          Mix.shell().error("  #{y}-#{pad(m)}-#{pad(d)} #{pad(h)}Z failed: #{inspect(reason)}")
      end
    end)
  end

  defp pad(n), do: n |> Integer.to_string() |> String.pad_leading(2, "0")

  defp top_hours_by_contact_count(limit) do
    Repo.all(
      from(c in Contact,
        where: not is_nil(c.pos1),
        select: %{
          year: fragment("EXTRACT(YEAR FROM ?)::int", c.qso_timestamp),
          month: fragment("EXTRACT(MONTH FROM ?)::int", c.qso_timestamp),
          day: fragment("EXTRACT(DAY FROM ?)::int", c.qso_timestamp),
          hour: fragment("EXTRACT(HOUR FROM ?)::int", c.qso_timestamp),
          contacts: count(c.id)
        },
        group_by: [
          fragment("EXTRACT(YEAR FROM ?)::int", c.qso_timestamp),
          fragment("EXTRACT(MONTH FROM ?)::int", c.qso_timestamp),
          fragment("EXTRACT(DAY FROM ?)::int", c.qso_timestamp),
          fragment("EXTRACT(HOUR FROM ?)::int", c.qso_timestamp)
        ],
        order_by: [desc: count(c.id)],
        limit: ^limit
      )
    )
  end
end
