defmodule Mix.Tasks.Mobilizon.Actors.Delete do
  @moduledoc """
  Task to delete an actor
  """
  use Mix.Task
  alias Mobilizon.{Actors, Users}
  alias Mobilizon.Actors.Actor
  alias Mobilizon.Service.ActorSuspension
  alias Mobilizon.Users.User
  import Mix.Tasks.Mobilizon.Common

  @shortdoc "Deletes a Mobilizon person or a group"

  @impl Mix.Task
  def run([federated_username | rest]) do
    {options, [], []} =
      OptionParser.parse(
        rest,
        strict: [
          assume_yes: :boolean,
          keep_username: :boolean
        ],
        aliases: [
          y: :assume_yes,
          k: :keep_username
        ]
      )

    assume_yes? = Keyword.get(options, :assume_yes, false)
    keep_username? = Keyword.get(options, :keep_username, false)

    start_mobilizon()

    # To make sure we can delete actors created by mistake with "@" in their username
    case Actors.get_local_actor_by_name(federated_username) ||
           Actors.get_actor_by_name(federated_username) do
      %Actor{preferred_username: username, domain: nil} when username in ["relay", "anonymous"] ->
        shell_error("This actor can't be deleted.")

      %Actor{} = actor ->
        if check_everything(actor, assume_yes?) do
          ActorSuspension.suspend_actor(actor,
            reserve_username: keep_username?,
            suspension: false
          )

          display_name = Actor.display_name_and_username(actor)

          shell_info("""
          The actor #{display_name} has been deleted
          """)
        else
          shell_error("Actor has not been deleted.")
        end

      nil ->
        shell_error("No actor found with this username")
    end
  end

  def run(_) do
    shell_error(
      "mobilizon.actors.delete requires an username or a federated username as argument"
    )
  end

  @spec check_everything(Actor.t(), boolean()) :: boolean()
  defp check_everything(%Actor{} = actor, assume_yes?) do
    display_name = Actor.display_name_and_username(actor)

    (assume_yes? or
       shell_yes?(
         "All content by this profile or group will be deleted. Continue with deleting #{display_name}?"
       )) and
      check_actor(actor, assume_yes?)
  end

  @spec check_actor(Actor.t(), boolean()) :: boolean()
  defp check_actor(%Actor{type: :Group} = group, assume_yes?) do
    display_name = Actor.display_name_and_username(group)
    nb_members = Actors.count_members_for_group(group)
    nb_followers = Actors.count_followers_for_actor(group)

    if nb_followers + nb_members > 0 do
      shell_info("Group members will be notified of the group deletion.")

      assume_yes? or
        shell_yes?(
          "Group #{display_name} has #{nb_members} members and #{nb_followers} followers. Continue deleting?"
        )
    else
      true
    end
  end

  defp check_actor(%Actor{type: :Person, domain: nil, user_id: user_id} = profile, assume_yes?)
       when not is_nil(user_id) do
    %User{actors: actors, email: email} = Users.get_user_with_actors!(profile.user_id)

    if length(actors) == 1 do
      assume_yes? or
        shell_yes?(
          "This profile is the only one user #{email} has. Mobilizon will invite the user to create a new profile on their next login. If you want to remove the whole user account, use the `mobilizon.users.delete` command. Continue deleting?"
        )
    else
      true
    end
  end

  defp check_actor(%Actor{} = _actor, assume_yes?), do: assume_yes?
end