"""Tests for FRREP-VL007 — per-document "saved documents" (bookmarks).

The SRS audit found that withdrawing a document only notified the original
submitter, even though users can bookmark an individual document (distinct
from ``SavedSearch``, which tracks saved *queries*). This slice adds
``DocumentBookmark`` + save/unsave endpoints + a "my saved documents" list,
and wires withdrawal to notify bookmarkers too.
"""

from __future__ import annotations

import pytest
from django.contrib.auth import get_user_model
from django.core.files.base import ContentFile
from django.urls import reverse

from apps.core.notifications.models import Notification
from apps.repository.documents import services
from apps.repository.documents.models import Document, DocumentBookmark

User = get_user_model()


def _make_pdf(name: str) -> ContentFile:
    return ContentFile(
        b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n1 0 obj<</Type/Catalog>>endobj\n"
        + name.encode()
        + b"\ntrailer<<>>\n%%EOF\n",
        name=name,
    )


def _make_doc(title, submitter_user, public_collection):
    return services.ingest_document(
        file=_make_pdf(f"{title}.pdf"),
        title=title,
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        abstract="Abstract text.",
        submitted_by=submitter_user,
        actor=submitter_user,
    )


# ── Save / unsave ────────────────────────────────────────────────────────────


@pytest.mark.django_db
def test_save_creates_a_bookmark(client, submitter_user, manager_user, public_collection):
    doc = _make_doc("Bookmark save", submitter_user, public_collection)
    client.force_login(manager_user)

    resp = client.post(reverse("repo_documents:document_save", kwargs={"uid": doc.document_uid}))

    assert resp.status_code == 302
    assert DocumentBookmark.objects.filter(user=manager_user, document=doc).exists()


@pytest.mark.django_db
def test_save_is_idempotent_no_duplicate_on_double_save(
    client, submitter_user, manager_user, public_collection
):
    doc = _make_doc("Bookmark idempotent", submitter_user, public_collection)
    client.force_login(manager_user)
    url = reverse("repo_documents:document_save", kwargs={"uid": doc.document_uid})

    resp1 = client.post(url)
    resp2 = client.post(url)

    assert resp1.status_code == 302
    assert resp2.status_code == 302
    assert DocumentBookmark.objects.filter(user=manager_user, document=doc).count() == 1


@pytest.mark.django_db
def test_unsave_removes_the_bookmark(client, submitter_user, manager_user, public_collection):
    doc = _make_doc("Bookmark unsave", submitter_user, public_collection)
    DocumentBookmark.objects.create(user=manager_user, document=doc)
    client.force_login(manager_user)

    resp = client.post(reverse("repo_documents:document_unsave", kwargs={"uid": doc.document_uid}))

    assert resp.status_code == 302
    assert not DocumentBookmark.objects.filter(user=manager_user, document=doc).exists()


@pytest.mark.django_db
def test_unsave_when_not_bookmarked_does_not_error(
    client, submitter_user, manager_user, public_collection
):
    doc = _make_doc("Bookmark unsave noop", submitter_user, public_collection)
    client.force_login(manager_user)

    resp = client.post(reverse("repo_documents:document_unsave", kwargs={"uid": doc.document_uid}))

    assert resp.status_code == 302
    assert not DocumentBookmark.objects.filter(user=manager_user, document=doc).exists()


@pytest.mark.django_db
def test_save_unsave_requires_login(client, submitter_user, public_collection):
    doc = _make_doc("Bookmark anon", submitter_user, public_collection)

    resp = client.post(reverse("repo_documents:document_save", kwargs={"uid": doc.document_uid}))

    assert resp.status_code == 302
    assert "/accounts/login/" in resp.url
    assert not DocumentBookmark.objects.filter(document=doc).exists()


# ── My saved documents list ──────────────────────────────────────────────────


@pytest.mark.django_db
def test_my_bookmarks_list_only_shows_current_users_bookmarks(
    client, submitter_user, manager_user, public_collection
):
    doc1 = _make_doc("Mine", submitter_user, public_collection)
    doc2 = _make_doc("Someone else's", submitter_user, public_collection)
    DocumentBookmark.objects.create(user=manager_user, document=doc1)
    DocumentBookmark.objects.create(user=submitter_user, document=doc2)

    client.force_login(manager_user)
    resp = client.get(reverse("repo_documents:my_bookmarks"))

    assert resp.status_code == 200
    bookmarks = list(resp.context["bookmarks"])
    assert len(bookmarks) == 1
    assert bookmarks[0].document_id == doc1.pk


# ── Document detail reflects bookmark state ──────────────────────────────────


@pytest.mark.django_db
def test_document_detail_reflects_bookmark_state(
    client, submitter_user, manager_user, public_collection
):
    doc = _make_doc("Detail state", submitter_user, public_collection)
    client.force_login(manager_user)
    url = reverse("repo_documents:document_detail", kwargs={"uid": doc.document_uid})

    resp = client.get(url)
    assert resp.status_code == 200
    assert resp.context["is_bookmarked"] is False

    DocumentBookmark.objects.create(user=manager_user, document=doc)
    resp2 = client.get(url)
    assert resp2.status_code == 200
    assert resp2.context["is_bookmarked"] is True


# ── Withdrawal notifies bookmarkers ──────────────────────────────────────────


@pytest.mark.django_db
def test_withdraw_notifies_bookmarker_distinct_from_submitter(
    submitter_user, manager_user, public_collection
):
    doc = _make_doc("Withdraw notifies bookmarker", submitter_user, public_collection)
    bookmarker = User.objects.create_user(email="bookmarker@test.example", password="pw-test-1234")
    DocumentBookmark.objects.create(user=bookmarker, document=doc)
    Notification.objects.all().delete()

    services.withdraw_document(doc, reason="Superseded.", actor=manager_user)

    doc = Document.objects.get(pk=doc.pk)  # Document.status is a protected FSMField.
    assert doc.status == Document.Status.WITHDRAWN
    assert Notification.objects.filter(
        recipient=submitter_user, verb=Notification.Verb.REPO_DOC_WITHDRAWN
    ).exists()
    assert Notification.objects.filter(
        recipient=bookmarker, verb=Notification.Verb.REPO_SAVED_DOC_WITHDRAWN
    ).exists()


@pytest.mark.django_db
def test_withdraw_does_not_double_notify_when_bookmarker_is_submitter(
    submitter_user, manager_user, public_collection
):
    doc = _make_doc("Withdraw no double notify", submitter_user, public_collection)
    DocumentBookmark.objects.create(user=submitter_user, document=doc)
    Notification.objects.all().delete()

    services.withdraw_document(doc, reason="Superseded.", actor=manager_user)

    assert Notification.objects.filter(
        recipient=submitter_user, verb=Notification.Verb.REPO_DOC_WITHDRAWN
    ).count() == 1
    assert not Notification.objects.filter(
        recipient=submitter_user, verb=Notification.Verb.REPO_SAVED_DOC_WITHDRAWN
    ).exists()


@pytest.mark.django_db
def test_withdraw_does_not_notify_a_user_who_unsaved_the_document(
    submitter_user, manager_user, public_collection
):
    doc = _make_doc("Withdraw unsaved no notify", submitter_user, public_collection)
    bookmarker = User.objects.create_user(email="unsaved@test.example", password="pw-test-1234")
    bookmark = DocumentBookmark.objects.create(user=bookmarker, document=doc)
    bookmark.delete()
    Notification.objects.all().delete()

    services.withdraw_document(doc, reason="Superseded.", actor=manager_user)

    assert not Notification.objects.filter(
        recipient=bookmarker, verb=Notification.Verb.REPO_SAVED_DOC_WITHDRAWN
    ).exists()
