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

  alias Mobilizon.{Actors, Discussions}
  alias Mobilizon.Actors.Actor
  alias Mobilizon.Discussions.{Comment, Discussion}
  alias Mobilizon.Federation.ActivityPub.Actions
  alias Mobilizon.GraphQL.API.Comments
  alias Mobilizon.Storage.Page
  alias Mobilizon.Users.User
  import Mobilizon.Web.Gettext

  @spec find_discussions_for_actor(Actor.t(), map(), Absinthe.Resolution.t()) ::
          {:ok, Page.t(Discussion.t())} | {:error, :unauthenticated}
  def find_discussions_for_actor(
        %Actor{id: group_id},
        %{page: page, limit: limit},
        %{
          context: %{
            current_actor: %Actor{id: actor_id}
          }
        }
      ) do
    with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
         {:ok, %Actor{type: :Group} = group} <- Actors.get_group_by_actor_id(group_id) do
      {:ok, Discussions.find_discussions_for_actor(group, page, limit)}
    else
      {:member, false} ->
        {:ok, %Page{total: 0, elements: []}}
    end
  end

  def find_discussions_for_actor(%Actor{}, _args, _resolution) do
    {:ok, %Page{total: 0, elements: []}}
  end

  @spec get_discussion(any(), map(), Absinthe.Resolution.t()) ::
          {:ok, Discussion.t()} | {:error, :unauthorized | :discussion_not_found | String.t()}
  def get_discussion(_parent, %{id: id}, %{
        context: %{
          current_actor: %Actor{id: creator_id}
        }
      }) do
    case Discussions.get_discussion(id) do
      %Discussion{actor_id: actor_id} = discussion ->
        if Actors.is_member?(creator_id, actor_id) do
          {:ok, discussion}
        else
          {:error, :unauthorized}
        end

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

  def get_discussion(_parent, %{slug: slug}, %{
        context: %{
          current_actor: %Actor{id: creator_id}
        }
      }) do
    with %Discussion{actor_id: actor_id} = discussion <-
           Discussions.get_discussion_by_slug(slug),
         {:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do
      {:ok, discussion}
    else
      nil -> {:error, dgettext("errors", "Discussion not found")}
      {:member, false} -> {:error, :unauthorized}
    end
  end

  def get_discussion(_parent, _args, %{
        context: %{
          current_user: %User{} = _user
        }
      }),
      do:
        {:error,
         dgettext("errors", "You must provide either an ID or a slug to access a discussion")}

  def get_discussion(_parent, _args, _resolution),
    do: {:error, dgettext("errors", "You need to be logged-in to access discussions")}

  @spec get_comments_for_discussion(Discussion.t(), map(), Absinthe.Resolution.t()) ::
          {:ok, Page.t(Discussion.t())}
  def get_comments_for_discussion(
        %Discussion{id: discussion_id},
        %{page: page, limit: limit},
        _resolution
      ) do
    {:ok, Discussions.get_comments_for_discussion(discussion_id, page, limit)}
  end

  @spec create_discussion(any(), map(), Absinthe.Resolution.t()) ::
          {:ok, Discussion.t()}
          | {:error, Ecto.Changeset.t() | String.t() | :unauthorized | :unauthenticated}
  def create_discussion(
        _parent,
        %{title: title, text: text, actor_id: group_id},
        %{
          context: %{
            current_actor: %Actor{id: creator_id}
          }
        }
      ) do
    if Actors.is_member?(creator_id, group_id) do
      case Comments.create_discussion(%{
             title: title,
             text: text,
             actor_id: group_id,
             creator_id: creator_id,
             attributed_to_id: group_id
           }) do
        {:ok, _activity, %Discussion{} = discussion} ->
          {:ok, discussion}

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

        {:error, _err} ->
          {:error, dgettext("errors", "Error while creating a discussion")}
      end
    else
      {:error, :unauthorized}
    end
  end

  def create_discussion(_, _, _), do: {:error, :unauthenticated}

  @spec reply_to_discussion(any(), map(), Absinthe.Resolution.t()) ::
          {:ok, Discussion.t()} | {:error, :discussion_not_found | :unauthenticated}
  def reply_to_discussion(
        _parent,
        %{text: text, discussion_id: discussion_id},
        %{
          context: %{
            current_actor: %Actor{id: creator_id}
          }
        }
      ) do
    with {:no_discussion,
          %Discussion{
            actor_id: actor_id,
            last_comment: %Comment{
              id: last_comment_id,
              origin_comment_id: origin_comment_id,
              in_reply_to_comment_id: previous_in_reply_to_comment_id
            }
          } = _discussion} <-
           {:no_discussion, Discussions.get_discussion(discussion_id)},
         {:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
         {:ok, _activity, %Discussion{} = discussion} <-
           Comments.create_discussion(%{
             text: text,
             discussion_id: discussion_id,
             actor_id: creator_id,
             attributed_to_id: actor_id,
             in_reply_to_comment_id: last_comment_id,
             origin_comment_id:
               origin_comment_id || previous_in_reply_to_comment_id || last_comment_id
           }) do
      {:ok, discussion}
    else
      {:no_discussion, _} ->
        {:error, :discussion_not_found}
    end
  end

  def reply_to_discussion(_, _, _), do: {:error, :unauthenticated}

  @spec update_discussion(map(), map(), map()) ::
          {:ok, Discussion.t()} | {:error, :unauthorized | :unauthenticated}
  def update_discussion(
        _parent,
        %{title: title, discussion_id: discussion_id},
        %{
          context: %{
            current_actor: %Actor{id: creator_id}
          }
        }
      ) do
    with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
           {:no_discussion, Discussions.get_discussion(discussion_id)},
         {:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
         {:ok, _activity, %Discussion{} = discussion} <-
           Actions.Update.update(
             discussion,
             %{
               title: title
             }
           ) do
      {:ok, discussion}
    else
      {:member, false} ->
        {:error, :unauthorized}
    end
  end

  def update_discussion(_, _, _), do: {:error, :unauthenticated}

  @spec delete_discussion(any(), map(), Absinthe.Resolution.t()) ::
          {:ok, Discussion.t()} | {:error, String.t() | :unauthorized | :unauthenticated}
  def delete_discussion(_parent, %{discussion_id: discussion_id}, %{
        context: %{
          current_user: %User{},
          current_actor: %Actor{id: creator_id} = actor
        }
      }) do
    with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
           {:no_discussion, Discussions.get_discussion(discussion_id)},
         {:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
         {:ok, _activity, %Discussion{} = discussion} <-
           Actions.Delete.delete(discussion, actor) do
      {:ok, discussion}
    else
      {:no_discussion, _} ->
        {:error, dgettext("errors", "No discussion with ID %{id}", id: discussion_id)}

      {:member, _} ->
        {:error, :unauthorized}
    end
  end

  def delete_discussion(_, _, _), do: {:error, :unauthenticated}
end
