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

  alias Microwaveprop.Propagation.BandConfig

  @all_freqs [
    50,
    144,
    222,
    432,
    902,
    1_296,
    2_304,
    3_400,
    5_760,
    10_000,
    24_000,
    47_000,
    68_000,
    75_000,
    122_000,
    134_000,
    142_000,
    145_000,
    241_000,
    288_000,
    322_000,
    403_000,
    411_000
  ]

  describe "get/1" do
    test "returns config for 10 GHz" do
      config = BandConfig.get(10_000)
      assert config.freq_mhz == 10_000
      assert config.label == "10 GHz"
      assert config.humidity_effect == :beneficial
      assert config.humidity_penalty == 0.0
      assert config.rain_k == 0.010
      assert config.rain_alpha == 1.28
      assert config.typical_range_km == 200
      assert config.extended_range_km == 500
      assert config.exceptional_range_km == 1000
    end

    test "returns config for 24 GHz" do
      config = BandConfig.get(24_000)
      assert config.freq_mhz == 24_000
      assert config.label == "24 GHz"
      assert config.humidity_effect == :harmful
      assert config.humidity_penalty == 1.6
      assert config.rain_k == 0.070
      assert config.rain_alpha == 1.07
      assert config.typical_range_km == 100
      assert config.extended_range_km == 250
      assert config.exceptional_range_km == 500
    end

    test "returns config for 47 GHz" do
      config = BandConfig.get(47_000)
      assert config.freq_mhz == 47_000
      assert config.humidity_effect == :harmful
      assert config.humidity_penalty == 1.0
      assert config.rain_k == 0.187
      assert config.rain_alpha == 0.93
    end

    test "returns config for 68 GHz with atmospheric absorption" do
      config = BandConfig.get(68_000)
      assert config.o2_db_km == 0.90
      assert config.h2o_coeff == 0.007
      assert config.humidity_penalty == 1.4
      assert config.rain_k == 0.310
      assert config.rain_alpha == 0.86
      assert config.typical_range_km == 40
      assert config.extended_range_km == 80
      assert config.exceptional_range_km == 150
    end

    test "returns config for 75 GHz" do
      config = BandConfig.get(75_000)
      assert config.o2_db_km == 0.012
      assert config.h2o_coeff == 0.006
      assert config.humidity_penalty == 1.2
      assert config.rain_k == 0.345
      assert config.rain_alpha == 0.84
    end

    test "returns config for 122 GHz" do
      config = BandConfig.get(122_000)
      assert config.o2_db_km == 0.80
      assert config.h2o_coeff == 0.010
      assert config.humidity_penalty == 1.0
      assert config.rain_k == 0.498
      assert config.rain_alpha == 0.77
    end

    test "returns config for 134 GHz" do
      config = BandConfig.get(134_000)
      assert config.o2_db_km == 0.08
      assert config.h2o_coeff == 0.015
      assert config.humidity_penalty == 1.3
      assert config.rain_k == 0.520
      assert config.rain_alpha == 0.75
    end

    test "returns config for 241 GHz" do
      config = BandConfig.get(241_000)
      assert config.o2_db_km == 0.08
      assert config.h2o_coeff == 0.30
      assert config.humidity_penalty == 3.0
      assert config.rain_k == 0.550
      assert config.rain_alpha == 0.70
      assert config.typical_range_km == 10
      assert config.extended_range_km == 50
      assert config.exceptional_range_km == 115
    end

    test "returns nil for unknown band" do
      assert BandConfig.get(999) == nil
    end

    test "returns config for 144 MHz (2m VHF)" do
      config = BandConfig.get(144)
      assert config.freq_mhz == 144
      assert config.label == "144 MHz"
      # Tropo ducting physics is the same as low microwave — humidity
      # builds refractivity, rain and atmospheric absorption are
      # negligible at VHF.
      assert config.humidity_effect == :beneficial
      assert config.humidity_penalty == 0.0
      assert config.rain_k == 0.0
      assert config.rain_alpha == 1.0
      assert config.o2_db_km == 0.0
      assert config.h2o_coeff == 0.0
      # 2m tropo ducts can reach 2500+ km under exceptional conditions
      # (e.g. documented Hawaii ↔ California openings).
      assert config.exceptional_range_km >= 2000
    end

    test "returns config for 432 MHz (70cm UHF)" do
      config = BandConfig.get(432)
      assert config.freq_mhz == 432
      assert config.label == "432 MHz"
      assert config.humidity_effect == :beneficial
      assert config.humidity_penalty == 0.0
      assert config.rain_k == 0.0
      assert config.rain_alpha == 1.0
      assert config.h2o_coeff == 0.0
      # 70cm tropo is strong but slightly less extreme than 2m.
      assert config.exceptional_range_km >= 1500
    end

    test "returns config for 142 GHz (between H2O 118 and 183 lines)" do
      config = BandConfig.get(142_000)
      assert config.freq_mhz == 142_000
      assert config.humidity_effect == :harmful
      assert config.rain_alpha < 1.0
    end

    test "returns config for 145 GHz" do
      config = BandConfig.get(145_000)
      assert config.freq_mhz == 145_000
      assert config.humidity_effect == :harmful
    end

    test "returns config for 288 GHz (sub-mm window)" do
      config = BandConfig.get(288_000)
      assert config.freq_mhz == 288_000
      assert config.humidity_effect == :harmful
      assert config.typical_range_km <= 10
    end

    test "returns config for 322 GHz" do
      assert %{freq_mhz: 322_000, humidity_effect: :harmful} = BandConfig.get(322_000)
    end

    test "returns config for 403 GHz" do
      assert %{freq_mhz: 403_000, humidity_effect: :harmful} = BandConfig.get(403_000)
    end

    test "returns config for 411 GHz" do
      assert %{freq_mhz: 411_000, humidity_effect: :harmful} = BandConfig.get(411_000)
    end
  end

  describe "all_bands/0" do
    test "returns 23 bands (13 original + 142/145/288/322/403/411 GHz + 50/144/222/432 MHz)" do
      bands = BandConfig.all_bands()
      assert length(bands) == 23
    end

    test "bands are sorted by frequency" do
      freqs = Enum.map(BandConfig.all_bands(), & &1.freq_mhz)
      assert freqs == @all_freqs
    end
  end

  describe "all_freqs/0" do
    test "returns sorted frequency list" do
      assert BandConfig.all_freqs() == @all_freqs
    end
  end

  describe "weights/0" do
    test "returns all 10 factor keys" do
      weights = BandConfig.weights()

      expected_keys =
        MapSet.new([
          :humidity,
          :time_of_day,
          :td_depression,
          :refractivity,
          :sky,
          :season,
          :wind,
          :rain,
          :pwat,
          :pressure
        ])

      assert MapSet.new(Map.keys(weights)) == expected_keys
    end

    test "weights sum to 1.0" do
      total = BandConfig.weights() |> Map.values() |> Enum.sum()
      assert_in_delta total, 1.0, 0.001
    end

    test "individual weights have correct values" do
      weights = BandConfig.weights()
      assert weights.humidity == 0.1243
      assert weights.time_of_day == 0.0496
      assert weights.td_depression == 0.0978
      assert weights.refractivity == 0.1049
      assert weights.sky == 0.08
      assert weights.season == 0.1112
      assert weights.wind == 0.08
      assert weights.rain == 0.1362
      assert weights.pwat == 0.1128
      assert weights.pressure == 0.1032
    end
  end

  describe "weights/1 — band-aware" do
    test "returns the default weights when the band has no override" do
      # 50 MHz has 0 contacts in the corpus → no per-band fit → default weights.
      band = BandConfig.get(50)
      assert BandConfig.weights(band) == BandConfig.weights()
    end

    test "returns the override weights when the band has a :weights key" do
      band = %{freq_mhz: 99_999, weights: %{rain: 0.5, humidity: 0.5}}
      assert BandConfig.weights(band) == %{rain: 0.5, humidity: 0.5}
    end

    test "returns defaults when passed nil" do
      assert BandConfig.weights(nil) == BandConfig.weights()
    end
  end

  describe "sunrise_table/0" do
    test "returns 12 monthly values" do
      table = BandConfig.sunrise_table()
      assert length(table) == 12
    end

    test "returns expected sunrise hours" do
      assert BandConfig.sunrise_table() == [
               7.4,
               7.3,
               7.0,
               6.7,
               6.35,
               6.25,
               6.35,
               6.65,
               6.9,
               7.1,
               7.35,
               7.45
             ]
    end
  end

  describe "tiers/0" do
    test "returns 5 tiers" do
      tiers = BandConfig.tiers()
      assert length(tiers) == 5
    end

    test "tiers are ordered by threshold descending" do
      thresholds = Enum.map(BandConfig.tiers(), & &1.min_score)
      assert thresholds == [80, 65, 50, 33, 0]
    end

    test "tier labels and colors are correct" do
      tiers = BandConfig.tiers()
      [excellent, good, marginal, poor, negligible] = tiers

      assert excellent == %{min_score: 80, label: "EXCELLENT", color: "#059669"}
      assert good == %{min_score: 65, label: "GOOD", color: "#0d9488"}
      assert marginal == %{min_score: 50, label: "MARGINAL", color: "#ca8a04"}
      assert poor == %{min_score: 33, label: "POOR", color: "#ea580c"}
      assert negligible == %{min_score: 0, label: "NEGLIGIBLE", color: "#dc2626"}
    end
  end

  describe "seasonal bases" do
    test "every band has 12 months in seasonal_base" do
      for freq <- @all_freqs do
        config = BandConfig.get(freq)
        assert map_size(config.seasonal_base) == 12, "#{freq} missing months in seasonal_base"
        assert Enum.all?(1..12, &Map.has_key?(config.seasonal_base, &1))
      end
    end

    test "10 GHz seasonal base peaks in summer" do
      config = BandConfig.get(10_000)
      assert config.seasonal_base[7] == 95
      assert config.seasonal_base[3] == 22
    end

    test "24 GHz seasonal base peaks in winter" do
      config = BandConfig.get(24_000)
      assert config.seasonal_base[11] == 96
      assert config.seasonal_base[7] == 18
    end

    test "24 GHz has seasonal adjustments" do
      config = BandConfig.get(24_000)
      assert config.seasonal_adj[7] == -10
      assert config.seasonal_adj[5] == -4
    end

    test "10 GHz has empty seasonal adjustments" do
      config = BandConfig.get(10_000)
      assert config.seasonal_adj == %{}
    end
  end

  describe "humidity_beneficial_thresholds/0" do
    test "returns threshold tuples sorted by hour" do
      thresholds = BandConfig.humidity_beneficial_thresholds()
      assert thresholds == [{4, 55}, {7, 70}, {10, 82}, {14, 90}, {18, 95}, {22, 88}]
    end
  end

  describe "humidity_beneficial_default/0" do
    test "returns 75" do
      assert BandConfig.humidity_beneficial_default() == 75
    end
  end

  describe "refractivity_thresholds/0" do
    test "returns gradient/score tuples" do
      thresholds = BandConfig.refractivity_thresholds()

      assert thresholds == [
               {-200, 98, 85},
               {-150, 92, 80},
               {-100, 82, 72},
               {-75, 68, 62},
               {-55, 55, 55},
               {-40, 48, 48}
             ]
    end
  end

  describe "refractivity_default/0" do
    test "returns default scores" do
      assert BandConfig.refractivity_default() == {42, 42}
    end
  end

  describe "only 10 GHz is beneficial" do
    test "10 GHz has beneficial humidity effect" do
      assert BandConfig.get(10_000).humidity_effect == :beneficial
    end

    test "sub-10 GHz bands are beneficial, 24+ GHz are harmful" do
      beneficial = [50, 144, 222, 432, 902, 1_296, 2_304, 3_400, 5_760, 10_000]
      harmful = @all_freqs -- beneficial

      for freq <- beneficial do
        assert BandConfig.get(freq).humidity_effect == :beneficial, "#{freq} should be :beneficial"
      end

      for freq <- harmful do
        assert BandConfig.get(freq).humidity_effect == :harmful, "#{freq} should be :harmful"
      end
    end
  end
end
