defmodule Microwaveprop.Workers.TerrainProfileWorker do
  @moduledoc false
  use Oban.Worker,
    queue: :terrain,
    max_attempts: 20,
    unique: [period: 300, states: [:available, :scheduled, :executing, :retryable]]

  alias Microwaveprop.Radio
  alias Microwaveprop.Terrain
  alias Microwaveprop.Terrain.ElevationClient
  alias Microwaveprop.Terrain.TerrainAnalysis
  alias Microwaveprop.Weather

  @impl Oban.Worker
  def backoff(%Oban.Job{attempt: attempt}) do
    min(120 * Integer.pow(2, attempt - 1), _six_hours = 21_600)
  end

  @impl Oban.Worker
  def perform(%Oban.Job{args: %{"contact_id" => contact_id}}) do
    Microwaveprop.Instrument.span([:worker, :terrain_profile], %{contact_id: contact_id}, fn ->
      if Terrain.has_terrain_profile?(contact_id) do
        Radio.set_enrichment_status!([contact_id], :terrain_status, :complete)
        :ok
      else
        analyse_terrain(contact_id)
      end
    end)
  end

  defp analyse_terrain(contact_id) do
    contact = Radio.get_contact!(contact_id)

    with %{"lat" => lat1} <- contact.pos1,
         lon1 when is_number(lon1) <- contact.pos1["lon"],
         %{"lat" => lat2} <- contact.pos2,
         lon2 when is_number(lon2) <- contact.pos2["lon"] do
      fetch_and_store_profile(contact_id, contact, lat1, lon1, lat2, lon2)
    else
      _ ->
        Radio.set_enrichment_status!([contact_id], :terrain_status, :unavailable)
        :ok
    end
  end

  defp fetch_and_store_profile(contact_id, contact, lat1, lon1, lat2, lon2) do
    dist_km = Decimal.to_float(contact.distance_km || Decimal.new(0))
    freq_ghz = Decimal.to_float(contact.band) / 1000
    k = lookup_k_factor(contact)

    case ElevationClient.fetch_elevation_profile(lat1, lon1, lat2, lon2, 64, download: true) do
      {:ok, profile} ->
        store_terrain_result(contact_id, profile, dist_km, freq_ghz, k)

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp store_terrain_result(contact_id, profile, dist_km, freq_ghz, k) do
    analysis = TerrainAnalysis.analyse(profile, dist_km, freq_ghz, k_factor: k)

    path_points =
      Enum.map(profile, fn p ->
        %{"lat" => p.lat, "lon" => p.lon, "d" => p.d, "elev" => p.elev, "dist_km" => p.dist_km}
      end)

    Terrain.upsert_terrain_profile(%{
      contact_id: contact_id,
      sample_count: length(profile),
      path_points: path_points,
      max_elevation_m: analysis.max_elevation_m,
      min_clearance_m: analysis.min_clearance_m,
      diffraction_db: analysis.diffraction_db,
      fresnel_hit_count: analysis.fresnel_hit_count,
      obstructed_count: analysis.obstructed_count,
      verdict: analysis.verdict
    })

    Radio.set_enrichment_status!([contact_id], :terrain_status, :complete)

    Phoenix.PubSub.broadcast(
      Microwaveprop.PubSub,
      "contact_enrichment:#{contact_id}",
      {:terrain_ready, contact_id}
    )

    :ok
  end

  defp lookup_k_factor(contact) do
    case Weather.hrrr_for_contact(contact) do
      %{min_refractivity_gradient: grad} when not is_nil(grad) ->
        TerrainAnalysis.k_factor(grad)

      _ ->
        4 / 3
    end
  end
end
