import types

import pytest
import tantivy
from django.contrib.admin.sites import AdminSite
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.test import Client
from django.test import TestCase
from pytest_mock import MockerFixture
from rest_framework import status

from documents.admin import DocumentAdmin
from documents.admin import TagAdmin
from documents.models import Document
from documents.models import Tag
from documents.search import get_backend
from documents.search import reset_backend
from documents.tests.factories import DocumentFactory
from documents.tests.factories import TagFactory
from documents.tests.factories import UserFactory
from documents.tests.utils import DirectoriesMixin
from paperless.admin import PaperlessUserAdmin


@pytest.fixture
def tag_admin() -> TagAdmin:
    return TagAdmin(model=Tag, admin_site=AdminSite())


@pytest.fixture
def user_admin() -> PaperlessUserAdmin:
    return PaperlessUserAdmin(model=User, admin_site=AdminSite())


@pytest.fixture
def staff_user(db) -> User:
    return UserFactory.create(username="staff", staff=True)


class TestDocumentAdmin(DirectoriesMixin, TestCase):
    def get_document_from_index(self, doc):
        backend = get_backend()
        searcher = backend._index.searcher()
        results = searcher.search(
            tantivy.Query.term_query(backend._schema, "id", doc.pk),
            limit=1,
        )
        if results.hits:
            return searcher.doc(results.hits[0][1]).to_dict()
        return None

    def setUp(self) -> None:
        super().setUp()
        reset_backend()
        self.doc_admin = DocumentAdmin(model=Document, admin_site=AdminSite())

    def tearDown(self) -> None:
        reset_backend()
        super().tearDown()

    def test_save_model(self) -> None:
        doc = DocumentFactory.create(title="test")

        doc.title = "new title"
        self.doc_admin.save_model(None, doc, None, None)

        self.assertEqual(self.get_document_from_index(doc)["id"], [doc.id])

    def test_delete_model(self) -> None:
        doc = DocumentFactory.create(title="test")
        get_backend().add_or_update(doc)
        self.assertIsNotNone(self.get_document_from_index(doc))

        self.doc_admin.delete_model(None, doc)

        self.assertIsNone(self.get_document_from_index(doc))

    def test_delete_queryset(self) -> None:
        docs = DocumentFactory.create_batch(
            2,
            title="Many documents with the same title",
        )
        for doc in docs:
            get_backend().add_or_update(doc)
            self.assertIsNotNone(self.get_document_from_index(doc))

        self.doc_admin.delete_queryset(None, Document.objects.all())

        for doc in docs:
            self.assertIsNone(self.get_document_from_index(doc))


@pytest.mark.django_db
class TestTagAdmin:
    def test_parent_tags_get_added(
        self,
        tag_admin: TagAdmin,
        mocker: MockerFixture,
    ) -> None:
        mock_bulk_update = mocker.patch(
            "documents.tasks.bulk_update_documents.apply_async",
        )
        document = DocumentFactory.create(title="test")
        parent = TagFactory.create(name="parent")
        child = TagFactory.create(name="child")
        document.tags.add(child)

        child.tn_parent = parent
        tag_admin.save_model(None, child, None, change=True)

        document.refresh_from_db()
        assert parent in document.tags.all()
        mock_bulk_update.assert_called_once()
        assert mock_bulk_update.call_args.kwargs["kwargs"] == {
            "document_ids": [document.id],
        }


@pytest.mark.django_db
class TestPaperlessAdmin:
    def test_request_is_passed_to_form(
        self,
        user_admin: PaperlessUserAdmin,
    ) -> None:
        user = UserFactory.create()
        non_superuser = UserFactory.create()
        request = types.SimpleNamespace(user=non_superuser)
        form_type = user_admin.get_form(request)
        form = form_type(data={}, instance=user)
        assert form.request == request

    def test_non_superuser_cannot_change_superuser_status(
        self,
        user_admin: PaperlessUserAdmin,
    ) -> None:
        non_superuser = UserFactory.create()
        user = UserFactory.create()

        form = user_admin.form(
            {"username": user.username, "is_superuser": True},
            instance=user,
        )
        form.request = types.SimpleNamespace(user=non_superuser)

        assert not form.is_valid()
        assert form.errors.get("__all__") == [
            "Superuser status can only be changed by a superuser",
        ]

    def test_superuser_can_change_superuser_status(
        self,
        user_admin: PaperlessUserAdmin,
        admin_user: User,
    ) -> None:
        user = UserFactory.create()

        form = user_admin.form(
            {"username": user.username, "is_superuser": True},
            instance=user,
        )
        form.request = types.SimpleNamespace(user=admin_user)

        assert form.is_valid()
        assert form.errors == {}

    @pytest.mark.parametrize(
        ("method", "perm_codename", "expected_message"),
        [
            pytest.param(
                "patch",
                "change_user",
                "Superusers can only be modified by other superusers",
                id="modify",
            ),
            pytest.param(
                "delete",
                "delete_user",
                "Superusers can only be deleted by other superusers",
                id="delete",
            ),
        ],
    )
    def test_non_superuser_cannot_mutate_superuser(
        self,
        client: Client,
        admin_user: User,
        staff_user: User,
        method: str,
        perm_codename: str,
        expected_message: str,
    ) -> None:
        staff_user.user_permissions.add(
            Permission.objects.get(codename=perm_codename),
        )
        client.force_login(staff_user)

        response = getattr(client, method)(
            f"/api/users/{admin_user.pk}/",
            {"first_name": "Updated"},
            content_type="application/json",
        )

        assert response.status_code == status.HTTP_403_FORBIDDEN
        assert response.content.decode() == expected_message
        assert User.objects.filter(pk=admin_user.pk).exists()

    def test_superuser_can_modify_superuser(
        self,
        client: Client,
        admin_user: User,
    ) -> None:
        client.force_login(admin_user)
        response = client.patch(
            f"/api/users/{admin_user.pk}/",
            {"first_name": "Updated"},
            content_type="application/json",
        )

        assert response.status_code == status.HTTP_200_OK
        admin_user.refresh_from_db()
        assert admin_user.first_name == "Updated"

    def test_superuser_can_delete_superuser(
        self,
        client: Client,
        admin_user: User,
    ) -> None:
        target = UserFactory.create(superuser=True)
        client.force_login(admin_user)

        response = client.delete(f"/api/users/{target.pk}/")

        assert response.status_code == status.HTTP_204_NO_CONTENT
        assert not User.objects.filter(pk=target.pk).exists()
