defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
  @moduledoc """
  Task to display an actor details
  """
  use Mix.Task
  alias Mobilizon.Actors.Actor
  alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
  alias Mobilizon.Storage.Repo
  import Ecto.Query
  import Mix.Tasks.Mobilizon.Common
  require Logger

  @shortdoc "Refresh an actor or all actors"

  @impl Mix.Task
  def run(["--all" | options]) do
    {options, [], []} =
      OptionParser.parse(
        options,
        strict: [
          verbose: :boolean
        ],
        aliases: [
          v: :verbose
        ]
      )

    verbose = Keyword.get(options, :verbose, false)

    start_mobilizon()

    total = count_actors()

    shell_info("""
    #{total} actors to process
    """)

    query = from(a in Actor, where: not is_nil(a.domain) and not a.suspended)

    {:ok, _res} =
      Repo.transaction(
        fn ->
          query
          |> Repo.stream(timeout: :infinity)
          |> Stream.map(&"#{&1.preferred_username}@#{&1.domain}")
          |> Stream.each(
            if verbose,
              do: &Logger.info("Processing #{inspect(&1)}"),
              else: &Logger.debug("Processing #{inspect(&1)}")
          )
          |> Stream.map(fn username -> make_actor(username, verbose) end)
          |> Stream.scan(0, fn _, acc -> acc + 1 end)
          |> Stream.each(fn index ->
            if verbose,
              do: Logger.info("#{index}/#{total}"),
              else: ProgressBar.render(index, total)
          end)
          |> Stream.run()
        end,
        timeout: :infinity
      )
  end

  @impl Mix.Task
  def run([preferred_username]) do
    start_mobilizon()

    case ActivityPubActor.make_actor_from_nickname(preferred_username) do
      {:ok, %Actor{}} ->
        shell_info("""
        Actor #{preferred_username} refreshed
        """)

      {:error, _err} ->
        shell_error("Error while refreshing actor #{preferred_username}")
    end
  end

  @impl Mix.Task
  def run(_) do
    shell_error("mobilizon.actors.refresh requires an username as argument or --all as an option")
  end

  @spec make_actor(String.t(), boolean()) :: any()
  defp make_actor(username, verbose) do
    ActivityPubActor.make_actor_from_nickname(username)
  rescue
    _ ->
      if verbose do
        Logger.warn("Failed to refresh #{username}")
      end

      nil
  end

  defp count_actors do
    Repo.aggregate(from(a in Actor, where: not is_nil(a.domain)), :count)
  end
end