defmodule AprsmeWeb.MobileUserSocket do
  @moduledoc """
  Socket for mobile applications (iOS/Android) to receive real-time APRS packets.

  Connections are anonymous but rate-limited per peer IP to prevent a single
  client from opening unbounded connections and exhausting server resources.
  """
  use Phoenix.Socket

  require Logger

  # Channels
  channel "mobile:packets", AprsmeWeb.MobileChannel

  # 30 new connections per IP per minute — well above normal reconnect churn
  # but tight enough to stop a script from floodting the socket layer.
  @connect_limit 30
  @connect_scale 60_000

  @impl true
  def connect(_params, socket, connect_info) do
    ip = peer_ip(connect_info)

    if test_env?() or allow_connect?(ip) do
      ua = connect_info[:user_agent] || "unknown"

      socket =
        socket
        |> assign(:peer_ip, ip)
        |> assign(:user_agent, ua)

      # Track connection for analytics (fire-and-forget)
      Aprsme.PlausibleAnalytics.track(
        %{user_agent: ua, remote_ip: ip, request_path: "/mobile/websocket"},
        "ws_connect"
      )

      Aprsme.ApiMetrics.track(%{}, "ws_connect")

      {:ok, socket}
    else
      Logger.warning("Mobile socket connect rate-limited for ip=#{ip}")
      :error
    end
  end

  defp allow_connect?(ip) do
    case Aprsme.RateLimiter.hit("mobile_socket_connect:#{ip}", @connect_scale, @connect_limit) do
      {:allow, _count} -> true
      {:deny, _retry_after} -> false
    end
  end

  defp test_env?, do: Application.get_env(:aprsme, :env) == :test

  @impl true
  def id(_socket), do: nil

  defp peer_ip(%{x_headers: headers}) when is_list(headers) do
    headers
    |> Enum.find_value(fn
      {"cf-connecting-ip", v} -> v
      {"x-forwarded-for", v} -> v |> String.split(",") |> List.first() |> String.trim()
      {"x-real-ip", v} -> v
      _ -> nil
    end)
    |> case do
      nil -> "unknown"
      ip -> ip
    end
  end

  defp peer_ip(%{peer_data: %{address: address}}) when not is_nil(address) do
    address |> :inet.ntoa() |> to_string()
  end

  defp peer_ip(_), do: "unknown"
end
