import httpx
import respx
from fastapi.testclient import TestClient
from sqlalchemy import func
from sqlalchemy import select
from sqlalchemy.orm import Session

from app import activitypub as ap
from app import models
from app.ap_object import RemoteObject
from tests import factories
from tests.utils import mock_httpsig_checker
from tests.utils import run_process_next_incoming_activity
from tests.utils import setup_remote_actor
from tests.utils import setup_remote_actor_as_following_and_follower


def test_inbox__incoming_delete_for_unknown_actor(
    db: Session,
    client: TestClient,
    respx_mock: respx.MockRouter,
) -> None:
    # Given a remote actor who is already deleted
    ra = factories.RemoteActorFactory(
        base_url="https://deleted.com",
        username="toto",
        public_key="pk",
    )
    respx_mock.get(ra.ap_id).mock(return_value=httpx.Response(404, json=ra.ap_actor))

    # When receiving a Delete activity for an unknown actor
    delete_activity = RemoteObject(
        factories.build_delete_activity(
            from_remote_actor=ra,
            deleted_object_ap_id=ra.ap_id,
        ),
        ra,
    )
    with mock_httpsig_checker(ra, has_valid_signature=False, is_ap_actor_gone=True):
        response = client.post(
            "/inbox",
            headers={"Content-Type": ap.AS_CTX},
            json=delete_activity.ap_object,
        )

    # Then the server returns a 202
    assert response.status_code == 202

    # And no incoming activity was created
    assert db.scalar(select(func.count(models.IncomingActivity.id))) == 0


def test_inbox__incoming_delete_for_known_actor(
    db: Session,
    client: TestClient,
    respx_mock: respx.MockRouter,
) -> None:
    # Given a remote actor
    ra = setup_remote_actor(respx_mock)

    # Which is both followed and a follower
    following, _ = setup_remote_actor_as_following_and_follower(ra)
    actor = following.actor
    assert actor
    assert following.outbox_object

    # TODO: setup few more activities (like announce and create)

    # When receiving a Delete activity for an unknown actor
    delete_activity = RemoteObject(
        factories.build_delete_activity(
            from_remote_actor=ra,
            deleted_object_ap_id=ra.ap_id,
        ),
        ra,
    )

    with mock_httpsig_checker(ra):
        response = client.post(
            "/inbox",
            headers={"Content-Type": ap.AS_CTX},
            json=delete_activity.ap_object,
        )

    # Then the server returns a 202
    assert response.status_code == 202

    run_process_next_incoming_activity()

    # Then every inbox object from the actor was deleted
    assert (
        db.scalar(
            select(func.count(models.InboxObject.id)).where(
                models.InboxObject.actor_id == actor.id,
                models.InboxObject.is_deleted.is_(False),
            )
        )
        == 0
    )

    # And the following actor was deleted
    assert db.scalar(select(func.count(models.Following.id))) == 0

    # And the follower actor was deleted too
    assert db.scalar(select(func.count(models.Follower.id))) == 0

    # And the actor was marked in deleted
    db.refresh(actor)
    assert actor.is_deleted is True