defmodule Mix.Tasks.Bonny.Gen.Manifest do
  @moduledoc """
  Generates the Kubernetes YAML manifest for this operator

  mix bonny.gen.manifest expects a docker image name if deploying to a cluster. You may optionally provide a namespace.

  ## Examples

  The `image` switch is required.

  Options:
  * --image (docker image to deploy)
  * --namespace (of service account and deployment; defaults to "default")
  * --out (path to save manifest; defaults to "manifest.yaml")

  *Deploying to kubernetes:*

  ```shell

  docker build -t $(YOUR_IMAGE_URL) .
  docker push $(YOUR_IMAGE_URL)

  mix bonny.gen.manifest --image $(YOUR_IMAGE_URL):latest --namespace default
  kubectl apply -f manifest.yaml -n default
  ```

  To skip the `deployment` for running an operator outside of the cluster (like in development) simply omit the `--image` flag:

  ```shell
  mix bonny.gen.manifest
  ```
  """

  use Mix.Task
  alias Bonny.Mix.Operator

  @default_opts [namespace: "default", local: false]
  @switches [out: :string, namespace: :string, image: :string, local: :boolean]
  @aliases [o: :out, n: :namespace, i: :image]

  @shortdoc "Generate Kubernetes YAML manifest for this operator"

  @spec run(list()) :: any()
  def run(args) do
    Mix.Task.run("compile")

    {opts, _, _} =
      Mix.Bonny.parse_args(args, @default_opts, switches: @switches, aliases: @aliases)

    manifest =
      opts
      |> resource_manifests()
      |> apply_overrides()
      |> Ymlr.documents!()

    out = opts[:out] || "manifest.yaml"

    Mix.Bonny.render(manifest, out)
  end

  defp resource_manifests(opts) when is_list(opts),
    do: opts |> Enum.into(%{}) |> resource_manifests

  defp resource_manifests(%{local: false, image: image, namespace: namespace}) do
    deployment = Operator.deployment(image, namespace)
    manifests = resource_manifests(%{namespace: namespace})
    [deployment | manifests]
  end

  defp resource_manifests(%{namespace: namespace}) do
    operators = Operator.find_operators()

    Operator.crds(operators) ++
      [
        Operator.cluster_role(operators),
        Operator.service_account(namespace),
        Operator.cluster_role_binding(namespace)
      ]
  end

  defp apply_overrides(manifests) do
    case Application.get_env(:bonny, :manifest_override_callback) do
      callback when is_function(callback, 1) ->
        Enum.map(manifests, callback)

      _ ->
        manifests
    end
  end
end
