defmodule Microwaveprop.Workers.CommonVolumeRadarWorkerTest do
  use Microwaveprop.DataCase, async: false
  use Oban.Testing, repo: Microwaveprop.Repo

  alias Microwaveprop.Radio.Contact
  alias Microwaveprop.Radio.ContactCommonVolumeRadar
  alias Microwaveprop.Repo
  alias Microwaveprop.Workers.CommonVolumeRadarWorker

  setup do
    Req.Test.set_req_test_from_context(%{async: false})
    :ok
  end

  defp insert_contact(attrs \\ %{}) do
    default = %{
      station1: "W5XD",
      station2: "K5TR",
      qso_timestamp: ~U[2024-09-15 18:30:00Z],
      mode: "CW",
      band: Decimal.new("10000"),
      grid1: "EM12",
      grid2: "EM00",
      pos1: %{"lat" => 32.9, "lon" => -97.0},
      pos2: %{"lat" => 33.1, "lon" => -96.8},
      distance_km: Decimal.new("30")
    }

    {:ok, contact} =
      %Contact{}
      |> Contact.changeset(Map.merge(default, attrs))
      |> Repo.insert()

    contact
  end

  describe "perform/1" do
    test "marks radar_status :unavailable and does not insert a row when n0q frame is 404" do
      contact = insert_contact()

      Req.Test.stub(Microwaveprop.Weather.NexradClient, fn conn ->
        Plug.Conn.send_resp(conn, 404, "not found")
      end)

      assert :ok =
               perform_job(CommonVolumeRadarWorker, %{"contact_id" => contact.id})

      refute Repo.get_by(ContactCommonVolumeRadar, contact_id: contact.id)
      assert %Contact{radar_status: :unavailable} = Repo.get!(Contact, contact.id)
    end

    test "skips contacts without both positions and marks unavailable" do
      contact = insert_contact(%{pos1: nil})

      assert :ok =
               perform_job(CommonVolumeRadarWorker, %{"contact_id" => contact.id})

      refute Repo.get_by(ContactCommonVolumeRadar, contact_id: contact.id)
      assert %Contact{radar_status: :unavailable} = Repo.get!(Contact, contact.id)
    end

    test "skips contacts beyond 2R so we don't waste a fetch" do
      # 2R = 800 km. Put the stations 2000 km apart.
      contact =
        insert_contact(%{
          pos1: %{"lat" => 33.0, "lon" => -97.0},
          pos2: %{"lat" => 33.0, "lon" => -75.0}
        })

      assert :ok =
               perform_job(CommonVolumeRadarWorker, %{"contact_id" => contact.id})

      refute Repo.get_by(ContactCommonVolumeRadar, contact_id: contact.id)
      assert %Contact{radar_status: :unavailable} = Repo.get!(Contact, contact.id)
    end
  end

  describe "aggregate_stats/4 (pure)" do
    test "returns zeroed stats when no pixels fall inside the common volume" do
      # A 1-pixel-wide synthetic buffer far from the stations — shouldn't match.
      # Simplest path: check stations 2000 km apart -> :empty bbox -> zero stats.
      pos1 = {33.0, -97.0}
      pos2 = {33.0, -75.0}
      stats = CommonVolumeRadarWorker.aggregate_stats(<<0, 0, 0>>, 3, pos1, pos2, step: 1)

      assert stats.pixel_count == 0
      assert stats.rain_pixel_count == 0
      assert stats.heavy_rain_pixel_count == 0
      assert stats.max_dbz == nil
    end

    test "counts rain, heavy rain, and core pixels from a synthetic CV patch" do
      # Drop two stations close together near the top-left of the image so
      # a hand-built tiny buffer falls inside the common volume. We reuse
      # NexradClient's lat/lon↔pixel mapping: (50 N, -126 W) is pixel (0, 0).
      pos1 = {49.99, -125.99}
      pos2 = {49.98, -125.98}

      # 5x5 buffer, pixel values chosen so pixel_to_dbz/1 gives a known
      # mix of 0 dBZ (no echo), ~30 dBZ (rain), ~40 dBZ (heavy), and
      # ~50 dBZ (core). Pixel value 1 -> -30 dBZ, value 255 -> +95 dBZ.
      # We just need a couple of non-zero pixels in the right spots.
      # Value 123 ≈ 30 dBZ, 143 ≈ 40 dBZ, 163 ≈ 50 dBZ.
      pixels =
        <<0, 123, 0, 0, 0, 0, 143, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>

      stats = CommonVolumeRadarWorker.aggregate_stats(pixels, 5, pos1, pos2, step: 1)

      assert stats.pixel_count >= 3
      assert stats.rain_pixel_count >= 3
      assert stats.heavy_rain_pixel_count >= 2
      assert stats.core_pixel_count >= 1
      assert stats.max_dbz >= 49.0
    end
  end
end
