defmodule Parrhesia.API.Admin do
  @moduledoc """
  Public management API facade.

  This module exposes the DX-friendly control plane for administrative tasks. It wraps
  storage-backed management methods and a set of built-in helpers for ACL, identity, sync,
  and listener management.

  `execute/3` accepts the same method names used by NIP-86 style management endpoints, while
  the dedicated functions (`stats/1`, `health/1`, `list_audit_logs/1`) are easier to call
  from Elixir code.
  """

  alias Parrhesia.API.ACL
  alias Parrhesia.API.Identity
  alias Parrhesia.API.Sync
  alias Parrhesia.Storage
  alias Parrhesia.Web.Endpoint

  @supported_admin_methods ~w(health list_audit_logs stats)
  @supported_acl_methods ~w(acl_grant acl_revoke acl_list)
  @supported_identity_methods ~w(identity_ensure identity_get identity_import identity_rotate)
  @supported_listener_methods ~w(listener_reload)
  @supported_sync_methods ~w(
    sync_get_server
    sync_health
    sync_list_servers
    sync_put_server
    sync_remove_server
    sync_server_stats
    sync_start_server
    sync_stats
    sync_stop_server
    sync_sync_now
  )

  @doc """
  Executes a management method by name.

  Built-in methods include:

  - `supportedmethods`
  - `stats`
  - `health`
  - `list_audit_logs`
  - `acl_grant`, `acl_revoke`, `acl_list`
  - `identity_get`, `identity_ensure`, `identity_import`, `identity_rotate`
  - `listener_reload`
  - `sync_*`

  Unknown methods are delegated to the configured `Parrhesia.Storage.Admin` implementation.
  """
  @spec execute(String.t() | atom(), map(), keyword()) :: {:ok, map()} | {:error, term()}
  def execute(method, params, opts \\ [])

  def execute(method, params, opts) when is_map(params) do
    method_name = normalize_method_name(method)

    case execute_builtin(method_name, params, opts) do
      {:continue, other_method} -> Storage.admin().execute(%{}, other_method, params)
      result -> result
    end
  end

  def execute(method, _params, _opts),
    do: {:error, {:unsupported_method, normalize_method_name(method)}}

  @doc """
  Returns aggregate relay stats plus nested sync stats.
  """
  @spec stats(keyword()) :: {:ok, map()} | {:error, term()}
  def stats(opts \\ []) do
    with {:ok, relay_stats} <- relay_stats(),
         {:ok, sync_stats} <- Sync.sync_stats(opts) do
      {:ok, Map.put(relay_stats, "sync", sync_stats)}
    end
  end

  @doc """
  Returns the overall management health payload.

  The top-level `"status"` is currently derived from sync health, while relay-specific health
  details remain delegated to storage-backed management methods.
  """
  @spec health(keyword()) :: {:ok, map()} | {:error, term()}
  def health(opts \\ []) do
    with {:ok, sync_health} <- Sync.sync_health(opts) do
      {:ok,
       %{
         "status" => overall_health_status(sync_health),
         "sync" => sync_health
       }}
    end
  end

  @doc """
  Lists persisted audit log entries from the configured admin storage backend.

  Supported options are storage-adapter specific. The built-in admin execution path forwards
  `:limit`, `:method`, and `:actor_pubkey`.
  """
  @spec list_audit_logs(keyword()) :: {:ok, [map()]} | {:error, term()}
  def list_audit_logs(opts \\ []) do
    Storage.admin().list_audit_logs(%{}, opts)
  end

  defp acl_grant(params) do
    with :ok <- ACL.grant(params) do
      {:ok, %{"ok" => true}}
    end
  end

  defp acl_revoke(params) do
    with :ok <- ACL.revoke(params) do
      {:ok, %{"ok" => true}}
    end
  end

  defp acl_list(params) do
    with {:ok, rules} <- ACL.list(acl_list_opts(params)) do
      {:ok, %{"rules" => rules}}
    end
  end

  defp acl_list_opts(params) do
    []
    |> maybe_put_opt(:principal_type, fetch_value(params, :principal_type))
    |> maybe_put_opt(:principal, fetch_value(params, :principal))
    |> maybe_put_opt(:capability, fetch_value(params, :capability))
  end

  defp supported_methods do
    storage_supported =
      case Storage.admin().execute(%{}, :supportedmethods, %{}) do
        {:ok, methods} when is_list(methods) -> methods
        {:ok, %{"methods" => methods}} when is_list(methods) -> methods
        _other -> []
      end

    (storage_supported ++
       @supported_admin_methods ++
       @supported_acl_methods ++
       @supported_identity_methods ++ @supported_listener_methods ++ @supported_sync_methods)
    |> Enum.uniq()
    |> Enum.sort()
  end

  defp identity_get(_params), do: Identity.get()

  defp identity_ensure(_params), do: Identity.ensure()

  defp identity_rotate(_params), do: Identity.rotate()

  defp identity_import(params) do
    Identity.import(params)
  end

  defp admin_stats(_params, opts), do: stats(opts)
  defp admin_health(_params, opts), do: health(opts)

  defp admin_list_audit_logs(params, _opts) do
    list_audit_logs(audit_log_opts(params))
  end

  defp listener_reload(params) do
    case normalize_listener_id(fetch_value(params, :id)) do
      :all ->
        Endpoint.reload_all()
        |> ok_result()

      {:ok, listener_id} ->
        listener_id
        |> Endpoint.reload_listener()
        |> ok_result()

      :error ->
        {:error, :not_found}
    end
  end

  defp sync_put_server(params, opts), do: Sync.put_server(params, opts)

  defp sync_remove_server(params, opts) do
    with {:ok, server_id} <- fetch_required_string(params, :id),
         :ok <- Sync.remove_server(server_id, opts) do
      {:ok, %{"ok" => true}}
    end
  end

  defp sync_get_server(params, opts) do
    with {:ok, server_id} <- fetch_required_string(params, :id),
         {:ok, server} <- Sync.get_server(server_id, opts) do
      {:ok, server}
    else
      :error -> {:error, :not_found}
      other -> other
    end
  end

  defp sync_list_servers(_params, opts), do: Sync.list_servers(opts)

  defp sync_start_server(params, opts) do
    with {:ok, server_id} <- fetch_required_string(params, :id),
         :ok <- Sync.start_server(server_id, opts) do
      {:ok, %{"ok" => true}}
    end
  end

  defp sync_stop_server(params, opts) do
    with {:ok, server_id} <- fetch_required_string(params, :id),
         :ok <- Sync.stop_server(server_id, opts) do
      {:ok, %{"ok" => true}}
    end
  end

  defp sync_sync_now(params, opts) do
    with {:ok, server_id} <- fetch_required_string(params, :id),
         :ok <- Sync.sync_now(server_id, opts) do
      {:ok, %{"ok" => true}}
    end
  end

  defp sync_server_stats(params, opts) do
    with {:ok, server_id} <- fetch_required_string(params, :id),
         {:ok, stats} <- Sync.server_stats(server_id, opts) do
      {:ok, stats}
    else
      :error -> {:error, :not_found}
      other -> other
    end
  end

  defp sync_stats(_params, opts), do: Sync.sync_stats(opts)
  defp sync_health(_params, opts), do: Sync.sync_health(opts)

  defp execute_builtin("stats", params, opts), do: admin_stats(params, opts)
  defp execute_builtin("health", params, opts), do: admin_health(params, opts)
  defp execute_builtin("list_audit_logs", params, opts), do: admin_list_audit_logs(params, opts)
  defp execute_builtin("acl_grant", params, _opts), do: acl_grant(params)
  defp execute_builtin("acl_revoke", params, _opts), do: acl_revoke(params)
  defp execute_builtin("acl_list", params, _opts), do: acl_list(params)
  defp execute_builtin("identity_get", params, _opts), do: identity_get(params)
  defp execute_builtin("identity_ensure", params, _opts), do: identity_ensure(params)
  defp execute_builtin("identity_import", params, _opts), do: identity_import(params)
  defp execute_builtin("identity_rotate", params, _opts), do: identity_rotate(params)
  defp execute_builtin("listener_reload", params, _opts), do: listener_reload(params)
  defp execute_builtin("sync_put_server", params, opts), do: sync_put_server(params, opts)
  defp execute_builtin("sync_remove_server", params, opts), do: sync_remove_server(params, opts)
  defp execute_builtin("sync_get_server", params, opts), do: sync_get_server(params, opts)
  defp execute_builtin("sync_list_servers", params, opts), do: sync_list_servers(params, opts)
  defp execute_builtin("sync_start_server", params, opts), do: sync_start_server(params, opts)
  defp execute_builtin("sync_stop_server", params, opts), do: sync_stop_server(params, opts)
  defp execute_builtin("sync_sync_now", params, opts), do: sync_sync_now(params, opts)
  defp execute_builtin("sync_server_stats", params, opts), do: sync_server_stats(params, opts)
  defp execute_builtin("sync_stats", params, opts), do: sync_stats(params, opts)
  defp execute_builtin("sync_health", params, opts), do: sync_health(params, opts)

  defp execute_builtin("supportedmethods", _params, _opts),
    do: {:ok, %{"methods" => supported_methods()}}

  defp execute_builtin(other_method, _params, _opts), do: {:continue, other_method}

  defp relay_stats do
    case Storage.admin().execute(%{}, :stats, %{}) do
      {:ok, stats} when is_map(stats) -> {:ok, stats}
      {:error, {:unsupported_method, _method}} -> {:ok, %{}}
      other -> other
    end
  end

  defp overall_health_status(%{"status" => "degraded"}), do: "degraded"
  defp overall_health_status(_sync_health), do: "ok"

  defp audit_log_opts(params) do
    []
    |> maybe_put_opt(:limit, fetch_value(params, :limit))
    |> maybe_put_opt(:method, fetch_value(params, :method))
    |> maybe_put_opt(:actor_pubkey, fetch_value(params, :actor_pubkey))
  end

  defp maybe_put_opt(opts, _key, nil), do: opts
  defp maybe_put_opt(opts, key, value), do: Keyword.put(opts, key, value)

  defp ok_result(:ok), do: {:ok, %{"ok" => true}}
  defp ok_result({:error, _reason} = error), do: error
  defp ok_result(other), do: other

  defp normalize_listener_id(nil), do: :all

  defp normalize_listener_id(listener_id) when is_atom(listener_id) do
    {:ok, listener_id}
  end

  defp normalize_listener_id(listener_id) when is_binary(listener_id) do
    case Supervisor.which_children(Endpoint) do
      children when is_list(children) ->
        Enum.find_value(children, :error, &match_listener_child(&1, listener_id))

      _other ->
        :error
    end
  end

  defp normalize_listener_id(_listener_id), do: :error

  defp match_listener_child({{:listener, id}, _pid, _type, _modules}, listener_id) do
    normalized_id = Atom.to_string(id)
    if normalized_id == listener_id, do: {:ok, id}, else: false
  end

  defp match_listener_child(_child, _listener_id), do: false

  defp fetch_required_string(map, key) do
    case fetch_value(map, key) do
      value when is_binary(value) and value != "" -> {:ok, value}
      _other -> {:error, {:missing_param, Atom.to_string(key)}}
    end
  end

  defp fetch_value(map, key), do: Map.get(map, key) || Map.get(map, Atom.to_string(key))

  defp normalize_method_name(method) when is_atom(method), do: Atom.to_string(method)
  defp normalize_method_name(method) when is_binary(method), do: method
  defp normalize_method_name(method), do: inspect(method)
end
