defmodule Mobilizon.GraphQL.Resolvers.Application do
  @moduledoc """
  Handles the Application-related GraphQL calls.
  """

  alias Mobilizon.Applications, as: ApplicationManager
  alias Mobilizon.Applications.{Application, ApplicationDeviceActivation, ApplicationToken}
  alias Mobilizon.GraphQL.Error
  alias Mobilizon.Service.Auth.Applications
  alias Mobilizon.Users.User
  import Mobilizon.Web.Gettext, only: [dgettext: 2]

  require Logger

  @doc """
  Authorize an application
  """
  @spec authorize(any(), map(), Absinthe.Resolution.t()) :: {:ok, map()} | {:error, String.t()}
  def authorize(
        _parent,
        %{client_id: client_id, redirect_uri: redirect_uri, scope: scope} = args,
        %{context: %{current_user: %User{id: user_id}}}
      ) do
    case Applications.autorize(client_id, redirect_uri, scope, user_id) do
      {:ok,
       %ApplicationToken{
         application: %Application{client_id: client_id},
         scope: scope,
         authorization_code: code
       }} ->
        {:ok, %{code: code, state: Map.get(args, :state), client_id: client_id, scope: scope}}

      {:error, %Ecto.Changeset{} = err} ->
        {:error, err}

      {:error, :application_not_found} ->
        {:error,
         dgettext(
           "errors",
           "No application with this client_id was found"
         )}

      {:error, :redirect_uri_not_in_allowed} ->
        {:error,
         dgettext(
           "errors",
           "The given redirect_uri is not in the list of allowed redirect URIs"
         )}
    end
  end

  def authorize(_parent, _args, _context) do
    {:error, :unauthenticated}
  end

  @spec get_application(any(), map(), Absinthe.Resolution.t()) ::
          {:ok, Application.t()} | {:error, :application_not_found | :unauthenticated}
  def get_application(_parent, %{client_id: client_id}, %{context: %{current_user: %User{}}}) do
    case ApplicationManager.get_application_by_client_id(client_id) do
      %Application{} = application ->
        {:ok, application}

      nil ->
        {:error, :application_not_found}
    end
  end

  def get_application(_parent, _args, _resolution) do
    {:error, :unauthenticated}
  end

  def get_user_applications(_parent, _args, %{context: %{current_user: %User{id: user_id}}}) do
    {:ok, ApplicationManager.list_application_tokens_for_user_id(user_id)}
  end

  def get_user_applications(_parent, _args, _resolution) do
    {:error, :unauthenticated}
  end

  def revoke_application_token(_parent, %{app_token_id: app_token_id}, %{
        context: %{current_user: %User{id: user_id}}
      }) do
    case ApplicationManager.get_application_token(app_token_id) do
      %ApplicationToken{user_id: ^user_id} = app_token ->
        case Applications.revoke_application_token(app_token) do
          {:ok, %{delete_app_token: app_token, delete_guardian_tokens: _delete_guardian_tokens}} ->
            {:ok, %{id: app_token.id}}

          {:error, _, _, _} ->
            {:error, dgettext("errors", "Error while revoking token")}
        end

      _ ->
        {:error, :application_token_not_found}
    end
  end

  def revoke_application_token(_parent, _args, _resolution) do
    {:error, :unauthenticated}
  end

  def activate_device(_parent, %{user_code: user_code}, %{
        context: %{current_user: %User{} = user}
      }) do
    case Applications.activate_device(user_code, user) do
      {:ok, %ApplicationDeviceActivation{} = app_device_activation} ->
        {:ok, app_device_activation |> Map.from_struct() |> Map.take([:application, :id, :scope])}

      {:error, :expired} ->
        {:error,
         %Error{
           message: dgettext("errors", "The given user code has expired"),
           status_code: 400,
           code: :device_application_code_expired
         }}

      {:error, :not_found} ->
        {:error, dgettext("errors", "The given user code is invalid")}
    end
  end

  def activate_device(_parent, _args, _resolution) do
    {:error, :unauthenticated}
  end

  @spec authorize_device_application(any(), map(), Absinthe.Resolution.t()) ::
          {:ok, map()} | {:error, String.t()}
  def authorize_device_application(
        _parent,
        %{client_id: client_id, user_code: user_code},
        %{context: %{current_user: %User{}}}
      ) do
    case Applications.autorize_device_application(client_id, user_code) do
      {:ok, %ApplicationDeviceActivation{application: app}} ->
        {:ok, app}

      {:error, :not_confirmed} ->
        {:error,
         dgettext(
           "errors",
           "The device user code was not provided before approving the application"
         )}

      {:error, :not_found} ->
        {:error,
         dgettext(
           "errors",
           "The given user code is invalid"
         )}

      {:error, :expired} ->
        {:error,
         %Error{
           message: dgettext("errors", "The given user code has expired"),
           status_code: 400,
           code: :device_application_code_expired
         }}
    end
  end

  def authorize_device_application(_parent, _args, _resolution) do
    {:error, :unauthenticated}
  end
end