defmodule Kraken.Api.Services do
  alias Kraken.Services

  def define(payload) do
    with {:ok, definition} <- Jason.decode(payload),
         {:ok, service_name} <-
           (case definition do
              definition when is_map(definition) ->
                Services.define(definition)

              definitions when is_list(definitions) ->
                define_many(definitions)
            end) do
      {:ok, Jason.encode!(%{"ok" => service_name})}
    else
      {:error, error} ->
        {:error, Jason.encode!(%{"error" => inspect(error)})}
    end
  end

  def services() do
    services = Services.services()
    {:ok, Jason.encode!(services)}
  end

  def status(service_name) do
    status = Services.status(service_name)
    {:ok, Jason.encode!(%{"status" => inspect(status)})}
  end

  def definition(service_name) do
    case Services.definition(service_name) do
      {:ok, definition} ->
        {:ok, Jason.encode!(definition)}

      {:error, error} ->
        {:error, Jason.encode!(%{"error" => inspect(error)})}
    end
  end

  def state(service_name) do
    case Services.state(service_name) do
      {:ok, definition} ->
        {:ok, Jason.encode!(definition)}

      {:error, error} ->
        {:error, Jason.encode!(%{"error" => inspect(error)})}
    end
  end

  def call(service_name, function_name, payload) do
    with {:ok, args} <- Jason.decode(payload),
         {:ok, result} <- Services.call(service_name, function_name, args) do
      {:ok, Jason.encode!(%{"ok" => result})}
    else
      {:error, error} ->
        {:error, Jason.encode!(%{"error" => inspect(error)})}
    end
  end

  def start(service_name), do: start(service_name, "{}")
  def start(service_name, ""), do: start(service_name, "{}")

  def start(service_name, payload) do
    with {:ok, args} <- Jason.decode(payload),
         {:ok, state} <- Services.start(service_name, args) do
      {:ok, Jason.encode!(%{"ok" => state})}
    else
      {:error, error} ->
        {:error, Jason.encode!(%{"error" => inspect(error)})}
    end
  end

  def stop(service_name), do: stop(service_name, "{}")
  def stop(service_name, ""), do: stop(service_name, "{}")

  def stop(service_name, payload) do
    with {:ok, args} <- Jason.decode(payload),
         :ok <- Services.stop(service_name, args) do
      {:ok, Jason.encode!(%{"ok" => "ok"})}
    else
      {:error, error} ->
        {:error, Jason.encode!(%{"error" => inspect(error)})}
    end
  end

  def delete(service_name) do
    case Services.delete(service_name) do
      :ok ->
        {:ok, Jason.encode!(%{"ok" => "ok"})}

      {:error, error} ->
        {:error, Jason.encode!(%{"error" => inspect(error)})}
    end
  end

  defp define_many(definitions) do
    try do
      names =
        definitions
        |> Enum.reduce([], fn definition, acc ->
          case Services.define(definition) do
            {:ok, service_name} ->
              [service_name | acc]

            {:error, error} ->
              throw({:error, error})
          end
        end)
        |> Enum.reverse()

      {:ok, names}
    catch
      {:error, error} ->
        {:error, error}
    end
  end
end
