"""SRS gap-closure tests — REPO-SP7 Document Collaboration + REPO-SP8 §INT006.

COL003 — a document's own author gets a working comment box (previously an
unhandled PermissionDenied), and the discussion is grouped by section_anchor.
COL005 — the assignee (or the document owner/manager) can drive a review
task's status through the create → in_progress → completed loop.
COL006 — an overdue reminder also reaches the document's owner, not just the
assignee.
COL007 — the share page lists current collaborators (owner/manager-only) with
a working revoke action.
COL008 — a rejected concurrent edit notifies both editors instead of just
silently discarding the loser's changes.
INT006 — the REP OER "Open" link is routed through a tracked redirect that
logs an AccessEvent(source="rep").
"""
from __future__ import annotations

import pytest
from django.core.files.base import ContentFile
from django.test import Client
from django.urls import reverse
from django.utils import timezone

from apps.core.notifications.models import Notification
from apps.repository.documents import services
from apps.repository.documents.models import (
    Document,
    DocumentComment,
    DocumentReviewTask,
    DocumentShare,
)


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 _published_doc(submitter_user, manager_user, public_collection, *, name):
    doc = services.ingest_document(
        file=_make_pdf(name),
        title=f"Doc {name}",
        collection=public_collection,
        document_type=Document.DocumentType.THESIS,
        submitted_by=submitter_user,
        actor=submitter_user,
    )
    services.submit_for_qa(doc, actor=submitter_user)
    services.decide_qa(
        doc.qa_records.first(),
        decision="approve",
        actor=manager_user,
        checklist=services.full_qa_checklist(),
    )
    return Document.objects.get(pk=doc.pk)


# ── COL003 — owner can comment on their own document; grouped by anchor ────


@pytest.mark.django_db(transaction=True)
def test_col003_owner_can_comment_on_own_document(
    submitter_user, manager_user, public_collection
):
    doc = _published_doc(submitter_user, manager_user, public_collection, name="col003.pdf")
    client = Client()
    client.force_login(submitter_user)
    resp = client.post(
        reverse("repo_documents:comment_create", kwargs={"uid": doc.document_uid}),
        {"text": "Note to self", "section_anchor": ""},
    )
    assert resp.status_code in (301, 302)
    assert DocumentComment.objects.filter(document=doc, author=submitter_user).exists()


@pytest.mark.django_db(transaction=True)
def test_col003_stranger_without_share_cannot_comment(
    submitter_user, manager_user, public_collection
):
    doc = _published_doc(submitter_user, manager_user, public_collection, name="col003b.pdf")
    stranger = submitter_user.__class__.objects.create_user(
        email="stranger@test.example", password="pw-test-1234"
    )
    client = Client()
    client.force_login(stranger)
    resp = client.post(
        reverse("repo_documents:comment_create", kwargs={"uid": doc.document_uid}),
        {"text": "Uninvited comment", "section_anchor": ""},
    )
    assert resp.status_code == 403


@pytest.mark.django_db(transaction=True)
def test_col003_comments_are_grouped_by_section_anchor(
    submitter_user, manager_user, public_collection
):
    doc = _published_doc(submitter_user, manager_user, public_collection, name="col003c.pdf")
    services.add_comment(doc, submitter_user, "General note")
    services.add_comment(doc, submitter_user, "Methods note", section_anchor="p.4 §2")
    client = Client()
    client.force_login(submitter_user)
    resp = client.get(reverse("repo_documents:document_detail", kwargs={"uid": doc.document_uid}))
    assert resp.status_code == 200
    groups = {g["anchor"]: [c.text for c in g["comments"]] for g in resp.context["comment_groups"]}
    assert groups["General"] == ["General note"]
    assert groups["p.4 §2"] == ["Methods note"]


# ── COL005 — review task status transitions ────────────────────────────────


@pytest.mark.django_db(transaction=True)
def test_col005_assignee_can_mark_task_complete_and_notifies_owner(
    submitter_user, manager_user, public_collection
):
    doc = _published_doc(submitter_user, manager_user, public_collection, name="col005.pdf")
    task = services.add_review_task(
        doc,
        assignee=manager_user,
        scope="Section 1",
        created_by=submitter_user,
    )
    Notification.objects.all().delete()
    client = Client()
    client.force_login(manager_user)
    resp = client.post(
        reverse(
            "repo_documents:review_task_update_status",
            kwargs={"uid": doc.document_uid, "pk": task.pk},
        ),
        {"status": "completed"},
    )
    assert resp.status_code in (301, 302)
    task.refresh_from_db()
    assert task.status == DocumentReviewTask.Status.COMPLETED
    assert Notification.objects.filter(
        recipient=submitter_user,
        verb=Notification.Verb.REPO_REVIEW_TASK_COMPLETED,
    ).exists()


@pytest.mark.django_db(transaction=True)
def test_col005_non_assignee_non_manager_cannot_update_status(
    submitter_user, manager_user, public_collection
):
    doc = _published_doc(submitter_user, manager_user, public_collection, name="col005b.pdf")
    task = services.add_review_task(
        doc, assignee=manager_user, scope="Section 1", created_by=submitter_user
    )
    stranger = submitter_user.__class__.objects.create_user(
        email="stranger2@test.example", password="pw-test-1234"
    )
    client = Client()
    client.force_login(stranger)
    resp = client.post(
        reverse(
            "repo_documents:review_task_update_status",
            kwargs={"uid": doc.document_uid, "pk": task.pk},
        ),
        {"status": "in_progress"},
    )
    assert resp.status_code == 403


# ── COL006 — overdue reminder also reaches the document owner ─────────────


@pytest.mark.django_db(transaction=True)
def test_col006_overdue_reminder_also_notifies_document_owner(
    submitter_user, manager_user, public_collection
):
    from datetime import timedelta

    from apps.repository.documents import tasks

    doc = _published_doc(submitter_user, manager_user, public_collection, name="col006.pdf")
    services.add_review_task(
        doc,
        assignee=manager_user,
        scope="Overdue section",
        due_date=timezone.now().date() - timedelta(days=1),
        created_by=submitter_user,
    )
    Notification.objects.all().delete()
    tasks.send_review_task_reminders()
    assert Notification.objects.filter(
        recipient=manager_user, verb=Notification.Verb.REPO_REVIEW_TASK_DUE
    ).exists()
    assert Notification.objects.filter(
        recipient=submitter_user, verb=Notification.Verb.REPO_REVIEW_TASK_DUE
    ).exists()


# ── COL007 — collaborator list + revoke ─────────────────────────────────────


@pytest.mark.django_db(transaction=True)
def test_col007_owner_sees_collaborators_and_can_revoke(
    submitter_user, manager_user, public_collection
):
    doc = _published_doc(submitter_user, manager_user, public_collection, name="col007.pdf")
    share = services.share_document(doc, manager_user, "view", granted_by=submitter_user)

    client = Client()
    client.force_login(submitter_user)
    resp = client.get(reverse("repo_documents:document_share", kwargs={"uid": doc.document_uid}))
    assert resp.status_code == 200
    assert resp.context["can_manage_shares"] is True
    assert list(resp.context["collaborators"]) == [share]
    assert "Current collaborators" in resp.content.decode()

    resp = client.post(reverse("repo_documents:share_revoke", kwargs={"share_id": share.pk}))
    assert resp.status_code in (301, 302)
    share = DocumentShare.objects.get(pk=share.pk)
    assert share.revoked_at is not None


@pytest.mark.django_db(transaction=True)
def test_col007_view_only_collaborator_does_not_see_management_panel(
    submitter_user, manager_user, public_collection
):
    doc = _published_doc(submitter_user, manager_user, public_collection, name="col007b.pdf")
    viewer = submitter_user.__class__.objects.create_user(
        email="viewer@test.example", password="pw-test-1234"
    )
    services.share_document(doc, viewer, "view", granted_by=submitter_user)

    client = Client()
    client.force_login(viewer)
    resp = client.get(reverse("repo_documents:document_share", kwargs={"uid": doc.document_uid}))
    assert resp.status_code == 200
    assert resp.context["can_manage_shares"] is False
    assert "collaborators" not in resp.context


# ── COL008 — concurrent-edit conflict notifies both editors ────────────────


@pytest.mark.django_db(transaction=True)
def test_col008_conflict_notifies_rejected_and_winning_editor(
    submitter_user, manager_user, public_collection
):
    doc = _published_doc(submitter_user, manager_user, public_collection, name="col008.pdf")
    stale_token = services.document_edit_token(doc)

    # Manager's edit "wins" the race.
    services.update_document_metadata(
        Document.objects.get(pk=doc.pk),
        fields={"title": "Winning edit"},
        expected_token=stale_token,
        actor=manager_user,
    )
    doc = Document.objects.get(pk=doc.pk)

    Notification.objects.all().delete()
    client = Client()
    client.force_login(submitter_user)
    resp = client.post(
        reverse("repo_documents:document_edit", kwargs={"uid": doc.document_uid}),
        {
            "edit_token": stale_token,
            "title": "Submitter's stale edit",
            "abstract": doc.abstract,
            "document_type": doc.document_type,
            "language": doc.language,
            "license_type": doc.license_type,
            # Deliberately not `services.render_authors_text(doc)` — the
            # fixture ingests without an author roster, so that would come
            # back blank and fail the form's `required` Authors field before
            # ever reaching the conflict-detecting service call.
            "authors_text": "Doe, Jane",
        },
    )
    assert resp.status_code == 200  # form_invalid re-renders
    assert Notification.objects.filter(
        recipient=submitter_user, verb=Notification.Verb.REPO_EDIT_CONFLICT
    ).exists()
    assert Notification.objects.filter(
        recipient=manager_user, verb=Notification.Verb.REPO_EDIT_CONFLICT
    ).exists()


# ── INT006 — REP OER access is tracked ──────────────────────────────────────


@pytest.mark.django_db(transaction=True)
def test_int006_oer_redirect_logs_rep_sourced_access_event(
    submitter_user, manager_user, public_collection
):
    from apps.repository.analytics.models import AccessEvent

    doc = _published_doc(submitter_user, manager_user, public_collection, name="int006.pdf")
    client = Client()
    resp = client.get(reverse("repo_documents:oer_access", kwargs={"uid": doc.document_uid}))
    assert resp.status_code in (301, 302)
    assert AccessEvent.objects.filter(document=doc, source=AccessEvent.Source.REP).exists()
