defmodule Mobilizon.Service.Workers.ActivityBuilder do
  @moduledoc """
  Worker to insert activity items in users feeds
  """

  alias Mobilizon.{Activities, Actors, Users}
  alias Mobilizon.Activities.Activity
  alias Mobilizon.Actors.Actor
  alias Mobilizon.Service.Notifier
  alias Mobilizon.Users.User

  use Mobilizon.Service.Workers.Helper, queue: "activity"

  @impl Oban.Worker
  @spec perform(Job.t()) :: {:ok, Activity.t()} | {:error, Ecto.Changeset.t()}
  def perform(%Job{args: args}) do
    {"build_activity", args} = Map.pop(args, "op")

    case build_activity(args) do
      {:ok, %Activity{} = activity} ->
        activity
        |> Activities.preload_activity()
        |> notify_activity()

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

  @spec build_activity(map()) :: {:ok, Activity.t()} | {:error, Ecto.Changeset.t()}
  def build_activity(args) do
    Activities.create_activity(args)
  end

  @spec notify_activity(Activity.t()) :: :ok
  def notify_activity(%Activity{} = activity) do
    activity
    |> users_to_notify()
    |> Enum.each(&Notifier.notify(&1, activity, single_activity: true))
  end

  @spec users_to_notify(Activity.t()) :: list(User.t())
  defp users_to_notify(%Activity{group: %Actor{type: :Group} = group, author_id: author_id}) do
    group
    |> Actors.list_internal_actors_members_for_group([
      :creator,
      :administrator,
      :moderator,
      :member
    ])
    |> Enum.filter(&(&1.id != author_id))
    |> Enum.map(& &1.user_id)
    |> Enum.filter(& &1)
    |> Enum.uniq()
    |> Enum.map(&Users.get_user_with_activity_settings!/1)
  end

  defp users_to_notify(_), do: []
end