defmodule Microwaveprop.SpaceWeather do
  @moduledoc """
  Context for NOAA SWPC space-weather observations: planetary K-index,
  10.7 cm solar flux, and GOES X-ray flux. Used by the propagation
  scorer for HF MUF prediction (F10.7 → SSN proxy), geomagnetic storm
  effects (Kp), and short-wave fade detection (GOES X-ray flares).
  """

  import Ecto.Query

  alias Microwaveprop.Repo
  alias Microwaveprop.SpaceWeather.GeomagneticObservation
  alias Microwaveprop.SpaceWeather.SolarFluxObservation
  alias Microwaveprop.SpaceWeather.SolarXrayObservation

  # ---------- Kp ----------

  @spec upsert_kp([map()]) :: {:ok, non_neg_integer()}
  def upsert_kp([]), do: {:ok, 0}

  def upsert_kp(rows) when is_list(rows) do
    stamp_upsert(GeomagneticObservation, rows, conflict_target: :valid_time)
  end

  @spec latest_kp() :: GeomagneticObservation.t() | nil
  def latest_kp do
    Repo.one(from o in GeomagneticObservation, order_by: [desc: o.valid_time], limit: 1)
  end

  # ---------- F10.7 ----------

  @spec upsert_solar_flux([map()]) :: {:ok, non_neg_integer()}
  def upsert_solar_flux([]), do: {:ok, 0}

  def upsert_solar_flux(rows) when is_list(rows) do
    stamp_upsert(SolarFluxObservation, rows, conflict_target: :valid_time)
  end

  @spec latest_f107() :: SolarFluxObservation.t() | nil
  def latest_f107 do
    Repo.one(from o in SolarFluxObservation, order_by: [desc: o.valid_time], limit: 1)
  end

  # ---------- GOES X-ray ----------

  @spec upsert_xray([map()]) :: {:ok, non_neg_integer()}
  def upsert_xray([]), do: {:ok, 0}

  def upsert_xray(rows) when is_list(rows) do
    stamp_upsert(SolarXrayObservation, rows, conflict_target: [:valid_time, :energy_band])
  end

  @spec latest_xray() :: SolarXrayObservation.t() | nil
  def latest_xray do
    Repo.one(from o in SolarXrayObservation, order_by: [desc: o.valid_time], limit: 1)
  end

  # ---------- Shared ----------

  defp stamp_upsert(schema, rows, opts) do
    now = DateTime.truncate(DateTime.utc_now(), :second)

    stamped =
      Enum.map(rows, fn row ->
        row
        |> Map.put(:inserted_at, now)
        |> Map.put(:updated_at, now)
      end)

    {count, _} =
      Repo.insert_all(schema, stamped,
        on_conflict: {:replace_all_except, [:id, :inserted_at]},
        conflict_target: Keyword.fetch!(opts, :conflict_target)
      )

    {:ok, count}
  end
end
