defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.FollowTest do
  use Mobilizon.DataCase
  import Mox
  import ExUnit.CaptureLog
  import Mobilizon.Factory
  alias Mobilizon.Actors
  alias Mobilizon.Actors.Follower
  alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Relay, Transmogrifier}
  alias Mobilizon.Service.HTTP.ActivityPub.Mock
  alias Mobilizon.Users.User

  import Swoosh.TestAssertions

  describe "handle incoming follow requests" do
    test "it works only for groups" do
      actor = insert(:actor)

      actor_data =
        File.read!("test/fixtures/mastodon-actor.json")
        |> Jason.decode!()

      Mock
      |> expect(:call, fn
        %{method: :get, url: "https://social.tcit.fr/users/tcit"}, _opts ->
          {:ok,
           %Tesla.Env{
             status: 200,
             body: Map.put(actor_data, "id", "https://social.tcit.fr/users/tcit")
           }}
      end)

      data =
        File.read!("test/fixtures/mastodon-follow-activity.json")
        |> Jason.decode!()
        |> Map.put("object", actor.url)

      assert capture_log(fn ->
               :error = Transmogrifier.handle_incoming(data)
             end) =~ "Only group and instances can be followed"

      actor = Actors.get_actor_with_preload(actor.id)
      refute Actors.check_follow(Actors.get_actor_by_url!(data["actor"], true), actor)
    end

    test "it works for incoming follow requests" do
      actor = insert(:group)

      actor_data =
        File.read!("test/fixtures/mastodon-actor.json")
        |> Jason.decode!()

      Mock
      |> expect(:call, 2, fn
        %{method: :get, url: "https://social.tcit.fr/users/tcit"}, _opts ->
          {:ok,
           %Tesla.Env{
             status: 200,
             body: Map.put(actor_data, "id", "https://social.tcit.fr/users/tcit")
           }}

        %{method: :post, url: "https://framapiaf.org/inbox"} = args, _opts ->
          {:ok, args}
      end)

      data =
        File.read!("test/fixtures/mastodon-follow-activity.json")
        |> Jason.decode!()
        |> Map.put("object", actor.url)

      {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)

      assert data["actor"] == "https://social.tcit.fr/users/tcit"
      assert data["type"] == "Follow"
      assert data["id"] == "https://social.tcit.fr/users/tcit#follows/2"

      actor = Actors.get_actor_with_preload(actor.id)
      assert Actors.check_follow(Actors.get_actor_by_url!(data["actor"], true), actor)
    end

    test "it rejects activities without a valid ID" do
      actor = insert(:group)

      data =
        File.read!("test/fixtures/mastodon-follow-activity.json")
        |> Jason.decode!()
        |> Map.put("object", actor.url)
        |> Map.put("id", "")

      :error = Transmogrifier.handle_incoming(data)
    end

    #     test "it works for incoming follow requests from hubzilla" do
    #       user = insert(:user)

    #       data =
    #         File.read!("test/fixtures/hubzilla-follow-activity.json")
    #         |> Jason.decode!()
    #         |> Map.put("object", user.ap_id)
    #         |> Utils.normalize_params()

    #       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)

    #       assert data["actor"] == "https://hubzilla.example.org/channel/kaniini"
    #       assert data["type"] == "Follow"
    #       assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2"
    #       assert User.check_follow(User.get_by_ap_id(data["actor"]), user)
    #     end

    test "it works for accepting instance follow from user" do
      %User{} = insert(:user, email: "loulou@example.com", role: :administrator)
      relay = Relay.get_actor()

      actor_data =
        File.read!("test/fixtures/mastodon-actor.json")
        |> Jason.decode!()

      Mock
      |> expect(:call, fn
        %{method: :get, url: "https://social.tcit.fr/users/tcit"}, _opts ->
          {:ok,
           %Tesla.Env{
             status: 200,
             body: Map.put(actor_data, "id", "https://social.tcit.fr/users/tcit")
           }}
      end)

      data =
        File.read!("test/fixtures/mastodon-follow-activity.json")
        |> Jason.decode!()
        |> Map.put("object", relay.url)

      {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)

      assert data["actor"] == "https://social.tcit.fr/users/tcit"
      assert data["type"] == "Follow"
      assert data["id"] == "https://social.tcit.fr/users/tcit#follows/2"

      follow = Actors.check_follow(Actors.get_actor_by_url!(data["actor"], true), relay)
      assert follow
      refute follow.approved

      refute_email_sent()
    end

    test "it works for accepting instance follow from other instance" do
      %User{email: admin_email} = insert(:user, email: "loulou@example.com", role: :administrator)
      relay = Relay.get_actor()

      actor_data =
        File.read!("test/fixtures/mastodon-actor.json")
        |> Jason.decode!()

      Mock
      |> expect(:call, fn
        %{method: :get, url: "https://mobilizon.fr/relay"}, _opts ->
          {:ok,
           %Tesla.Env{
             status: 200,
             body:
               actor_data
               |> Map.put("id", "https://mobilizon.fr/relay")
               |> Map.put("type", "Application")
           }}
      end)

      data =
        File.read!("test/fixtures/mastodon-follow-activity.json")
        |> Jason.decode!()
        |> Map.put("actor", "https://mobilizon.fr/relay")
        |> Map.put("id", "https://mobilizon.fr/relay#follows/2")
        |> Map.put("object", relay.url)

      {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)

      assert data["actor"] == "https://mobilizon.fr/relay"
      assert data["type"] == "Follow"
      assert data["id"] == "https://mobilizon.fr/relay#follows/2"

      follow = Actors.check_follow(Actors.get_actor_by_url!(data["actor"], true), relay)
      assert follow
      refute follow.approved

      assert_email_sent(to: admin_email)
    end
  end

  describe "handle incoming follow accept activities" do
    test "it works for incoming accepts" do
      follower = insert(:actor)
      followed = insert(:group, manually_approves_followers: false)

      refute Actors.check_follow(follower, followed)

      {:ok, follow_activity, _} = Actions.Follow.follow(follower, followed)
      assert Actors.check_follow(follower, followed)

      follow_object_id = follow_activity.data["id"]

      assert %Follower{} = Actors.get_follower_by_url(follow_object_id)

      accept_data =
        File.read!("test/fixtures/mastodon-accept-activity.json")
        |> Jason.decode!()
        |> Map.put("actor", followed.url)

      object =
        accept_data["object"]
        |> Map.put("actor", follower.url)
        |> Map.put("id", follow_object_id)

      accept_data = Map.put(accept_data, "object", object)

      :error = Transmogrifier.handle_incoming(accept_data)

      {:ok, follower} = Actors.get_actor_by_url(follower.url)

      assert Actors.check_follow(follower, followed)
    end

    test "it works for incoming accepts which were pre-accepted" do
      follower = insert(:actor)
      followed = insert(:group, manually_approves_followers: true)

      refute Actors.check_follow(follower, followed)

      {:ok, follow_activity, _} = Actions.Follow.follow(follower, followed)
      assert Actors.check_follow(follower, followed)

      follow_object_id = follow_activity.data["id"]

      assert %Follower{} = Actors.get_follower_by_url(follow_object_id)

      accept_data =
        File.read!("test/fixtures/mastodon-accept-activity.json")
        |> Jason.decode!()
        |> Map.put("actor", followed.url)

      object =
        accept_data["object"]
        |> Map.put("actor", follower.url)
        |> Map.put("id", follow_object_id)

      accept_data = Map.put(accept_data, "object", object)

      {:ok, activity, _} = Transmogrifier.handle_incoming(accept_data)
      refute activity.local

      assert activity.data["object"]["id"] == follow_activity.data["id"]

      {:ok, follower} = Actors.get_actor_by_url(follower.url)

      assert Actors.check_follow(follower, followed)
    end

    test "it works for incoming accepts which are referenced by IRI only" do
      follower = insert(:actor)
      followed = insert(:group, manually_approves_followers: true)

      {:ok, follow_activity, _} = Actions.Follow.follow(follower, followed)

      accept_data =
        File.read!("test/fixtures/mastodon-accept-activity.json")
        |> Jason.decode!()
        |> Map.put("actor", followed.url)
        |> Map.put("object", follow_activity.data["id"])

      {:ok, activity, _} = Transmogrifier.handle_incoming(accept_data)
      assert activity.data["object"]["id"] == follow_activity.data["id"]
      assert activity.data["object"]["id"] =~ "/follow/"
      assert activity.data["id"] =~ "/accept/follow/"

      {:ok, follower} = Actors.get_actor_by_url(follower.url)

      assert Actors.check_follow(follower, followed)
    end

    test "it fails for incoming accepts which cannot be correlated" do
      follower = insert(:actor)
      followed = insert(:group)

      accept_data =
        File.read!("test/fixtures/mastodon-accept-activity.json")
        |> Jason.decode!()
        |> Map.put("actor", followed.url)

      accept_data =
        Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.url))

      :error = Transmogrifier.handle_incoming(accept_data)

      {:ok, follower} = Actors.get_actor_by_url(follower.url)

      refute Actors.check_follow(follower, followed)
    end
  end

  describe "handle incoming follow reject activities" do
    test "it fails for incoming rejects which cannot be correlated" do
      follower = insert(:actor)
      followed = insert(:group)

      accept_data =
        File.read!("test/fixtures/mastodon-reject-activity.json")
        |> Jason.decode!()
        |> Map.put("actor", followed.url)

      accept_data =
        Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.url))

      :error = Transmogrifier.handle_incoming(accept_data)

      {:ok, follower} = Actors.get_actor_by_url(follower.url)

      refute Actors.check_follow(follower, followed)
    end

    test "it works for incoming rejects which are referenced by IRI only" do
      follower = insert(:actor)
      followed = insert(:group)

      {:ok, follow_activity, _} = Actions.Follow.follow(follower, followed)

      assert Actors.check_follow(follower, followed)

      reject_data =
        File.read!("test/fixtures/mastodon-reject-activity.json")
        |> Jason.decode!()
        |> Map.put("actor", followed.url)
        |> Map.put("object", follow_activity.data["id"])

      {:ok, %Activity{data: _}, _} = Transmogrifier.handle_incoming(reject_data)

      refute Actors.check_follow(follower, followed)
    end
  end
end