defmodule Microwaveprop.Weather.MrmsCache do
  @moduledoc """
  Node-local ETS cache of the latest MRMS PrecipRate grid, regridded onto
  the 0.125° propagation grid. Mirrors the pattern used by
  `Microwaveprop.Propagation.ScoreCache` and
  `Microwaveprop.Weather.GridCache` — a single GenServer owns the table,
  callers read directly from ETS, and `broadcast_put/2` fans updates out
  to peer nodes via PubSub.

  Only one node runs `Microwaveprop.Workers.MrmsFetchWorker` per cron
  tick (Oban Pro Smart engine picks a leader), so the other nodes get
  the grid via the `"mrms:cache"` topic and stay in sync without each
  one paying the 1 MB fetch + wgrib2 regrid cost.
  """
  use GenServer

  alias Phoenix.PubSub

  @table :mrms_cache
  @topic "mrms:cache"
  @pubsub Microwaveprop.PubSub

  @type rain_grid :: %{{float(), float()} => float()}

  @spec start_link(keyword()) :: GenServer.on_start()
  def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)

  @doc "Return the cached `{valid_time, rain_grid}` or `:miss`."
  @spec fetch() :: {:ok, DateTime.t(), rain_grid()} | :miss
  def fetch do
    case :ets.lookup(@table, :current) do
      [{_, valid_time, grid}] -> {:ok, valid_time, grid}
      [] -> :miss
    end
  end

  @doc "Current MRMS valid_time if anything is cached."
  @spec valid_time() :: DateTime.t() | nil
  def valid_time do
    case fetch() do
      {:ok, vt, _} -> vt
      :miss -> nil
    end
  end

  @spec put(DateTime.t(), rain_grid()) :: :ok
  def put(valid_time, grid) do
    :ets.insert(@table, {:current, valid_time, grid})
    :ok
  end

  @doc "Insert locally and broadcast to peer nodes."
  @spec broadcast_put(DateTime.t(), rain_grid()) :: :ok
  def broadcast_put(valid_time, grid) do
    put(valid_time, grid)
    PubSub.broadcast(@pubsub, @topic, {:mrms_cache_refresh, valid_time, grid})
    :ok
  end

  @spec clear() :: :ok
  def clear do
    :ets.delete_all_objects(@table)
    :ok
  end

  @impl true
  def init(_opts) do
    :ets.new(@table, [:set, :named_table, :public, read_concurrency: true])
    PubSub.subscribe(@pubsub, @topic)
    {:ok, %{}}
  end

  @impl true
  def handle_info({:mrms_cache_refresh, valid_time, grid}, state) do
    put(valid_time, grid)
    {:noreply, state}
  end

  def handle_info(_msg, state), do: {:noreply, state}
end
