defmodule Boruta.Config do
  @moduledoc """
  Utilities to access Boruta configuration ad set defaults.

  Boruta configuration can be set as following in `config.exs` overriding following default configuration
  ```
  config :boruta, Boruta.Oauth,
    repo: MyApp.Repo, # mandatory
    cache_backend: Boruta.Cache,
    contexts: [
      access_tokens: Boruta.Ecto.AccessTokens,
      agent_tokens: Boruta.Ecto.AgentTokens,
      clients: Boruta.Ecto.Clients,
      codes: Boruta.Ecto.Codes,
      preauthorized_codes: Boruta.Ecto.PreauthorizedCodes,
      resource_owners: MyApp.ResourceOwners, # mandatory for user flows
      scopes: Boruta.Ecto.Scopes,
      requests: Boruta.Ecto.Requests,
      credentials: Boruta.Ecto.Credentials,
      signatures: Boruta.Internal.Signatures
    ],
    max_ttl: [
      authorization_code: 60,
      authorization_request: 60,
      access_token: 60 * 60 * 24,
      agent_token: 60 * 60 * 24 * 30,
      id_token: 60 * 60 * 24,
      refresh_token: 60 * 60 * 24 * 30
    ],
    ebsi_did_resolver_base_url: "https://api-conformance.ebsi.eu/did-registry/v5",
    did_resolver_base_url: "https://api.godiddy.com/1.0.0/universal-resolver",
    did_registrar_base_url: "https://api.godiddy.com/1.0.0/universal-registrar",
    universal_did_auth: %{
      type: "bearer",
      token: DID_API_KEY
    },
    token_generator: Boruta.TokenGenerator,
    issuer: "boruta"
  ```
  > Note: To use the did resolver and registrar services, you must provide a compliant server. Here the default is set to the [Godiddy](https://godiddy.com/) server which require an API key to perform the requests.
  """

  @defaults cache_backend: Boruta.Cache,
            contexts: [
              access_tokens: Boruta.Ecto.AccessTokens,
              agent_tokens: Boruta.Ecto.AgentTokens,
              clients: Boruta.Ecto.Clients,
              codes: Boruta.Ecto.Codes,
              preauthorized_codes: Boruta.Ecto.PreauthorizedCodes,
              resource_owners: nil,
              scopes: Boruta.Ecto.Scopes,
              requests: Boruta.Ecto.Requests,
              credentials: Boruta.Ecto.Credentials,
            ],
            max_ttl: [
              authorization_request: 300,
              authorization_code: 60,
              access_token: 60 * 60 * 24,
              agent_token: 60 * 60 * 24 * 30,
              id_token: 60 * 60 * 24,
              refresh_token: 60 * 60 * 24 * 30
            ],
            ebsi_did_resolver_base_url: "https://api-conformance.ebsi.eu/did-registry/v5",
            did_resolver_base_url: "https://api.godiddy.com/1.0.0/universal-resolver",
            did_registrar_base_url: "https://api.godiddy.com/1.0.0/universal-registrar",
            signature_credentials_base_url: "https://api.godiddy.com/1.0.0/universal-issuer/credentials/issue",
            universal_keys_base_url: "https://api.godiddy.com/0.1.0/wallet-service/keys",
            universal_sign_base_url: "https://api.godiddy.com/0.1.0/wallet-service/keys/sign",
            universal_did_auth: %{
              type: "bearer",
              token: nil
            },
            token_generator: Boruta.TokenGenerator,
            issuer: "boruta"

  @spec repo() :: module()
  @doc false
  def repo do
    Keyword.fetch!(oauth_config(), :repo)
  end

  @spec cache_backend() :: module()
  @doc false
  def cache_backend do
    Keyword.fetch!(oauth_config(), :cache_backend)
  end

  @spec access_token_max_ttl() :: integer()
  @doc false
  def access_token_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:access_token]
  end

  @spec agent_token_max_ttl() :: integer()
  @doc false
  def agent_token_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:agent_token]
  end

  @spec authorization_code_max_ttl() :: integer()
  @doc false
  def authorization_code_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:authorization_code]
  end

  @spec authorization_request_max_ttl() :: integer()
  @doc false
  def authorization_request_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:authorization_request]
  end

  @spec id_token_max_ttl() :: integer()
  @doc false
  def id_token_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:id_token]
  end

  @spec refresh_token_max_ttl() :: integer()
  @doc false
  def refresh_token_max_ttl do
    Keyword.fetch!(oauth_config(), :max_ttl)[:refresh_token]
  end

  @spec token_generator() :: module()
  @doc false
  def token_generator do
    Keyword.fetch!(oauth_config(), :token_generator)
  end

  @spec access_tokens() :: module()
  @doc false
  def access_tokens do
    Keyword.fetch!(oauth_config(), :contexts)[:access_tokens]
  end

  @spec agent_tokens() :: module()
  @doc false
  def agent_tokens do
    Keyword.fetch!(oauth_config(), :contexts)[:agent_tokens]
  end

  @spec clients() :: module()
  @doc false
  def clients do
    Keyword.fetch!(oauth_config(), :contexts)[:clients]
  end

  @spec codes() :: module()
  @doc false
  def codes do
    Keyword.fetch!(oauth_config(), :contexts)[:codes]
  end

  @spec preauthorized_codes() :: module()
  @doc false
  def preauthorized_codes do
    Keyword.fetch!(oauth_config(), :contexts)[:preauthorized_codes]
  end

  @spec scopes() :: module()
  @doc false
  def scopes do
    Keyword.fetch!(oauth_config(), :contexts)[:scopes]
  end

  @spec requests() :: module()
  @doc false
  def requests do
    Keyword.fetch!(oauth_config(), :contexts)[:requests]
  end

  @spec credentials() :: module()
  @doc false
  def credentials do
    Keyword.fetch!(oauth_config(), :contexts)[:credentials]
  end

  @spec resource_owners() :: module()
  @doc false
  def resource_owners do
    case Keyword.fetch!(oauth_config(), :contexts)[:resource_owners] do
      nil ->
        raise """
        Missing configuration for resource_owners context. You can set your own
        `Boruta.Oauth.ResourceOwners` behaviour implementation in config:

          config :boruta, Boruta.Oauth,
            repo: MyApp.Repo,
            contexts: [
              resource_owners: MyApp.ResourceOwners
            ]
        """

      module ->
        module
    end
  end

  @spec ebsi_did_resolver_base_url() :: String.t()
  @doc false
  def ebsi_did_resolver_base_url do
    Keyword.fetch!(oauth_config(), :ebsi_did_resolver_base_url)
  end

  @spec did_resolver_base_url() :: String.t()
  @doc false
  def did_resolver_base_url do
    Keyword.fetch!(oauth_config(), :did_resolver_base_url)
  end

  @spec did_registrar_base_url() :: String.t()
  @doc false
  def did_registrar_base_url do
    Keyword.fetch!(oauth_config(), :did_registrar_base_url)
  end

  @spec signature_credentials_base_url() :: String.t()
  @doc false
  def signature_credentials_base_url do
    Keyword.fetch!(oauth_config(), :signature_credentials_base_url)
  end

  @spec universal_keys_base_url() :: String.t()
  @doc false
  def universal_keys_base_url do
    Keyword.fetch!(oauth_config(), :universal_keys_base_url)
  end

  @spec universal_sign_base_url() :: String.t()
  @doc false
  def universal_sign_base_url do
    Keyword.fetch!(oauth_config(), :universal_sign_base_url)
  end

  @spec universal_did_auth() :: map()
  @doc false
  def universal_did_auth do
    Keyword.fetch!(oauth_config(), :universal_did_auth)
  end

  @spec issuer() :: String.t()
  @doc false
  def issuer do
    Keyword.fetch!(oauth_config(), :issuer)
  end

  @spec oauth_config() :: keyword()
  @doc false
  defp oauth_config do
      Keyword.merge(
        @defaults,
        Application.get_env(:boruta, Boruta.Oauth) || [],
        fn _, a, b ->
          if Keyword.keyword?(a) && Keyword.keyword?(b) do
            Keyword.merge(a, b)
          else
            b
          end
        end
      )
  end
end
