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

  alias Microwaveprop.Propagation.Duct

  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))
    }
  end

  describe "refractivity_profile/1" do
    test "computes N at each level with plausible values" do
      p =
        profile([
          {10.0, 295.0, 0.012, 101_000.0},
          {500.0, 290.0, 0.006, 95_000.0},
          {1000.0, 283.0, 0.003, 90_000.0}
        ])

      n_profile = Duct.refractivity_profile(p)
      assert length(n_profile) == 3

      # N should decrease with height in a normal atmosphere
      [{_, n0}, {_, n1}, {_, n2}] = n_profile
      assert n0 > n1
      assert n1 > n2
      # Typical surface N = 300-400
      assert n0 > 250 and n0 < 500
    end
  end

  describe "m_profile/1" do
    test "M = N + 157*h where h is in km" do
      n_profile = [{0.0, 350.0}, {1000.0, 280.0}]
      m_profile = Duct.m_profile(n_profile)

      [{h0, m0}, {1000.0, m1}] = m_profile
      assert h0 == 0.0
      assert_in_delta m0, 350.0, 0.01
      # M at 1km = 280 + 157*1.0 = 437
      assert_in_delta m1, 437.0, 0.01
    end
  end

  describe "detect_ducts/1" do
    test "returns empty list when M increases monotonically (no duct)" do
      # Standard atmosphere: M always increases with height
      m_profile = [
        {0.0, 350.0},
        {100.0, 365.0},
        {300.0, 397.0},
        {500.0, 428.0},
        {1000.0, 507.0}
      ]

      assert Duct.detect_ducts(m_profile) == []
    end

    test "detects a surface-based duct where M decreases" do
      # Surface-based duct: M decreases from 0 to 100m, then resumes increasing
      m_profile = [
        {0.0, 380.0},
        {50.0, 370.0},
        {100.0, 360.0},
        {200.0, 375.0},
        {500.0, 428.0}
      ]

      ducts = Duct.detect_ducts(m_profile)
      assert length(ducts) == 1

      [duct] = ducts
      assert duct.base_m == 0.0
      assert duct.top_m == 100.0
      assert duct.thickness_m == 100.0
      assert duct.m_deficit > 0
    end

    test "detects an elevated duct" do
      # Elevated duct between 500-700m
      m_profile = [
        {0.0, 350.0},
        {100.0, 366.0},
        {300.0, 397.0},
        {500.0, 430.0},
        {600.0, 425.0},
        {700.0, 420.0},
        {1000.0, 500.0}
      ]

      ducts = Duct.detect_ducts(m_profile)
      assert length(ducts) == 1

      [duct] = ducts
      assert duct.base_m == 500.0
      assert duct.top_m == 700.0
      assert duct.thickness_m == 200.0
    end

    test "detects multiple ducts" do
      m_profile = [
        {0.0, 380.0},
        {50.0, 370.0},
        {100.0, 390.0},
        {500.0, 430.0},
        {600.0, 425.0},
        {700.0, 440.0}
      ]

      ducts = Duct.detect_ducts(m_profile)
      assert length(ducts) == 2
    end
  end

  describe "min_trapped_frequency_ghz/1" do
    test "a narrow duct with weak deficit traps only high frequencies" do
      # 10m duct, 1 M-unit deficit → λ_max ≈ 0.025m → f ≈ 12 GHz
      duct = %{thickness_m: 10.0, m_deficit: 1.0}
      f = Duct.min_trapped_frequency_ghz(duct)
      assert_in_delta f, 12.0, 1.0
    end

    test "a moderate duct traps sub-GHz frequencies" do
      # 50m duct, 10 M-units → λ_max ≈ 0.395m → f ≈ 0.76 GHz
      duct = %{thickness_m: 50.0, m_deficit: 10.0}
      f = Duct.min_trapped_frequency_ghz(duct)
      assert_in_delta f, 0.76, 0.1
    end

    test "a thick strong duct traps very low frequencies" do
      # 200m, 20 M-units → λ_max ≈ 2.24m → f ≈ 0.13 GHz
      duct = %{thickness_m: 200.0, m_deficit: 20.0}
      f = Duct.min_trapped_frequency_ghz(duct)
      assert f < 0.2
    end

    test "thicker ducts trap lower frequencies than thinner ones" do
      thin = Duct.min_trapped_frequency_ghz(%{thickness_m: 30.0, m_deficit: 5.0})
      thick = Duct.min_trapped_frequency_ghz(%{thickness_m: 150.0, m_deficit: 5.0})
      assert thick < thin
    end
  end

  describe "analyze/1" do
    test "full pipeline from native profile to duct list with frequencies" do
      # Profile with a surface-based duct (strong inversion)
      p =
        profile([
          {10.0, 300.0, 0.018, 101_325.0},
          {50.0, 301.0, 0.017, 100_800.0},
          {100.0, 302.0, 0.012, 100_300.0},
          {150.0, 298.0, 0.008, 99_800.0},
          {300.0, 293.0, 0.005, 97_000.0},
          {500.0, 288.0, 0.003, 95_000.0}
        ])

      result = Duct.analyze(p)
      assert is_list(result.ducts)
      assert is_float(result.best_duct_band_ghz) or is_nil(result.best_duct_band_ghz)
    end
  end
end
