"""Tests for the two SRS gaps closed in this slice:

* FRREP-QA003 — structured 6-criterion QA checklist, required (server-side)
  before a document can be approved; informational only for revision/reject.
* FRREP-COL004 — comment thread resolve/reopen action + notification.
"""

from __future__ import annotations

import pytest
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
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, DocumentComment, QARecord

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 _submit_doc(title, submitter_user, public_collection):
    doc = 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,
    )
    services.submit_for_qa(doc, actor=submitter_user)
    return doc


# ── FRREP-QA003 — structured checklist ──────────────────────────────────────


@pytest.mark.django_db
def test_qa003_approve_blocked_until_checklist_fully_checked(
    submitter_user, manager_user, public_collection
):
    doc = _submit_doc("QA003 incomplete", submitter_user, public_collection)
    qa = doc.qa_records.first()

    incomplete = services.full_qa_checklist()
    incomplete["no_copyright_issue"] = False

    with pytest.raises(ValidationError):
        services.decide_qa(qa, decision="approve", actor=manager_user, checklist=incomplete)

    qa.refresh_from_db()
    doc = Document.objects.get(pk=doc.pk)  # Document.status is a protected FSMField — refresh_from_db() can't set it.
    assert qa.decision == QARecord.Decision.PENDING
    assert doc.status == Document.Status.UNDER_QA


@pytest.mark.django_db
def test_qa003_approve_succeeds_when_checklist_fully_checked(
    submitter_user, manager_user, public_collection
):
    doc = _submit_doc("QA003 complete", submitter_user, public_collection)
    qa = doc.qa_records.first()

    services.decide_qa(
        qa, decision="approve", actor=manager_user, checklist=services.full_qa_checklist()
    )

    qa.refresh_from_db()
    doc = Document.objects.get(pk=doc.pk)  # Document.status is a protected FSMField — refresh_from_db() can't set it.
    assert qa.decision == QARecord.Decision.APPROVE
    assert qa.checklist_fully_passed is True
    assert doc.status == Document.Status.PUBLISHED


@pytest.mark.django_db
def test_qa003_revision_and_reject_not_blocked_by_incomplete_checklist(
    submitter_user, manager_user, public_collection
):
    # Revision — reviewer can flag missing metadata as the *reason* for the
    # bounce; the checklist stays informational, not a gate.
    doc = _submit_doc("QA003 revision", submitter_user, public_collection)
    qa = doc.qa_records.first()
    partial = {"relevance": True, "oa_compliance": False}
    services.decide_qa(
        qa, decision="revision", comments="Please fix metadata.", actor=manager_user, checklist=partial
    )
    qa.refresh_from_db()
    assert qa.decision == QARecord.Decision.REVISION
    assert qa.checklist == {
        "relevance": True,
        "oa_compliance": False,
        "metadata_complete": False,
        "abstract_quality": False,
        "no_copyright_issue": False,
        "no_duplication": False,
    }
    assert qa.checklist_fully_passed is False

    # Reject — same rule: rejecting *because* of a checklist failure (e.g.
    # copyright) must not itself be blocked by that failure.
    doc2 = _submit_doc("QA003 reject", submitter_user, public_collection)
    qa2 = doc2.qa_records.first()
    services.decide_qa(
        qa2,
        decision="reject",
        comments="Copyright violation.",
        actor=manager_user,
        checklist={"no_copyright_issue": False},
    )
    qa2.refresh_from_db()
    assert qa2.decision == QARecord.Decision.REJECT


@pytest.mark.django_db
def test_qa003_checklist_items_render_all_six_criteria(submitter_user, manager_user, public_collection):
    doc = _submit_doc("QA003 items", submitter_user, public_collection)
    qa = doc.qa_records.first()
    services.decide_qa(
        qa, decision="approve", actor=manager_user, checklist=services.full_qa_checklist()
    )
    qa.refresh_from_db()
    items = qa.checklist_items()
    assert len(items) == 6
    assert all(checked for _, _, checked in items)
    codes = {code for code, _, _ in items}
    assert codes == {
        "relevance",
        "oa_compliance",
        "metadata_complete",
        "abstract_quality",
        "no_copyright_issue",
        "no_duplication",
    }


@pytest.mark.django_db
def test_qa003_view_blocks_approve_until_all_boxes_checked(
    client, submitter_user, manager_user, public_collection
):
    doc = _submit_doc("QA003 view gate", submitter_user, public_collection)
    qa = doc.qa_records.first()
    client.force_login(manager_user)
    url = reverse("repo_documents:qa_review", kwargs={"pk": qa.pk})

    # 5 of 6 boxes checked — approval must be rejected server-side.
    resp = client.post(
        url,
        {
            "decision": "approve",
            "comments": "",
            "checklist_relevance": "on",
            "checklist_oa_compliance": "on",
            "checklist_metadata_complete": "on",
            "checklist_abstract_quality": "on",
            "checklist_no_copyright_issue": "on",
            # checklist_no_duplication intentionally omitted
        },
    )
    assert resp.status_code == 200  # form_invalid re-render, not a redirect
    qa.refresh_from_db()
    assert qa.decision == QARecord.Decision.PENDING

    # All 6 checked — now it goes through.
    resp2 = client.post(
        url,
        {
            "decision": "approve",
            "comments": "",
            "checklist_relevance": "on",
            "checklist_oa_compliance": "on",
            "checklist_metadata_complete": "on",
            "checklist_abstract_quality": "on",
            "checklist_no_copyright_issue": "on",
            "checklist_no_duplication": "on",
        },
    )
    assert resp2.status_code == 302
    qa.refresh_from_db()
    assert qa.decision == QARecord.Decision.APPROVE


@pytest.mark.django_db
def test_qa003_view_allows_revision_with_incomplete_checklist(
    client, submitter_user, manager_user, public_collection
):
    doc = _submit_doc("QA003 view revision", submitter_user, public_collection)
    qa = doc.qa_records.first()
    client.force_login(manager_user)
    url = reverse("repo_documents:qa_review", kwargs={"pk": qa.pk})

    resp = client.post(
        url,
        {
            "decision": "revision",
            "comments": "Please add ORCID for all authors.",
            # No checklist boxes checked at all.
        },
    )
    assert resp.status_code == 302
    qa.refresh_from_db()
    assert qa.decision == QARecord.Decision.REVISION


# ── FRREP-COL004 — comment resolve / reopen ─────────────────────────────────


@pytest.mark.django_db
def test_col004_owner_can_resolve_comment_and_notifies_author(
    client, submitter_user, manager_user, public_collection
):
    doc = services.ingest_document(
        file=_make_pdf("col004.pdf"),
        title="Comment resolve",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    comment = services.add_comment(
        document=doc, author=manager_user, text="Please check the licence field."
    )
    assert comment.is_resolved is False

    client.force_login(submitter_user)  # document owner, not the comment author
    Notification.objects.filter(verb=Notification.Verb.REPO_COMMENT_RESOLVED).delete()
    resp = client.post(
        reverse("repo_documents:comment_resolve", kwargs={"uid": doc.document_uid, "comment_id": comment.pk})
    )
    assert resp.status_code == 302
    comment.refresh_from_db()
    assert comment.is_resolved is True
    assert Notification.objects.filter(
        recipient=manager_user, verb=Notification.Verb.REPO_COMMENT_RESOLVED
    ).exists()


@pytest.mark.django_db
def test_col004_reopen_toggles_state_back(client, submitter_user, manager_user, public_collection):
    doc = services.ingest_document(
        file=_make_pdf("col004b.pdf"),
        title="Comment reopen",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    comment = services.add_comment(document=doc, author=manager_user, text="Check DOI.")
    services.resolve_comment(comment, actor=submitter_user)
    comment.refresh_from_db()
    assert comment.is_resolved is True

    client.force_login(submitter_user)
    resp = client.post(
        reverse("repo_documents:comment_reopen", kwargs={"uid": doc.document_uid, "comment_id": comment.pk})
    )
    assert resp.status_code == 302
    comment.refresh_from_db()
    assert comment.is_resolved is False


@pytest.mark.django_db
def test_col004_unrelated_user_cannot_resolve_comment(client, submitter_user, manager_user, public_collection):
    doc = services.ingest_document(
        file=_make_pdf("col004c.pdf"),
        title="Comment permission",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    comment = services.add_comment(document=doc, author=manager_user, text="Anything wrong here?")

    outsider = User.objects.create_user(email="outsider@test.example", password="pw-test-1234")
    client.force_login(outsider)
    resp = client.post(
        reverse("repo_documents:comment_resolve", kwargs={"uid": doc.document_uid, "comment_id": comment.pk})
    )
    assert resp.status_code == 403
    comment.refresh_from_db()
    assert comment.is_resolved is False


@pytest.mark.django_db
def test_col004_resolving_own_comment_does_not_self_notify(client, submitter_user, public_collection):
    doc = services.ingest_document(
        file=_make_pdf("col004d.pdf"),
        title="Comment self-resolve",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    comment = services.add_comment(document=doc, author=submitter_user, text="Note to self.")
    Notification.objects.filter(verb=Notification.Verb.REPO_COMMENT_RESOLVED).delete()
    services.resolve_comment(comment, actor=submitter_user)
    assert not Notification.objects.filter(verb=Notification.Verb.REPO_COMMENT_RESOLVED).exists()
