defmodule Microwaveprop.Propagation.RainScatter do
  @moduledoc """
  Estimates rain scatter potential from NEXRAD composite reflectivity.

  Rain scatter enables microwave contacts at 100-300+ km by scattering
  signals off precipitation cells. Signal strength depends on reflectivity
  (rain intensity), frequency, and geometry (distance from each station
  to the rain cell).

  Uses a simplified bistatic radar equation:
    scatter_db ≈ 10*log10(Z) + 10*log10(V) - 20*log10(R1) - 20*log10(R2)
                 + frequency_gain - path_losses

  where Z is reflectivity factor (from dBZ), V is effective scattering
  volume, and R1/R2 are distances from each endpoint to the cell.
  """

  @earth_radius_km 6371.0

  # Minimum reflectivity to consider for scatter (dBZ)
  @min_dbz 25.0

  # Maximum scatter range from either endpoint (km)
  @max_range_km 300.0

  @doc """
  Find rain cells with scatter potential for a given point and band.

  Takes a list of `{lat, lon, dbz}` rain cells (from NEXRAD extraction),
  the observer's position, and frequency in GHz.

  Returns a list of scatter cell maps sorted by potential (strongest first),
  each with: lat, lon, dbz, distance_km, scatter_db (relative signal estimate),
  and bearing from the observer.
  """
  @spec find_scatter_cells(
          [{float(), float(), float()}],
          float(),
          float(),
          float()
        ) :: [
          %{
            lat: float(),
            lon: float(),
            dbz: float(),
            distance_km: float(),
            bearing: float(),
            scatter_db: float()
          }
        ]
  def find_scatter_cells(rain_cells, obs_lat, obs_lon, freq_ghz) do
    rain_cells
    |> Enum.filter(fn {_lat, _lon, dbz} -> dbz >= @min_dbz end)
    |> Enum.map(fn {lat, lon, dbz} ->
      dist_km = haversine_km(obs_lat, obs_lon, lat, lon)
      bearing = bearing_deg(obs_lat, obs_lon, lat, lon)

      # Scatter signal estimate (relative dB)
      # Higher reflectivity = more scattering targets
      # Closer cells = stronger signal (inverse square from both endpoints)
      # Higher frequency = stronger Rayleigh scattering (up to ~10 GHz, then Mie)
      scatter_db = estimate_scatter_db(dbz, dist_km, freq_ghz)

      %{
        lat: Float.round(lat, 3),
        lon: Float.round(lon, 3),
        dbz: Float.round(dbz, 1),
        distance_km: Float.round(dist_km, 1),
        bearing: Float.round(bearing, 0),
        scatter_db: Float.round(scatter_db, 1)
      }
    end)
    |> Enum.filter(&(&1.distance_km <= @max_range_km and &1.distance_km >= 10))
    |> Enum.sort_by(&(-&1.scatter_db))
    |> Enum.take(20)
  end

  @doc """
  Classify overall scatter potential for a point.

  Returns :excellent, :good, :marginal, or :none based on
  the best available scatter cell.
  """
  @spec classify([%{scatter_db: float()}]) :: :excellent | :good | :marginal | :none
  def classify(scatter_cells) do
    case scatter_cells do
      [] ->
        :none

      [best | _] ->
        cond do
          best.scatter_db >= -10 -> :excellent
          best.scatter_db >= -20 -> :good
          best.scatter_db >= -30 -> :marginal
          true -> :none
        end
    end
  end

  # Simplified scatter signal estimate in relative dB.
  #
  # Based on bistatic radar equation for volume scattering:
  #   P_rx ∝ Z * σ_scatter * V / (R^4)
  #
  # For a single cell at distance R from the observer (assuming the
  # other station is also near R for a rough estimate):
  #   scatter_db ≈ dBZ + freq_factor - 40*log10(R_km) + volume_term
  defp estimate_scatter_db(dbz, dist_km, freq_ghz) do
    # Reflectivity contribution (dBZ is already in log scale)
    z_term = dbz

    # Frequency factor: Rayleigh scattering ∝ f^4 below ~10 GHz,
    # transitions to Mie at higher frequencies (weaker dependence).
    # Normalize to 10 GHz as reference.
    freq_factor =
      cond do
        freq_ghz <= 10 -> 40 * :math.log10(max(freq_ghz, 0.5) / 10.0)
        freq_ghz <= 50 -> 20 * :math.log10(freq_ghz / 10.0)
        true -> 20 * :math.log10(50.0 / 10.0)
      end

    # Path loss: R^4 for bistatic (R^2 each way)
    # Normalize to 100 km reference distance
    range_loss = 40 * :math.log10(max(dist_km, 1) / 100.0)

    # Effective scattering volume (~1 km^3 rain cell)
    volume_term = 10.0

    # Baseline offset to center the scale around useful values
    baseline = -50.0

    baseline + z_term + freq_factor - range_loss + volume_term
  end

  defp haversine_km(lat1, lon1, lat2, lon2) do
    dlat = :math.pi() * (lat2 - lat1) / 180.0
    dlon = :math.pi() * (lon2 - lon1) / 180.0
    rlat1 = :math.pi() * lat1 / 180.0
    rlat2 = :math.pi() * lat2 / 180.0

    a =
      :math.sin(dlat / 2) * :math.sin(dlat / 2) +
        :math.cos(rlat1) * :math.cos(rlat2) *
          :math.sin(dlon / 2) * :math.sin(dlon / 2)

    c = 2.0 * :math.atan2(:math.sqrt(a), :math.sqrt(1.0 - a))
    @earth_radius_km * c
  end

  defp bearing_deg(lat1, lon1, lat2, lon2) do
    rlat1 = :math.pi() * lat1 / 180.0
    rlat2 = :math.pi() * lat2 / 180.0
    dlon = :math.pi() * (lon2 - lon1) / 180.0

    x = :math.sin(dlon) * :math.cos(rlat2)
    y = :math.cos(rlat1) * :math.sin(rlat2) - :math.sin(rlat1) * :math.cos(rlat2) * :math.cos(dlon)

    bearing = :math.atan2(x, y) * 180.0 / :math.pi()
    Float.round(:math.fmod(bearing + 360.0, 360.0), 1)
  end
end
