defmodule Aprsme.Cluster.ConnectionManager do
  @moduledoc """
  Manages the APRS-IS connection based on cluster leadership.
  Only starts the connection when this node is elected as leader.
  """
  use GenServer

  alias Aprsme.Cluster.LeaderElection
  alias Aprsme.Is.IsSupervisor

  require Logger

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @impl true
  def init(_opts) do
    # Subscribe to leadership changes
    :ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, "cluster:leadership")

    # Check initial leadership state
    delay = Application.get_env(:aprsme, :connection_manager_init_delay, 1000)
    _ = Process.send_after(self(), :check_initial_state, delay)

    {:ok, %{connection_started: false}}
  end

  @impl true
  def handle_info(:check_initial_state, state) do
    if leader_check() do
      Logger.info("This node is the leader, starting APRS-IS connection")
      start_aprs_connection()
      {:noreply, %{state | connection_started: true}}
    else
      Logger.info("This node is not the leader, APRS-IS connection will not be started")
      {:noreply, state}
    end
  end

  @impl true
  def handle_info({:leadership_change, node, became_leader}, state) do
    handle_leadership_change(became_leader, node == node(), state)
  end

  def handle_info(_msg, state), do: {:noreply, state}

  # Became the cluster leader and we weren't already running — start APRS-IS.
  defp handle_leadership_change(true, true, %{connection_started: false} = state) do
    Logger.info("This node became the leader, starting APRS-IS connection")
    start_aprs_connection()
    {:noreply, %{state | connection_started: true}}
  end

  # Lost leadership while we were running — stop APRS-IS.
  defp handle_leadership_change(false, true, %{connection_started: true} = state) do
    Logger.info("This node lost leadership, stopping APRS-IS connection")
    stop_aprs_connection()
    {:noreply, %{state | connection_started: false}}
  end

  # Any other combination (different node, already in the right state) is a no-op.
  defp handle_leadership_change(_became_leader, _us?, state), do: {:noreply, state}

  @impl true
  def terminate(_reason, _state) do
    Phoenix.PubSub.unsubscribe(Aprsme.PubSub, "cluster:leadership")
    :ok
  end

  defp leader_check do
    LeaderElection.leader?()
  catch
    :exit, _ -> false
  end

  defp start_aprs_connection do
    # Start the APRS-IS connection supervisor if not already started
    case DynamicSupervisor.start_child(
           Aprsme.DynamicSupervisor,
           {IsSupervisor, []}
         ) do
      {:ok, _pid} ->
        Logger.info("APRS-IS connection started successfully")

      {:error, {:already_started, _pid}} ->
        Logger.info("APRS-IS connection was already running")

      {:error, reason} ->
        Logger.error("Failed to start APRS-IS connection: #{inspect(reason)}")
    end
  end

  defp stop_aprs_connection do
    # Find and terminate the APRS-IS connection supervisor
    children = DynamicSupervisor.which_children(Aprsme.DynamicSupervisor)

    Enum.each(children, fn
      {_, pid, _, [IsSupervisor]} when is_pid(pid) ->
        Logger.info("Stopping APRS-IS connection supervisor")
        DynamicSupervisor.terminate_child(Aprsme.DynamicSupervisor, pid)

      _ ->
        :ok
    end)
  end
end
