defmodule TaskBunny.Status do
  @moduledoc false
  # Functions that handle TaskBunny status.
  #
  # This module is private to TaskBunny.
  # It's aimed at providing useful stats to Wobserver integration.
  #
  alias TaskBunny.{Status, Connection}
  alias TaskBunny.Status.Worker

  @typedoc ~S"""
  The Worker status contains the follow fields:
    - `version`, the current status version.
    - `up`, whether the `TaskBunny.Supervisor` is running.
    - `connected`, whether a connection to RabbitMQ has been made.
    - `workers`, list of worker statuses.
  """
  @type t :: %__MODULE__{
          version: String.t(),
          up: boolean,
          connected: boolean,
          workers: list(Worker.t())
        }

  defstruct version: "1", up: true, connected: false, workers: []

  @doc ~S"""
  Returns TaskBunny status.
  """
  @spec overview(atom) :: Status.t()
  def overview(supervisor \\ TaskBunny.Supervisor) do
    case supervisor_alive?(supervisor) do
      true ->
        %Status{
          up: true,
          connected: get_connection_status(),
          workers: get_workers_status(supervisor)
        }

      _ ->
        %Status{
          up: false,
          connected: get_connection_status()
        }
    end
  end

  @doc ~S"""
  Returns TaskBunny metrics.
  """
  @spec metrics(atom) :: keyword
  def metrics(supervisor \\ TaskBunny.Supervisor) do
    case supervisor_alive?(supervisor) do
      true ->
        {runners, succeeded, failed, rejected} =
          supervisor
          |> get_workers_status()
          |> Enum.reduce({[], [], [], []}, &get_worker_metrics/2)

        [
          task_bunny_job_runners_now: {runners, :gauge, "Current amount of TaskBunny runners."},
          task_bunny_jobs_succeeded_total:
            {succeeded, :counter, "The amount of succeeded TaskBunny jobs."},
          task_bunny_jobs_failed_total:
            {failed, :counter, "The amount of failed TaskBunny jobs."},
          task_bunny_jobs_rejected_total:
            {rejected, :counter, "The amount of rejected TaskBunny jobs."}
        ]

      false ->
        []
    end
  end

  @doc ~S"""
  Returns TaskBunny `:wobserver` page.
  """
  @spec page(atom) :: map
  def page(supervisor \\ TaskBunny.Supervisor) do
    page = %{
      status: %{
        running: supervisor_alive?(supervisor),
        connected: get_connection_status()
      }
    }

    case page.status.running do
      true ->
        page
        |> Map.put_new(:workers, get_workers_page(supervisor))

      false ->
        page
    end
  end

  @spec get_connection_status() :: boolean
  defp get_connection_status do
    case Connection.get_connection() do
      {:ok, _} -> true
      _ -> false
    end
  end

  @spec supervisor_alive?(atom) :: boolean
  defp supervisor_alive?(supervisor) do
    case Process.whereis(supervisor) do
      nil -> false
      pid -> Process.alive?(pid)
    end
  end

  @spec get_workers_status(atom) :: list(Worker.t())
  defp get_workers_status(supervisor) do
    supervisor
    |> Supervisor.which_children()
    |> Enum.map(&get_worker_supervisor_status/1)
    |> List.flatten()
  end

  @spec get_workers_status({TaskBunny.WorkerSupervisor, pid, atom, list()}) :: list(Worker.t())
  defp get_worker_supervisor_status({TaskBunny.WorkerSupervisor, pid, _atom, _list}) do
    pid
    |> Supervisor.which_children()
    |> Enum.map(&Status.Worker.get/1)
  end

  @spec get_worker_supervisor_status(any) :: []
  defp get_worker_supervisor_status(_) do
    []
  end

  @spec get_workers_page(atom) :: list(map)
  defp get_workers_page(supervisor) do
    supervisor
    |> get_workers_status()
    |> Enum.map(fn worker ->
      %{
        queue: worker.queue,
        runners: worker.runners,
        channel: worker.channel,
        succeeded: worker.stats.succeeded,
        failed: worker.stats.failed,
        rejected: worker.stats.rejected
      }
    end)
  end

  @spec get_worker_metrics(Worker.t(), {list, list, list, list}) :: {list, list, list, list}
  defp get_worker_metrics(worker, {runners, succeeded, failed, rejected}) do
    {
      [{worker.runners, [queue: worker.queue]} | runners],
      [{worker.stats.succeeded, [queue: worker.queue]} | succeeded],
      [{worker.stats.failed, [queue: worker.queue]} | failed],
      [{worker.stats.rejected, [queue: worker.queue]} | rejected]
    }
  end
end
