defmodule Phoenix.Sync.Application do
  @moduledoc false

  use Application

  require Logger

  @default_adapter Phoenix.Sync.Electric

  @impl true
  def start(_type, _args) do
    base_children = [Phoenix.Sync.Shape.Supervisor, Phoenix.Sync.ShapeRequestRegistry]

    # Electric is noisy by default which isn't helpful when developing or in
    # production so up its log level to info (unless otherwise configured using
    # the `ELECTRIC_LOG_LEVEL` env var.
    Logger.put_application_level(:electric, electric_log_level())

    children =
      case children() do
        {:ok, children} ->
          children

        {:error, reason} ->
          Logger.warning(reason)
          []
      end

    Supervisor.start_link(base_children ++ children,
      strategy: :one_for_one,
      name: Phoenix.Sync.Supervisor
    )
  end

  @doc false
  def config do
    Application.get_all_env(:phoenix_sync)
  end

  @doc false
  def adapter do
    config() |> adapter()
  end

  @doc false
  def adapter(opts) do
    Keyword.get(opts, :adapter, @default_adapter)
  end

  @doc false
  def children do
    config() |> children()
  end

  @doc false
  def children(opts) when is_list(opts) do
    warn_missing_env(opts)

    {adapter, env} = adapter_env(opts)

    apply(adapter, :children, [env, opts])
  end

  @doc false
  def adapter_env(opts) do
    {
      adapter(),
      Keyword.get(opts, :env, :prod)
    }
  end

  @spec plug_opts() :: keyword()
  def plug_opts do
    config() |> plug_opts()
  end

  @doc false
  def plug_opts(opts) when is_list(opts) do
    {adapter, env} = adapter_env(opts)

    apply(adapter, :plug_opts, [env, opts])
  end

  @doc false
  def fetch_with_error(opts, key) do
    case Keyword.fetch(opts, key) do
      {:ok, url} -> {:ok, url}
      :error -> {:error, "Missing required key #{inspect(key)}"}
    end
  end

  defp warn_missing_env(config) do
    if config[:mode] != :disabled && !config[:env] do
      Logger.warning("""
      No `env` specified for :phoenix_sync: defaulting to `:prod`.

      Add the following to your config:

      config :phoenix_sync,
        env: config_env(),
        # the rest of your config

      In `:prod` mode, shapes are persisted between server restarts
      which may cause problems in `:dev` or `:test` environments.
      """)
    end

    config
  end

  defp electric_log_level do
    valid_levels = Enum.map(Logger.levels(), &to_string/1)
    configured_level = System.get_env("ELECTRIC_LOG_LEVEL", "info")

    if configured_level in valid_levels do
      String.to_existing_atom(configured_level)
    else
      Logger.warning("Invalid ELECTRIC_LOG_LEVEL: #{configured_level}. Defaulting to :info.")
      :info
    end
  end
end
