defmodule HexpmWeb.API.KeyController do
  use HexpmWeb, :controller

  plug :fetch_organization

  plug :authorize,
       [
         domains: [{"api", "write"}],
         allow_unconfirmed: true,
         fun: {AuthHelpers, :organization_access, [organization_role: "write"]}
       ]
       when action == :create

  plug :authorize,
       [
         domains: [{"api", "write"}],
         fun: {AuthHelpers, :organization_access, [organization_role: "write"]},
         authentication: :required
       ]
       when action in [:delete, :delete_all]

  plug :authorize,
       [
         domains: [{"api", "read"}],
         authentication: :required,
         fun: {AuthHelpers, :organization_access}
       ]
       when action in [:index, :show]

  plug :require_organization_path

  def index(conn, _params) do
    user_or_organization = conn.assigns.organization || conn.assigns.current_user
    authing_key = conn.assigns.auth_credential
    keys = Keys.all(user_or_organization)

    conn
    |> api_cache(:private)
    |> render(:index, keys: keys, authing_key: authing_key)
  end

  def show(conn, %{"name" => name}) do
    user_or_organization = conn.assigns.organization || conn.assigns.current_user
    authing_key = conn.assigns.auth_credential
    key = Keys.get(user_or_organization, name)

    if key do
      when_stale(conn, key, fn conn ->
        conn
        |> api_cache(:private)
        |> render(:show, key: key, authing_key: authing_key)
      end)
    else
      not_found(conn)
    end
  end

  def create(conn, params) do
    user_or_organization = conn.assigns.organization || conn.assigns.current_user
    authing_key = conn.assigns.auth_credential

    case Keys.create(user_or_organization, params, audit: audit_data(conn)) do
      {:ok, %{key: key}} ->
        location = ~p"/api/keys/#{key}"

        conn
        |> put_resp_header("location", location)
        |> api_cache(:private)
        |> put_status(201)
        |> render(:show, key: key, authing_key: authing_key)

      {:error, :key, changeset, _} ->
        validation_failed(conn, changeset)
    end
  end

  def delete(conn, %{"name" => name}) do
    user_or_organization = conn.assigns.organization || conn.assigns.current_user
    authing_key = conn.assigns.auth_credential

    case Keys.revoke(user_or_organization, name, audit: audit_data(conn)) do
      {:ok, %{key: key}} ->
        conn
        |> api_cache(:private)
        |> put_status(200)
        |> render(:delete, key: key, authing_key: authing_key)

      _ ->
        not_found(conn)
    end
  end

  def delete_all(conn, _params) do
    user_or_organization = conn.assigns.organization || conn.assigns.current_user
    authing_key = conn.assigns.auth_credential
    {:ok, _} = Keys.revoke_all(user_or_organization, audit: audit_data(conn))

    conn
    |> put_status(200)
    |> render(:delete, key: Keys.get(authing_key.id), authing_key: authing_key)
  end

  defp require_organization_path(conn, _opts) do
    if conn.assigns.current_organization && !conn.assigns.organization do
      not_found(conn)
    else
      conn
    end
  end
end
