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

  alias Microwaveprop.SpaceWeather
  alias Microwaveprop.SpaceWeather.GeomagneticObservation
  alias Microwaveprop.SpaceWeather.SolarFluxObservation
  alias Microwaveprop.SpaceWeather.SolarXrayObservation

  describe "upsert_kp/1" do
    test "inserts new rows and returns the count" do
      rows = [
        %{valid_time: ~U[2026-04-15 18:00:00Z], kp_index: 2, estimated_kp: 1.67},
        %{valid_time: ~U[2026-04-15 18:01:00Z], kp_index: 2, estimated_kp: 2.0}
      ]

      assert {:ok, 2} = SpaceWeather.upsert_kp(rows)
      assert Repo.aggregate(GeomagneticObservation, :count, :id) == 2
    end

    test "re-upsert replaces by valid_time (no dupes)" do
      row = %{valid_time: ~U[2026-04-15 18:00:00Z], kp_index: 2, estimated_kp: 1.67}
      {:ok, 1} = SpaceWeather.upsert_kp([row])

      updated = %{row | kp_index: 5, estimated_kp: 5.3}
      assert {:ok, 1} = SpaceWeather.upsert_kp([updated])

      assert [stored] = Repo.all(GeomagneticObservation)
      assert stored.kp_index == 5
      assert stored.estimated_kp == 5.3
    end

    test "empty list is a no-op returning {:ok, 0}" do
      assert {:ok, 0} = SpaceWeather.upsert_kp([])
    end
  end

  describe "upsert_solar_flux/1" do
    test "inserts rows and replaces by valid_time" do
      rows = [
        %{
          valid_time: ~U[2026-04-15 17:00:00Z],
          frequency_mhz: 2800,
          flux_sfu: 112.0,
          reporting_schedule: "Morning",
          ninety_day_mean: nil
        }
      ]

      assert {:ok, 1} = SpaceWeather.upsert_solar_flux(rows)

      updated = rows |> List.first() |> Map.put(:flux_sfu, 120.0)
      assert {:ok, 1} = SpaceWeather.upsert_solar_flux([updated])

      assert [stored] = Repo.all(SolarFluxObservation)
      assert stored.flux_sfu == 120.0
    end

    test "empty list is a no-op returning {:ok, 0}" do
      assert {:ok, 0} = SpaceWeather.upsert_solar_flux([])
      assert Repo.aggregate(SolarFluxObservation, :count, :id) == 0
    end
  end

  describe "upsert_xray/1" do
    test "inserts rows and replaces by (valid_time, energy_band)" do
      rows = [
        %{
          valid_time: ~U[2026-04-14 19:49:00Z],
          satellite: 18,
          flux_wm2: 3.5e-7,
          energy_band: "0.1-0.8nm",
          electron_contaminated: false
        }
      ]

      assert {:ok, 1} = SpaceWeather.upsert_xray(rows)
      assert Repo.aggregate(SolarXrayObservation, :count, :id) == 1

      # Re-upserting updates existing row
      updated = rows |> List.first() |> Map.put(:flux_wm2, 4.0e-7)
      assert {:ok, 1} = SpaceWeather.upsert_xray([updated])
      assert Repo.aggregate(SolarXrayObservation, :count, :id) == 1

      [stored] = Repo.all(SolarXrayObservation)
      assert stored.flux_wm2 == 4.0e-7
    end

    test "rows with same valid_time but different energy_band coexist" do
      rows = [
        %{
          valid_time: ~U[2026-04-14 19:49:00Z],
          satellite: 18,
          flux_wm2: 3.5e-7,
          energy_band: "0.1-0.8nm"
        },
        %{
          valid_time: ~U[2026-04-14 19:49:00Z],
          satellite: 18,
          flux_wm2: 1.2e-8,
          energy_band: "0.05-0.4nm"
        }
      ]

      assert {:ok, 2} = SpaceWeather.upsert_xray(rows)
      assert Repo.aggregate(SolarXrayObservation, :count, :id) == 2
    end

    test "empty list is a no-op returning {:ok, 0}" do
      assert {:ok, 0} = SpaceWeather.upsert_xray([])
      assert Repo.aggregate(SolarXrayObservation, :count, :id) == 0
    end
  end

  describe "latest_kp/0" do
    test "returns the most recent geomagnetic observation, or nil" do
      assert SpaceWeather.latest_kp() == nil

      {:ok, _} =
        SpaceWeather.upsert_kp([
          %{valid_time: ~U[2026-04-15 17:00:00Z], kp_index: 2, estimated_kp: 2.0},
          %{valid_time: ~U[2026-04-15 18:00:00Z], kp_index: 3, estimated_kp: 3.0}
        ])

      row = SpaceWeather.latest_kp()
      assert row.valid_time == ~U[2026-04-15 18:00:00Z]
      assert row.kp_index == 3
    end
  end

  describe "latest_f107/0 and latest_xray/0" do
    test "return the most recent rows, or nil" do
      assert SpaceWeather.latest_f107() == nil
      assert SpaceWeather.latest_xray() == nil

      {:ok, _} =
        SpaceWeather.upsert_solar_flux([
          %{valid_time: ~U[2026-04-15 17:00:00Z], frequency_mhz: 2800, flux_sfu: 112.0}
        ])

      {:ok, _} =
        SpaceWeather.upsert_xray([
          %{
            valid_time: ~U[2026-04-15 17:00:00Z],
            satellite: 18,
            flux_wm2: 1.0e-7,
            energy_band: "0.1-0.8nm"
          }
        ])

      assert %SolarFluxObservation{flux_sfu: 112.0} = SpaceWeather.latest_f107()
      assert %SolarXrayObservation{flux_wm2: 1.0e-7} = SpaceWeather.latest_xray()
    end

    test "latest_f107/0 picks newest across multiple observations" do
      {:ok, _} =
        SpaceWeather.upsert_solar_flux([
          %{valid_time: ~U[2026-04-14 20:00:00Z], frequency_mhz: 2800, flux_sfu: 100.0},
          %{valid_time: ~U[2026-04-15 20:00:00Z], frequency_mhz: 2800, flux_sfu: 115.0},
          %{valid_time: ~U[2026-04-13 20:00:00Z], frequency_mhz: 2800, flux_sfu: 90.0}
        ])

      row = SpaceWeather.latest_f107()
      assert row.valid_time == ~U[2026-04-15 20:00:00Z]
      assert row.flux_sfu == 115.0
    end

    test "latest_xray/0 picks newest across multiple energy bands" do
      {:ok, _} =
        SpaceWeather.upsert_xray([
          %{
            valid_time: ~U[2026-04-15 17:00:00Z],
            satellite: 18,
            flux_wm2: 1.0e-7,
            energy_band: "0.1-0.8nm"
          },
          %{
            valid_time: ~U[2026-04-15 18:00:00Z],
            satellite: 18,
            flux_wm2: 2.0e-7,
            energy_band: "0.05-0.4nm"
          },
          %{
            valid_time: ~U[2026-04-15 17:30:00Z],
            satellite: 18,
            flux_wm2: 3.0e-7,
            energy_band: "0.1-0.8nm"
          }
        ])

      row = SpaceWeather.latest_xray()
      assert row.valid_time == ~U[2026-04-15 18:00:00Z]
      assert row.flux_wm2 == 2.0e-7
    end
  end
end
