defmodule Microwaveprop.Weather.HrrrNativeProfileTest do
  use Microwaveprop.DataCase, async: true

  alias Microwaveprop.Weather.HrrrNativeProfile

  @valid_attrs %{
    valid_time: ~U[2026-03-28 18:00:00Z],
    run_time: ~U[2026-03-28 18:00:00Z],
    lat: 32.9,
    lon: -97.0,
    level_count: 3,
    heights_m: [10.0, 100.0, 500.0],
    temp_k: [295.0, 292.0, 285.0],
    spfh: [0.010, 0.008, 0.004],
    pressure_pa: [101_000.0, 99_800.0, 95_000.0],
    u_wind_ms: [2.0, 3.0, 4.0],
    v_wind_ms: [1.0, 1.5, 2.0],
    tke_m2s2: [0.5, 0.3, 0.1],
    surface_temp_k: 295.0,
    surface_spfh: 0.010,
    surface_pressure_pa: 101_325.0
  }

  describe "changeset/2" do
    test "accepts a full native profile" do
      changeset = HrrrNativeProfile.changeset(%HrrrNativeProfile{}, @valid_attrs)
      assert changeset.valid?

      assert {:ok, profile} = Repo.insert(changeset)
      assert profile.level_count == 3
      assert profile.heights_m == [10.0, 100.0, 500.0]
    end

    test "requires valid_time, lat, lon" do
      changeset = HrrrNativeProfile.changeset(%HrrrNativeProfile{}, %{})
      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).valid_time
      assert "can't be blank" in errors_on(changeset).lat
      assert "can't be blank" in errors_on(changeset).lon
    end

    test "rejects mismatched level array lengths" do
      attrs = Map.put(@valid_attrs, :temp_k, [295.0, 292.0])
      changeset = HrrrNativeProfile.changeset(%HrrrNativeProfile{}, attrs)
      refute changeset.valid?
      assert errors_on(changeset)[:temp_k]
    end

    test "enforces (lat, lon, valid_time) uniqueness" do
      {:ok, _} = %HrrrNativeProfile{} |> HrrrNativeProfile.changeset(@valid_attrs) |> Repo.insert()

      {:error, changeset} =
        %HrrrNativeProfile{} |> HrrrNativeProfile.changeset(@valid_attrs) |> Repo.insert()

      refute changeset.valid?
      assert "has already been taken" in errors_on(changeset).lat
    end
  end

  describe "to_skew_t_profile/1" do
    test "converts native parallel arrays into skew-T map list sorted surface-first" do
      profile = struct(HrrrNativeProfile, @valid_attrs)

      levels = HrrrNativeProfile.to_skew_t_profile(profile)

      assert length(levels) == 3

      # Ordered surface-first (descending pressure).
      pressures = Enum.map(levels, & &1["pres"])
      assert pressures == Enum.sort(pressures, :desc)

      [surface | _] = levels

      # pressure Pa → hPa
      assert_in_delta surface["pres"], 1010.0, 0.01
      # temperature K → °C
      assert_in_delta surface["tmpc"], 295.0 - 273.15, 0.01
      # geopotential height passes through
      assert surface["hght"] == 10.0

      # Dewpoint derived from SPFH + pressure + temperature via Magnus.
      # e = q*p/(0.622 + 0.378*q)/100 = 0.010*101000/0.62578/100 ≈ 16.140 hPa
      # td = 243.5*ln(e/6.112)/(17.67 − ln(e/6.112)) ≈ 14.16 °C
      assert_in_delta surface["dwpc"], 14.16, 0.1
    end

    test "returns empty list when arrays are nil or empty" do
      blank = %HrrrNativeProfile{
        heights_m: nil,
        temp_k: nil,
        spfh: nil,
        pressure_pa: nil
      }

      assert HrrrNativeProfile.to_skew_t_profile(blank) == []
    end

    test "drops levels with missing pressure or temperature" do
      attrs =
        @valid_attrs
        |> Map.put(:temp_k, [295.0, nil, 285.0])
        |> Map.put(:pressure_pa, [101_000.0, 99_800.0, nil])

      profile = struct(HrrrNativeProfile, attrs)

      levels = HrrrNativeProfile.to_skew_t_profile(profile)
      assert length(levels) == 1
      assert hd(levels)["hght"] == 10.0
    end
  end
end
