defmodule Bolt.Sips.ConnectionSupervisor do
  @moduledoc false

  use DynamicSupervisor

  alias Bolt.Sips.Protocol
  alias Bolt.Sips

  require Logger
  @name __MODULE__

  def start_link(init_args) do
    DynamicSupervisor.start_link(__MODULE__, init_args, name: @name)
  end

  @impl true
  def init(_args) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end

  @doc """
  the resulting connection name i.e. "write@localhost:7687" will be used to spawn new
  DBConnection processes, and for finding available connections
  """
  def start_child(role, url, config) do
    prefix = Keyword.get(config, :prefix, :default)

    connection_name = "#{prefix}_#{role}@#{url}"

    role_config =
      config
      |> Keyword.put(:role, role)
      |> Keyword.put(:name, via_tuple(connection_name))

    spec = %{
      id: connection_name,
      start: {DBConnection, :start_link, [Protocol, role_config]},
      type: :worker,
      restart: :transient,
      shutdown: 500
    }

    # [Protocol, role_config];
    with {:error, :not_found} <- find_connection(connection_name),
         {:ok, _pid} = r <- DynamicSupervisor.start_child(@name, spec) do
      [spec, r]

      r
    else
      {:ok, pid, _info} -> {:ok, pid}
      {:error, {:already_started, pid}} -> {:ok, pid}
      pid -> {:ok, pid}
    end
  end

  @spec find_connection(atom, String.t(), atom) :: {:error, :not_found} | {:ok, pid()}
  def find_connection(role, url, prefix), do: find_connection("#{prefix}_#{role}@#{url}")

  @spec find_connection(any()) :: {:error, :not_found} | {:ok, pid()}
  def find_connection(name) do
    case Registry.lookup(Sips.registry_name(), name) do
      [{pid, _}] -> {:ok, pid}
      _ -> {:error, :not_found}
    end
  end

  @spec terminate_connection(atom, String.t(), atom) :: {:error, :not_found} | {:ok, pid()}
  def terminate_connection(role, url, prefix \\ :default) do
    with {:ok, pid} = r <- find_connection(role, url, prefix),
         true <- Process.exit(pid, :normal) do
      connections()
      r
    end
  end

  def connections() do
    _connections()
    |> Enum.map(fn pid ->
      Sips.registry_name()
      |> Registry.keys(pid)
      |> List.first()
    end)
  end

  defp _connections() do
    __MODULE__
    |> DynamicSupervisor.which_children()
    |> Enum.filter(fn {_, pid, _type, modules} ->
      case {pid, modules} do
        {:restarting, _} -> false
        {_pid, _} -> true
      end
    end)
    |> Enum.map(fn {_, pid, _, _} ->
      pid
    end)
  end

  def via_tuple(name) do
    {:via, Registry, {Bolt.Sips.registry_name(), name}}
  end
end
