defmodule Microwaveprop.IonosphereTest do
  use Microwaveprop.DataCase, async: true

  alias Microwaveprop.Ionosphere
  alias Microwaveprop.Ionosphere.Observation

  @valid_attrs %{
    station_code: "MHJ45",
    valid_time: ~U[2026-04-15 18:00:00Z],
    confidence_score: 80,
    fo_f2_mhz: 7.113,
    fo_e_mhz: 3.10,
    fo_es_mhz: 3.08,
    hm_f2_km: 242.0,
    mufd_mhz: 23.00
  }

  describe "upsert_observations/2" do
    test "inserts new observations and returns the inserted count" do
      assert {:ok, 2} =
               Ionosphere.upsert_observations("MHJ45", [
                 Map.delete(@valid_attrs, :station_code),
                 @valid_attrs
                 |> Map.delete(:station_code)
                 |> Map.put(:valid_time, ~U[2026-04-15 18:07:30Z])
               ])

      assert Repo.aggregate(Observation, :count, :id) == 2
    end

    test "re-upserting the same (station, valid_time) replaces fields and does not duplicate" do
      {:ok, 1} = Ionosphere.upsert_observations("MHJ45", [Map.delete(@valid_attrs, :station_code)])

      updated =
        @valid_attrs
        |> Map.delete(:station_code)
        |> Map.put(:fo_es_mhz, 9.5)

      assert {:ok, 1} = Ionosphere.upsert_observations("MHJ45", [updated])
      assert Repo.aggregate(Observation, :count, :id) == 1

      [row] = Repo.all(Observation)
      assert row.fo_es_mhz == 9.5
    end

    test "stamps station_code on each observation" do
      {:ok, 1} =
        Ionosphere.upsert_observations("AL945", [
          Map.delete(@valid_attrs, :station_code)
        ])

      [row] = Repo.all(Observation)
      assert row.station_code == "AL945"
    end

    test "returns 0 and does nothing for an empty list" do
      assert {:ok, 0} = Ionosphere.upsert_observations("MHJ45", [])
      assert Repo.aggregate(Observation, :count, :id) == 0
    end
  end

  describe "latest_observation/1" do
    test "returns the most recent observation for a station" do
      older = Map.delete(@valid_attrs, :station_code)

      newer =
        @valid_attrs
        |> Map.delete(:station_code)
        |> Map.put(:valid_time, ~U[2026-04-15 19:00:00Z])
        |> Map.put(:fo_es_mhz, 10.0)

      {:ok, _} = Ionosphere.upsert_observations("MHJ45", [older, newer])

      row = Ionosphere.latest_observation("MHJ45")
      assert row.valid_time == ~U[2026-04-15 19:00:00Z]
      assert row.fo_es_mhz == 10.0
    end

    test "returns nil when no observations exist for the station" do
      assert Ionosphere.latest_observation("MHJ45") == nil
    end

    test "filters by station_code (another station's newer row does not leak through)" do
      mhj_old =
        @valid_attrs
        |> Map.delete(:station_code)
        |> Map.put(:valid_time, ~U[2026-04-15 18:00:00Z])
        |> Map.put(:fo_es_mhz, 4.0)

      al_newer =
        @valid_attrs
        |> Map.delete(:station_code)
        |> Map.put(:valid_time, ~U[2026-04-15 23:00:00Z])
        |> Map.put(:fo_es_mhz, 11.0)

      {:ok, _} = Ionosphere.upsert_observations("MHJ45", [mhj_old])
      {:ok, _} = Ionosphere.upsert_observations("AL945", [al_newer])

      row = Ionosphere.latest_observation("MHJ45")
      assert row.station_code == "MHJ45"
      assert row.valid_time == ~U[2026-04-15 18:00:00Z]
      assert row.fo_es_mhz == 4.0
    end
  end

  describe "nearest_foes/3" do
    setup do
      recent = DateTime.truncate(DateTime.utc_now(), :second)

      {:ok, _} =
        Ionosphere.upsert_observations("MHJ45", [
          %{valid_time: recent, fo_es_mhz: 7.5, fo_f2_mhz: 8.0, mufd_mhz: 25.0}
        ])

      {:ok, _} =
        Ionosphere.upsert_observations("AL945", [
          %{valid_time: recent, fo_es_mhz: 12.3, fo_f2_mhz: 7.5, mufd_mhz: 24.0}
        ])

      :ok
    end

    test "returns the nearest station's latest foEs for a grid point in New England" do
      # 42°N, -72°W is essentially on Millstone Hill (42.6, -71.5).
      assert {:ok, obs} = Ionosphere.nearest_foes(42.0, -72.0)
      assert obs.station_code == "MHJ45"
      assert obs.fo_es_mhz == 7.5
    end

    test "returns the nearest station's latest foEs for a grid point in Michigan" do
      # 45°N, -84°W is next to Alpena (45.1, -83.6).
      assert {:ok, obs} = Ionosphere.nearest_foes(45.0, -84.0)
      assert obs.station_code == "AL945"
      assert obs.fo_es_mhz == 12.3
    end

    test "returns {:error, :stale} when the nearest station's latest value is older than max_age" do
      stale = ~U[2020-01-01 00:00:00Z]

      Microwaveprop.Repo.update_all(
        Observation,
        set: [valid_time: stale]
      )

      assert {:error, :stale} = Ionosphere.nearest_foes(42.0, -72.0, max_age_seconds: 3600)
    end

    test "returns {:error, :no_data} when no observations have been ingested" do
      Microwaveprop.Repo.delete_all(Observation)
      assert {:error, :no_data} = Ionosphere.nearest_foes(42.0, -72.0)
    end
  end
end
