defmodule Aprsme.BroadcastTaskSupervisor do
  @moduledoc """
  A dedicated Task.Supervisor for handling broadcast operations efficiently.

  This supervisor manages a pool of tasks for broadcasting packets to multiple
  clients concurrently, preventing the main GenServer processes from blocking
  on I/O operations.
  """

  use Supervisor

  @pool_name :broadcast_task_pool

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    # Calculate optimal pool size based on schedulers
    pool_size = System.schedulers_online() * 2

    children = [
      # Create a Task.Supervisor with a specific name
      {Task.Supervisor, name: @pool_name}
    ]

    # Store pool configuration in persistent_term for fast access
    :persistent_term.put({__MODULE__, :pool_size}, pool_size)
    :persistent_term.put({__MODULE__, :pool_name}, @pool_name)

    Supervisor.init(children, strategy: :one_for_one)
  end

  @doc """
  Broadcasts a message to multiple topics asynchronously using the task pool.

  Uses `Task.Supervisor.start_child/2` for fire-and-forget execution.
  The spawned task is not linked to the caller and does not send reply
  messages back, avoiding orphaned `{ref, result}` and `:DOWN` messages
  in the calling process mailbox.

  ## Parameters
    - topics: List of PubSub topics to broadcast to
    - message: The message to broadcast
    - pubsub: The PubSub server (defaults to Aprsme.PubSub)

  ## Returns
    - {:ok, pid} of the spawned task
  """
  def broadcast_async(topics, message, pubsub \\ Aprsme.PubSub) do
    Task.Supervisor.start_child(@pool_name, fn ->
      # Use Stream for memory efficiency with large topic lists
      topics
      # Process in chunks to balance load
      |> Stream.chunk_every(10)
      |> Enum.each(fn topic_chunk ->
        broadcast_to_topics(topic_chunk, message, pubsub)
      end)
    end)
  end

  @doc """
  Broadcasts a single message to a topic asynchronously.
  """
  def broadcast_one_async(topic, message, pubsub \\ Aprsme.PubSub) do
    Task.Supervisor.start_child(@pool_name, fn ->
      Phoenix.PubSub.broadcast(pubsub, topic, message)
    end)
  end

  @doc """
  Executes a function asynchronously in the broadcast pool.

  This is useful for any broadcast-related async operation.
  """
  def async_execute(fun) when is_function(fun, 0) do
    Task.Supervisor.start_child(@pool_name, fun)
  end

  @doc """
  Gets statistics about the broadcast pool.
  """
  def get_stats do
    children = Task.Supervisor.children(@pool_name)

    %{
      active_tasks: length(children),
      pool_size: :persistent_term.get({__MODULE__, :pool_size}, 0),
      scheduler_usage: scheduler_usage()
    }
  end

  # Calculate approximate scheduler usage
  defp scheduler_usage do
    # In test env, skip expensive sampling and return dummy value
    if Application.get_env(:aprsme, :env) == :test do
      0.0
    else
      sample_seconds = Application.get_env(:aprsme, :scheduler_sample_seconds, 1)
      compute_scheduler_usage(sample_seconds)
    end
  rescue
    _ -> 0.0
  end

  # When sample_seconds is 0, use sample-pair API to avoid blocking.
  defp compute_scheduler_usage(0) do
    sample = :scheduler.sample_all()

    sample
    |> :scheduler.utilization()
    |> Enum.map(fn {_, usage, _} -> usage end)
    |> Enum.sum()
    |> Kernel./(System.schedulers_online())
    |> Float.round(2)
  end

  defp compute_scheduler_usage(seconds) when is_integer(seconds) and seconds > 0 do
    seconds
    |> :scheduler.utilization()
    |> Enum.map(fn {_, usage, _} -> usage end)
    |> Enum.sum()
    |> Kernel./(System.schedulers_online())
    |> Float.round(2)
  end

  # Private helper to broadcast to multiple topics
  defp broadcast_to_topics(topics, message, pubsub) do
    Enum.each(topics, fn topic ->
      Phoenix.PubSub.broadcast(pubsub, topic, message)
    end)
  end
end
