defmodule Microwaveprop.Workers.CanadianSoundingFetchWorkerTest do
  use Microwaveprop.DataCase, async: false

  alias Microwaveprop.Repo
  alias Microwaveprop.Weather
  alias Microwaveprop.Weather.Sounding
  alias Microwaveprop.Weather.Station
  alias Microwaveprop.Weather.UwyoSoundingClient
  alias Microwaveprop.Workers.CanadianSoundingFetchWorker

  @fixture File.read!("test/support/fixtures/uwyo_sounding_yyr.html")

  describe "most_recent_sounding_time/1" do
    test "returns 00Z of the same day when called shortly after 02:00Z" do
      # UWYO publishes 00Z soundings ~1h after launch; use 90 min buffer
      assert CanadianSoundingFetchWorker.most_recent_sounding_time(~U[2026-04-13 02:00:00Z]) ==
               ~U[2026-04-13 00:00:00Z]
    end

    test "returns 12Z of the same day after 14:00Z" do
      assert CanadianSoundingFetchWorker.most_recent_sounding_time(~U[2026-04-13 14:00:00Z]) ==
               ~U[2026-04-13 12:00:00Z]
    end

    test "returns prior day 12Z when called between midnight and 01:30Z" do
      # Too early for today's 00Z run to be published
      assert CanadianSoundingFetchWorker.most_recent_sounding_time(~U[2026-04-13 01:00:00Z]) ==
               ~U[2026-04-12 12:00:00Z]
    end

    test "returns 00Z when called between 13:00Z and 13:30Z (before 12Z is ready)" do
      assert CanadianSoundingFetchWorker.most_recent_sounding_time(~U[2026-04-13 13:00:00Z]) ==
               ~U[2026-04-13 00:00:00Z]
    end
  end

  describe "perform/1" do
    setup do
      cwse = insert_sounding_station("CWSE", "Edmonton Stony Plain")
      cyyr = insert_sounding_station("CYYR", "Goose Bay")
      us_sta = insert_sounding_station("KFWD", "Fort Worth")
      %{cwse: cwse, cyyr: cyyr, us_sta: us_sta}
    end

    test "fetches only Canadian sounding stations (station_code starts with 'C')", %{cwse: cwse, us_sta: us_sta} do
      stub_uwyo_with_fixture()

      :ok =
        CanadianSoundingFetchWorker.perform(%Oban.Job{
          args: %{"sounding_time" => "2026-04-13T00:00:00Z"}
        })

      assert soundings_for_station(cwse.id) == 1
      assert soundings_for_station(us_sta.id) == 0
    end

    test "stores a sounding with derived atmospheric parameters", %{cyyr: cyyr} do
      stub_uwyo_with_fixture()

      :ok =
        CanadianSoundingFetchWorker.perform(%Oban.Job{
          args: %{"sounding_time" => "2026-04-13T00:00:00Z"}
        })

      sounding = Repo.get_by!(Sounding, station_id: cyyr.id, observed_at: ~U[2026-04-13 00:00:00Z])

      assert sounding.level_count > 0
      assert is_float(sounding.surface_refractivity)
      assert is_float(sounding.min_refractivity_gradient)
    end

    test "skips stations that already have a sounding for this observed_at", %{cwse: cwse, cyyr: cyyr} do
      # Pre-existing stub sounding on BOTH Canadian stations so UWYO should
      # never be called.
      for sta <- [cwse, cyyr] do
        {:ok, _} =
          Weather.upsert_sounding(sta, %{
            observed_at: ~U[2026-04-13 00:00:00Z],
            profile: [],
            level_count: 0
          })
      end

      Req.Test.stub(UwyoSoundingClient, fn _conn ->
        raise "UwyoSoundingClient was called for a station that already has a sounding"
      end)

      :ok =
        CanadianSoundingFetchWorker.perform(%Oban.Job{
          args: %{"sounding_time" => "2026-04-13T00:00:00Z"}
        })

      assert soundings_for_station(cwse.id) == 1
      assert soundings_for_station(cyyr.id) == 1
    end

    test "uses most_recent_sounding_time when no sounding_time arg is given", %{cwse: cwse} do
      stub_uwyo_with_fixture()

      :ok = CanadianSoundingFetchWorker.perform(%Oban.Job{args: %{}})

      # A sounding was created — use the function's own logic to figure out the observed_at
      expected = CanadianSoundingFetchWorker.most_recent_sounding_time(DateTime.utc_now())

      assert Repo.get_by(Sounding, station_id: cwse.id, observed_at: expected)
    end
  end

  defp insert_sounding_station(code, name) do
    %Station{}
    |> Station.changeset(%{
      station_code: code,
      station_type: "sounding",
      name: name,
      lat: 50.0,
      lon: -100.0
    })
    |> Repo.insert!()
  end

  defp soundings_for_station(station_id) do
    Repo.aggregate(from(s in Sounding, where: s.station_id == ^station_id), :count, :id)
  end

  defp stub_uwyo_with_fixture do
    Req.Test.stub(UwyoSoundingClient, fn conn ->
      Req.Test.text(conn, @fixture)
    end)
  end
end
