defmodule AprsmeWeb.InfoController do
  use AprsmeWeb, :controller

  alias Aprsme.Packets
  alias AprsmeWeb.MapLive.PacketUtils

  @neighbor_radius_km 10
  @neighbor_limit 10

  def show(conn, %{"callsign" => callsign}) do
    normalized_callsign = String.upcase(String.trim(callsign))
    packet = get_latest_packet(normalized_callsign)
    neighbors = get_neighbors(packet, normalized_callsign)

    render(conn, :show,
      callsign: normalized_callsign,
      packet: packet,
      neighbors: neighbors,
      page_title: "APRS station #{normalized_callsign}"
    )
  end

  defp get_latest_packet(callsign) do
    %{callsign: callsign, limit: 1}
    |> Packets.get_recent_packets()
    |> List.first()
  end

  defp get_neighbors(nil, _callsign), do: []

  defp get_neighbors(packet, callsign) do
    lat = packet.lat
    lon = packet.lon

    if is_nil(lat) or is_nil(lon) do
      []
    else
      # Simple bounding box for ~10km radius
      delta = @neighbor_radius_km / 111.0
      min_lat = lat - delta
      max_lat = lat + delta
      min_lon = lon - delta
      max_lon = lon + delta
      opts = %{bounds: [min_lon, min_lat, max_lon, max_lat], limit: 50}

      opts
      |> Packets.get_recent_packets()
      |> Enum.filter(fn p ->
        (p.sender != callsign and p.lat) && p.lon
      end)
      |> uniq_by(& &1.sender)
      |> Enum.map(fn p ->
        dist = haversine(lat, lon, p.lat, p.lon)

        %{
          callsign: p.sender,
          distance: format_distance(dist),
          last_heard: PacketUtils.get_timestamp(p),
          packet: p
        }
      end)
      |> Enum.sort_by(& &1.distance)
      |> Enum.take(@neighbor_limit)
    end
  end

  defp uniq_by(list, fun) do
    list
    |> Enum.reduce({MapSet.new(), []}, fn item, {set, acc} ->
      key = fun.(item)

      if MapSet.member?(set, key) do
        {set, acc}
      else
        {MapSet.put(set, key), [item | acc]}
      end
    end)
    |> elem(1)
    |> Enum.reverse()
  end

  defp haversine(lat1, lon1, lat2, lon2) do
    # Returns distance in km
    r = 6371
    dlat = :math.pi() / 180 * (lat2 - lat1)
    dlon = :math.pi() / 180 * (lon2 - lon1)

    a =
      :math.sin(dlat / 2) * :math.sin(dlat / 2) +
        :math.cos(:math.pi() / 180 * lat1) * :math.cos(:math.pi() / 180 * lat2) *
          :math.sin(dlon / 2) * :math.sin(dlon / 2)

    c = 2 * :math.atan2(:math.sqrt(a), :math.sqrt(1 - a))
    r * c
  end

  defp format_distance(km) when km < 1.0 do
    "#{Float.round(km * 1000, 0)} m"
  end

  defp format_distance(km) do
    "#{Float.round(km, 2)} km"
  end
end
