defmodule Bonny.Config do
  @moduledoc """
  Operator configuration interface
  """

  @project_name Mix.Project.config()
                |> Keyword.fetch!(:app)
                |> Atom.to_string()
                |> String.replace("_", "-")

  defp project_name(), do: @project_name

  @doc """
  Kubernetes API Group of this operator
  """
  @spec group() :: binary
  def group() do
    Application.get_env(:bonny, :group) || "#{project_name()}.example.com"
  end

  @doc """
  Kubernetes API Versions of this operator
  """
  @spec versions() :: binary
  def versions() do
    Application.get_env(:bonny, :versions) ||
      raise RuntimeError,
        message:
          "You have to define a list of versions in your application config under .bonny.versions"
  end

  @doc """
  The name of the operator.

  Name must consist of only lowercase letters and hyphens.

  Defaults to hyphenated mix project app name. E.g.: `:hello_operator` becomes `hello-operator`
  """
  @spec name() :: binary
  def name() do
    dns_safe_name(
      System.get_env("BONNY_OEPRATOR_NAME") ||
        Application.get_env(:bonny, :operator_name) ||
        project_name()
    )
  end

  @doc """
  The name of the operator instance.

  This is set via environment variable `BONNY_POD_NAME`
  """
  @spec instance_name() :: binary
  def instance_name() do
    System.get_env("BONNY_POD_NAME") || name()
  end

  @doc """
  Kubernetes service account name to run operator as.

  *Note:* if a kube config file is provided, this service account will still be created
  and assigned to pods, but the *config file auth will be used* when making requests to the Kube API.

  Name must consist of only lowercase letters and hyphens.

  Defaults to hyphenated mix project app name. E.g.: `:hello_operator` becomes `hello-operator`
  """
  @spec service_account() :: binary
  def service_account() do
    :bonny
    |> Application.get_env(:service_account_name, project_name())
    |> dns_safe_name
  end

  defp dns_safe_name(str) do
    str
    |> String.downcase()
    |> String.replace(~r/[^a-z-]/, "-\\1\\g{1}")
  end

  @doc """
  Labels to apply to all operator resources.

  *Note:* These are only applied to the resources that compose the operator itself,
  not the resources created by the operator.

  This can be set in config.exs:

  ```
  config :bonny, labels: %{foo: "bar", quz: "baz"}
  ```

  """
  @spec labels() :: map()
  def labels() do
    Application.get_env(:bonny, :labels, %{})
  end

  @doc """
  List of all controller modules to watch.

  This *must* be set in config.exs:

  ```
  config :bonny, controllers: [MyController1, MyController2]
  ```
  """
  @spec controllers() :: list(atom)
  def controllers() do
    Application.get_env(:bonny, :controllers, [])
  end

  @doc """
  The namespace to watch for `Namespaced` CRDs.

  Defaults to `default`

  This can be set via environment variable:

  ```shell
  BONNY_POD_NAMESPACE=prod # specific namespace
  # or
  BONNY_POD_NAMESPACE=__ALL__ # all namespaces
  iex -S mix
  ```

  Or via config.exs:
  ```
  config :bonny, namespace: "mynamespace" # specific namespace
  # or
  config :bonny; namespace: :all # all namespaces
  ```

  Configuration via environment variable always takes precedence over config.exs.

  Bonny sets `BONNY_POD_NAMESPACE` on all Kubernetes deployments to the namespace the operator is deployed in.
  """
  @spec namespace() :: binary
  def namespace() do
    case System.get_env("BONNY_POD_NAMESPACE") do
      nil -> Application.get_env(:bonny, :namespace, "default")
      "__ALL__" -> :all
      namespace -> namespace
    end
  end

  @doc """
  Kubernetes APIVersion used. Defaults to `apiextensions.k8s.io/v1`
  """
  @spec api_version() :: binary
  def api_version() do
    Application.get_env(:bonny, :api_version, "apiextensions.k8s.io/v1beta1")
  end

  @doc """
  `K8s.Conn` name used for this operator.
  """
  @spec conn() :: K8s.Conn.t()
  def conn() do
    get_conn = Application.get_env(:bonny, :get_conn)

    case apply_get_conn(get_conn) do
      {:ok, %K8s.Conn{} = conn} ->
        conn

      %K8s.Conn{} = conn ->
        conn

      _ ->
        raise("""
        Check bonny.get_conn in your config.exs. get_conn must be a tuple in the form {Module, :function, [args]}
        which defines a function returning {:ok, K8s.Conn.t()}. Given: #{inspect(get_conn)}
        """)
    end
  end

  defp apply_get_conn({module, function, args}), do: apply(module, function, args)
  defp apply_get_conn({module, function}), do: apply(module, function, [])
  defp apply_get_conn(_), do: :error
end
