defmodule CentralcloudCore.OnCall do
  @moduledoc """
  Grafana OnCall API client.

  Talks to the Grafana OnCall HTTP API (v1).
  Auth: Authorization: Token <token>
  """

  # ---------------------------------------------------------------------------
  # Alert groups (incidents)

  @spec list_alert_groups(keyword()) :: {:ok, map()} | {:error, term()}
  def list_alert_groups(opts \\ []) do
    params = %{}
    params = if s = opts[:status], do: Map.put(params, :status, s), else: params
    get("/alert_groups/", params)
  end

  @spec get_alert_group(String.t()) :: {:ok, map()} | {:error, term()}
  def get_alert_group(id), do: get("/alert_groups/#{id}/")

  @spec acknowledge_alert_group(String.t()) :: {:ok, map()} | {:error, term()}
  def acknowledge_alert_group(id), do: post("/alert_groups/#{id}/acknowledge/", %{})

  @spec resolve_alert_group(String.t()) :: {:ok, map()} | {:error, term()}
  def resolve_alert_group(id), do: post("/alert_groups/#{id}/resolve/", %{})

  @spec silence_alert_group(String.t(), pos_integer()) :: {:ok, map()} | {:error, term()}
  def silence_alert_group(id, duration_seconds),
    do: post("/alert_groups/#{id}/silence/", %{delay: duration_seconds})

  # ---------------------------------------------------------------------------
  # Schedules

  @spec list_schedules() :: {:ok, map()} | {:error, term()}
  def list_schedules, do: get("/schedules/")

  @spec get_schedule(String.t()) :: {:ok, map()} | {:error, term()}
  def get_schedule(id), do: get("/schedules/#{id}/")

  @spec get_schedule_shifts(String.t()) :: {:ok, map()} | {:error, term()}
  def get_schedule_shifts(id), do: get("/schedules/#{id}/final_shifts/")

  # ---------------------------------------------------------------------------
  # On-call users

  @spec current_oncall(String.t()) :: {:ok, list(map())} | {:error, term()}
  def current_oncall(schedule_id) do
    case get_schedule_shifts(schedule_id) do
      {:ok, %{"results" => shifts}} ->
        now = DateTime.utc_now()

        on_call =
          shifts
          |> Enum.filter(fn s ->
            {:ok, from, _} = DateTime.from_iso8601(s["shift_start"])
            {:ok, to, _}   = DateTime.from_iso8601(s["shift_end"])
            DateTime.compare(from, now) != :gt and DateTime.compare(to, now) == :gt
          end)
          |> Enum.map(& &1["user"])

        {:ok, on_call}

      err ->
        err
    end
  end

  # ---------------------------------------------------------------------------
  # Users

  @spec list_users() :: {:ok, map()} | {:error, term()}
  def list_users, do: get("/users/")

  @spec get_user(String.t()) :: {:ok, map()} | {:error, term()}
  def get_user(id), do: get("/users/#{id}/")

  # ---------------------------------------------------------------------------
  # Escalation chains

  @spec list_escalation_chains() :: {:ok, map()} | {:error, term()}
  def list_escalation_chains, do: get("/escalation_chains/")

  # ---------------------------------------------------------------------------
  # Private

  defp get(path, params \\ %{}) do
    url = base_url() <> path
    headers = auth_headers()

    case Req.get(url, params: params, headers: headers) do
      {:ok, %{status: 200, body: body}} -> {:ok, body}
      {:ok, %{status: status, body: body}} -> {:error, {:http, status, body}}
      {:error, reason} -> {:error, reason}
    end
  end

  defp post(path, body) do
    url = base_url() <> path
    headers = auth_headers()

    case Req.post(url, json: body, headers: headers) do
      {:ok, %{status: s, body: b}} when s in 200..299 -> {:ok, b}
      {:ok, %{status: status, body: body}} -> {:error, {:http, status, body}}
      {:error, reason} -> {:error, reason}
    end
  end

  defp base_url do
    cfg = Application.fetch_env!(:centralcloud_core, :oncall)
    cfg[:base_url] <> "/api/v1"
  end

  defp auth_headers do
    cfg = Application.fetch_env!(:centralcloud_core, :oncall)
    [{"Authorization", to_string(cfg[:token])}]
  end
end
