defmodule Mobilizon.GraphQL.API.Reports do
  @moduledoc """
  API for Reports.
  """

  alias Mobilizon.Actors.Actor
  alias Mobilizon.{Admin, Users}
  alias Mobilizon.Federation.ActivityPub.{Actions, Activity}
  alias Mobilizon.Reports, as: ReportsAction
  alias Mobilizon.Reports.{Note, Report, ReportStatus}
  alias Mobilizon.Service.Akismet
  alias Mobilizon.Users.User
  import Mobilizon.Web.Gettext, only: [dgettext: 2]
  require Logger

  @doc """
  Create a report/flag on an actor, and optionally on an event or on comments.
  """
  @spec report(map()) :: {:ok, Activity.t(), Report.t()} | {:error, Ecto.Changeset.t()}
  def report(args) do
    Actions.Flag.flag(args, Map.get(args, :forward, false) == true)
  end

  @doc """
  Update the status of a report
  """
  @spec update_report_status(Actor.t(), Report.t(), atom(), atom() | nil) ::
          {:ok, Report.t()} | {:error, Ecto.Changeset.t() | String.t()}
  def update_report_status(
        %Actor{} = actor,
        %Report{status: old_status} = report,
        status,
        antispam_feedback \\ nil
      ) do
    if ReportStatus.valid_value?(status) do
      with {:ok, %Report{} = report} <-
             ReportsAction.update_report(report, %{
               "status" => status,
               "antispam_feedback" => antispam_feedback
             }) do
        if old_status != status do
          Admin.log_action(actor, "update", report)
        end

        antispam_response =
          case antispam_feedback do
            :ham ->
              Logger.debug("Reporting a report details as ham")
              Akismet.report_ham(report)

            :spam ->
              Logger.debug("Reporting a report details as spam")
              Akismet.report_spam(report)

            _ ->
              :ok
          end

        if antispam_response != :ok do
          Logger.warn("Antispam response has been #{inspect(antispam_response)}")
        end

        {:ok, report}
      end
    else
      {:error, dgettext("errors", "Unsupported status for a report")}
    end
  end

  @doc """
  Create a note on a report
  """
  @spec create_report_note(Report.t(), Actor.t(), String.t()) ::
          {:ok, Note.t()} | {:error, String.t() | Ecto.Changeset.t()}
  def create_report_note(
        %Report{id: report_id},
        %Actor{id: moderator_id, user_id: user_id} = moderator,
        content
      ) do
    %User{role: role} = Users.get_user!(user_id)

    if role in [:administrator, :moderator] do
      with {:ok, %Note{} = note} <-
             Mobilizon.Reports.create_note(%{
               "report_id" => report_id,
               "moderator_id" => moderator_id,
               "content" => content
             }),
           {:ok, _} <- Admin.log_action(moderator, "create", note) do
        {:ok, note}
      end
    else
      {:error,
       dgettext(
         "errors",
         "You need to be a moderator or an administrator to create a note on a report"
       )}
    end
  end

  @doc """
  Delete a report note
  """
  @spec delete_report_note(Note.t(), Actor.t()) ::
          {:ok, Note.t()} | {:error, Ecto.Changeset.t() | String.t()}
  def delete_report_note(
        %Note{moderator_id: note_moderator_id} = note,
        %Actor{id: moderator_id, user_id: user_id} = moderator
      ) do
    if note_moderator_id == moderator_id do
      %User{role: role} = Users.get_user!(user_id)

      if role in [:administrator, :moderator] do
        with {:ok, %Note{} = note} <-
               Mobilizon.Reports.delete_note(note),
             {:ok, _} <- Admin.log_action(moderator, "delete", note) do
          {:ok, note}
        end
      else
        {:error,
         dgettext(
           "errors",
           "You need to be a moderator or an administrator to create a note on a report"
         )}
      end
    else
      {:error, dgettext("errors", "You can only remove your own notes")}
    end
  end
end
