defmodule Microwaveprop.Pskr.SpotHourly do
  @moduledoc """
  Hourly aggregate of PSK Reporter spots between two Maidenhead
  grid locators on a single band. Locator precision is preserved
  from the source spot (4/6/8/10 chars), so two spotters in
  different subsquares of the same field generate distinct rows
  even though they share an `EM12` prefix.

  A single QSO often produces 50+ reception reports (one per
  monitoring station that decoded it). Storing each report verbatim
  would dwarf the rest of the database — instead `Pskr.Aggregator`
  collapses every spot inside `(hour_utc, band, sender_grid,
  receiver_grid)` into one row, retaining `spot_count`, SNR
  envelope, and the set of modes observed. Downstream weather
  correlation cares about *did propagation occur in this cell during
  this hour*, not about each individual reporter — so the lossy
  aggregation is the desired shape, not just a space optimization.

  `midpoint_lat/lon` is the great-circle midpoint of the path,
  pre-computed at insert time so HRRR / IEMRE grid lookups are a
  cheap `WHERE midpoint_lat BETWEEN …` rather than a per-row
  recomputation.
  """
  use Ecto.Schema

  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  schema "pskr_spots_hourly" do
    field :hour_utc, :utc_datetime
    field :band, :string
    field :sender_grid, :string
    field :receiver_grid, :string
    field :sender_country, :integer
    field :receiver_country, :integer
    field :sender_lat, :float
    field :sender_lon, :float
    field :receiver_lat, :float
    field :receiver_lon, :float
    field :midpoint_lat, :float
    field :midpoint_lon, :float
    field :distance_km, :float
    field :spot_count, :integer, default: 0
    field :max_snr_db, :integer
    field :min_snr_db, :integer
    field :modes, {:array, :string}, default: []
    field :first_spot_at, :utc_datetime
    field :last_spot_at, :utc_datetime

    timestamps(type: :utc_datetime)
  end

  @type t :: %__MODULE__{}

  @cast_fields ~w(hour_utc band sender_grid receiver_grid sender_country receiver_country
                  sender_lat sender_lon receiver_lat receiver_lon midpoint_lat midpoint_lon
                  distance_km spot_count max_snr_db min_snr_db modes first_spot_at
                  last_spot_at)a

  @required_fields ~w(hour_utc band sender_grid receiver_grid spot_count)a

  @spec changeset(t(), map()) :: Ecto.Changeset.t()
  def changeset(record, attrs) do
    record
    |> cast(attrs, @cast_fields)
    |> validate_required(@required_fields)
    |> unique_constraint([:hour_utc, :band, :sender_grid, :receiver_grid])
  end
end
