defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
  @moduledoc false
  alias Mobilizon.{Actors, Posts, Tombstone}
  alias Mobilizon.Actors.Actor
  alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
  alias Mobilizon.Federation.ActivityPub.Types.Entity
  alias Mobilizon.Federation.ActivityStream
  alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
  alias Mobilizon.Federation.ActivityStream.Convertible
  alias Mobilizon.Posts.Post
  alias Mobilizon.Service.Activity.Post, as: PostsActivity
  alias Mobilizon.Service.LanguageDetection
  require Logger
  import Mobilizon.Federation.ActivityPub.Utils, only: [make_create_data: 2, make_update_data: 2]

  @behaviour Entity

  @public_ap "https://www.w3.org/ns/activitystreams#Public"

  @impl Entity
  @spec create(map(), map()) :: {:ok, Post.t(), ActivityStream.t()}
  def create(args, additional) do
    with args <- prepare_args(args),
         {:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
           Posts.create_post(args),
         {:ok, _} <- PostsActivity.insert_activity(post, subject: "post_created"),
         {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
         %Actor{} = creator <- Actors.get_actor(creator_id),
         post_as_data <-
           Convertible.model_to_as(%{post | attributed_to: group, author: creator}) do
      create_data = make_create_data(post_as_data, additional)

      {:ok, post, create_data}
    else
      err ->
        Logger.debug(inspect(err))
        err
    end
  end

  @impl Entity
  @spec update(Post.t(), map(), map()) :: {:ok, Post.t(), ActivityStream.t()}
  def update(%Post{} = post, args, additional) do
    with args <- prepare_args(args),
         {:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
           Posts.update_post(post, args),
         {:ok, _} <- PostsActivity.insert_activity(post, subject: "post_updated"),
         {:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}"),
         {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
         %Actor{} = creator <- Actors.get_actor(creator_id),
         post_as_data <-
           Convertible.model_to_as(%{post | attributed_to: group, author: creator}),
         audience <-
           Audience.get_audience(post) do
      update_data = make_update_data(post_as_data, Map.merge(audience, additional))

      {:ok, post, update_data}
    else
      err ->
        Logger.debug(inspect(err))
        err
    end
  end

  @impl Entity
  @spec delete(Post.t(), Actor.t(), boolean, map) ::
          {:ok, ActivityStream.t(), Actor.t(), Post.t()}
  def delete(
        %Post{
          url: url,
          attributed_to: %Actor{url: group_url, members_url: members_url}
        } = post,
        %Actor{url: actor_url} = actor,
        _local,
        _additionnal
      ) do
    activity_data = %{
      "actor" => actor_url,
      "type" => "Delete",
      "object" => %{
        "type" => "Tombstone",
        "formerType" => "Article",
        "deleted" => DateTime.utc_now(),
        "id" => url
      },
      "id" => url <> "/delete",
      "to" => [group_url, @public_ap, members_url]
    }

    with {:ok, %Post{} = post} <- Posts.delete_post(post),
         {:ok, _} <- PostsActivity.insert_activity(post, subject: "post_deleted"),
         {:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}"),
         {:ok, %Tombstone{} = _tombstone} <-
           Tombstone.create_tombstone(%{uri: post.url, actor_id: actor.id}) do
      {:ok, activity_data, actor, post}
    end
  end

  @spec actor(Post.t()) :: Actor.t() | nil
  def actor(%Post{author_id: author_id}),
    do: Actors.get_actor(author_id)

  @spec group_actor(Post.t()) :: Actor.t() | nil
  def group_actor(%Post{attributed_to_id: attributed_to_id}),
    do: Actors.get_actor(attributed_to_id)

  @spec permissions(Post.t()) :: Permission.t()
  def permissions(%Post{}) do
    %Permission{
      access: :member,
      create: :moderator,
      update: :moderator,
      delete: :moderator
    }
  end

  @spec prepare_args(map()) :: map
  defp prepare_args(args) do
    args
    |> Map.update(:tags, [], &ConverterUtils.fetch_tags/1)
    |> Map.put_new(:language, "und")
    |> Map.update!(:language, fn lang ->
      if lang == "und", do: LanguageDetection.detect(:post, args), else: lang
    end)
  end
end