defmodule Microwaveprop.Propagation.InversionTest do
  use ExUnit.Case, async: true

  alias Microwaveprop.Propagation.Inversion

  # Helper: build a profile struct-like map from level tuples.
  # Each level is {height_m, temp_k, spfh, pressure_pa, u_ms, v_ms, tke}
  defp profile(levels) do
    %{
      level_count: length(levels),
      heights_m: Enum.map(levels, &elem(&1, 0)),
      temp_k: Enum.map(levels, &elem(&1, 1)),
      spfh: Enum.map(levels, &elem(&1, 2)),
      pressure_pa: Enum.map(levels, &elem(&1, 3)),
      u_wind_ms: Enum.map(levels, &elem(&1, 4)),
      v_wind_ms: Enum.map(levels, &elem(&1, 5)),
      tke_m2s2: Enum.map(levels, &elem(&1, 6))
    }
  end

  describe "find_inversion_top/1" do
    test "returns :none for a monotonically cooling profile (no inversion)" do
      # Standard atmosphere: T decreases with height, no inversion
      p =
        profile([
          {10.0, 295.0, 0.010, 101_000.0, 2.0, 1.0, 0.5},
          {100.0, 294.0, 0.009, 100_000.0, 2.5, 1.0, 0.4},
          {300.0, 292.0, 0.007, 97_000.0, 3.0, 1.5, 0.3},
          {500.0, 289.0, 0.005, 95_000.0, 4.0, 2.0, 0.2},
          {1000.0, 283.0, 0.003, 90_000.0, 5.0, 2.5, 0.1}
        ])

      assert Inversion.find_inversion_top(p) == :none
    end

    test "detects a surface-based temperature inversion" do
      # Cool at surface, warm layer at 100-300m (classic radiation inversion)
      p =
        profile([
          {10.0, 288.0, 0.008, 101_000.0, 1.0, 0.5, 0.1},
          {50.0, 289.0, 0.008, 100_500.0, 1.0, 0.5, 0.1},
          {100.0, 291.0, 0.007, 100_000.0, 1.5, 0.5, 0.1},
          {200.0, 293.0, 0.006, 99_000.0, 2.0, 1.0, 0.2},
          {300.0, 292.0, 0.006, 98_000.0, 3.0, 1.5, 0.3},
          {500.0, 289.0, 0.005, 95_000.0, 4.0, 2.0, 0.5}
        ])

      {:ok, top} = Inversion.find_inversion_top(p)
      # The inversion top is where temperature stops increasing and starts decreasing
      # Between 200m (293K) and 300m (292K) — so the top is at the 200m level
      assert top.height_m == 200.0
      assert top.level_idx == 3
      assert top.strength_k > 0
    end

    test "detects an elevated inversion" do
      # Normal lapse below 500m, inversion between 500-1000m
      p =
        profile([
          {10.0, 295.0, 0.010, 101_000.0, 2.0, 1.0, 0.5},
          {100.0, 294.0, 0.009, 100_000.0, 2.5, 1.0, 0.4},
          {300.0, 292.0, 0.007, 97_000.0, 3.0, 1.5, 0.3},
          {500.0, 290.0, 0.005, 95_000.0, 4.0, 2.0, 0.2},
          {700.0, 292.0, 0.004, 93_000.0, 5.0, 3.0, 0.1},
          {1000.0, 294.0, 0.003, 90_000.0, 6.0, 3.5, 0.1},
          {1500.0, 291.0, 0.002, 85_000.0, 8.0, 4.0, 0.2}
        ])

      {:ok, top} = Inversion.find_inversion_top(p)
      # T increases from 500m to 1000m then drops — top at 1000m
      assert top.height_m == 1000.0
      assert top.level_idx == 5
    end

    test "returns :none for a profile with too few levels" do
      p = profile([{10.0, 295.0, 0.010, 101_000.0, 2.0, 1.0, 0.5}])
      assert Inversion.find_inversion_top(p) == :none
    end
  end

  describe "bulk_richardson/3" do
    test "returns large Ri for a strong inversion with weak shear (laminar)" do
      # Strong warming (5 K) with minimal wind change → Ri >> 1
      p =
        profile([
          {10.0, 288.0, 0.008, 101_000.0, 2.0, 1.0, 0.1},
          {200.0, 293.0, 0.006, 99_000.0, 2.1, 1.1, 0.1}
        ])

      ri = Inversion.bulk_richardson(p, 0, 1)
      assert ri > 1.0
    end

    test "returns small Ri for an inversion with strong shear (turbulent)" do
      # Mild warming (1 K) with strong wind change (20 m/s) → Ri < 0.25
      p =
        profile([
          {10.0, 290.0, 0.008, 101_000.0, 2.0, 1.0, 0.1},
          {200.0, 291.0, 0.006, 99_000.0, 22.0, 1.0, 0.5}
        ])

      ri = Inversion.bulk_richardson(p, 0, 1)
      assert ri < 0.25
    end

    test "clamps at 100.0 when shear is near zero" do
      p =
        profile([
          {10.0, 290.0, 0.008, 101_000.0, 2.0, 1.0, 0.1},
          {200.0, 295.0, 0.006, 99_000.0, 2.0, 1.0, 0.1}
        ])

      ri = Inversion.bulk_richardson(p, 0, 1)
      assert ri == 100.0
    end
  end

  describe "shear_magnitude/3" do
    test "computes the vector magnitude of wind difference" do
      p =
        profile([
          {10.0, 290.0, 0.008, 101_000.0, 0.0, 0.0, 0.1},
          {200.0, 290.0, 0.008, 101_000.0, 3.0, 4.0, 0.1}
        ])

      shear = Inversion.shear_magnitude(p, 0, 1)
      assert_in_delta shear, 5.0, 0.001
    end
  end

  describe "potential_temperature/2" do
    test "computes theta correctly at standard sea-level conditions" do
      # At P = 100000 Pa (exactly), theta should equal T
      theta = Inversion.potential_temperature(300.0, 100_000.0)
      assert_in_delta theta, 300.0, 0.01
    end

    test "theta > T when pressure is above 1000 hPa" do
      # At P = 101325 Pa, theta should be slightly less than T
      theta = Inversion.potential_temperature(300.0, 101_325.0)
      assert theta < 300.0
    end
  end
end
