defmodule Microwaveprop.Pskr.AggregatorTest do
  use Microwaveprop.DataCase, async: true

  alias Ecto.Adapters.SQL.Sandbox
  alias Microwaveprop.Pskr.Aggregator
  alias Microwaveprop.Pskr.SpotHourly
  alias Microwaveprop.Repo

  @sample %{
    "f" => 144_174_000,
    "md" => "FT8",
    "rp" => -5,
    "t_tx" => 1_662_407_697,
    "sc" => "K5ABC",
    "sl" => "EM12kl",
    "rc" => "K7XYZ",
    "rl" => "DM43st",
    "sa" => 291,
    "ra" => 291,
    "b" => "2m"
  }

  setup do
    pid = start_supervised!({Aggregator, name: :"agg_#{:erlang.unique_integer([:positive])}", flush_ms: 0})
    Sandbox.allow(Repo, self(), pid)
    %{agg: pid}
  end

  test "ingest collapses repeats on the same path-hour", %{agg: agg} do
    payloads = [
      Jason.encode!(@sample),
      Jason.encode!(%{@sample | "rp" => -3}),
      Jason.encode!(%{@sample | "rp" => -12, "md" => "FT4"})
    ]

    Enum.each(payloads, &Aggregator.ingest(agg, &1))

    assert Aggregator.flush(agg) == 1
    [row] = Repo.all(SpotHourly)

    assert row.spot_count == 3
    assert row.max_snr_db == -3
    assert row.min_snr_db == -12
    assert Enum.sort(row.modes) == ["FT4", "FT8"]
    assert row.band == "2m"
    assert row.sender_grid == "EM12KL"
    assert row.receiver_grid == "DM43ST"
    assert row.distance_km > 0
  end

  test "two paths in the same hour produce two rows", %{agg: agg} do
    Aggregator.ingest(agg, Jason.encode!(@sample))
    Aggregator.ingest(agg, Jason.encode!(%{@sample | "sl" => "FN31pr"}))

    assert Aggregator.flush(agg) == 2
    assert Repo.aggregate(SpotHourly, :count) == 2
  end

  test "second flush merges into existing row via upsert", %{agg: agg} do
    Aggregator.ingest(agg, Jason.encode!(@sample))
    assert Aggregator.flush(agg) == 1

    Aggregator.ingest(agg, Jason.encode!(%{@sample | "rp" => 5}))
    assert Aggregator.flush(agg) == 1

    [row] = Repo.all(SpotHourly)
    assert row.spot_count == 2
    assert row.max_snr_db == 5
    assert row.min_snr_db == -5
  end

  test "malformed payloads are dropped silently", %{agg: agg} do
    Aggregator.ingest(agg, "not json")
    Aggregator.ingest(agg, Jason.encode!(%{"oops" => true}))
    Aggregator.ingest(agg, Jason.encode!(@sample))

    assert Aggregator.flush(agg) == 1
  end

  test "pending_count returns the number of unflushed rows", %{agg: agg} do
    assert Aggregator.pending_count(agg) == 0

    Aggregator.ingest(agg, Jason.encode!(@sample))
    assert Aggregator.pending_count(agg) == 1

    Aggregator.ingest(agg, Jason.encode!(%{@sample | "sl" => "FN31pr"}))
    assert Aggregator.pending_count(agg) == 2

    Aggregator.flush(agg)
    assert Aggregator.pending_count(agg) == 0
  end

  test "flush with empty rows returns 0", %{agg: agg} do
    assert Aggregator.flush(agg) == 0
  end
end
