"""PRD §5.1 FRFA-AM020 / FRFA-AM025 — blind review anonymisation.

Two sets of invariants:

1. `blinded_application_context()` scrubs applicant PII (name, email,
   institution) out of proposal text and replaces document labels.
2. Current reviewer-facing pages (`review_queue`, `review_submit`) render
   `anonymized_code` (not email / name) when the call has `blind_review=True`.
"""
from datetime import timedelta

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

from apps.core.authentication.models import Institution
from apps.core.permissions.roles import UserRole
from apps.rims.grants.blinding import (
    BlindedApplicationContext,
    blinded_application_context,
    scrub_author_tokens,
)
from apps.rims.grants.models import Application, ApplicationDocument, GrantCall, Review

User = get_user_model()


@pytest.fixture
def applicant_with_pii(db):
    inst = Institution.objects.create(name="Acme Research University", country="Uganda", is_member=True)
    u = User.objects.create_user(email="alice.wonder@applicant.example", password="pw")
    u.first_name = "Alice"
    u.last_name = "Wonder"
    u.institution = inst
    u.role = UserRole.SCHOLAR
    u.save()
    return u, inst


@pytest.fixture
def reviewer(db):
    u = User.objects.create_user(email="rev@example.com", password="pw")
    u.role = UserRole.REVIEWER
    u.save()
    return u


def _make_blind_call():
    return GrantCall.objects.create(
        title="Blind call",
        slug=f"blind-{int(timezone.now().timestamp())}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=10),
        status=GrantCall.Status.PUBLISHED,
        blind_review=True,
    )


@pytest.mark.django_db
def test_scrub_author_tokens_case_insensitive():
    text = "Alice Wonder proposed a project. alice.wonder@ex.com led it."
    cleaned = scrub_author_tokens(
        text,
        ["Alice Wonder", "alice.wonder@ex.com"],
    )
    assert "Alice" not in cleaned
    assert "wonder" not in cleaned.lower()
    assert "alice.wonder" not in cleaned.lower()


@pytest.mark.django_db
def test_scrub_author_tokens_replaces_longer_match_first():
    """`Alice Wonder` should be replaced before `Alice` so we don't end up
    with `[redacted] Wonder` leaking half the name."""
    text = "Alice Wonder wrote the proposal."
    cleaned = scrub_author_tokens(text, ["Alice Wonder", "Alice", "Wonder"])
    assert "Alice" not in cleaned
    assert "Wonder" not in cleaned


@pytest.mark.django_db
def test_blinded_application_context_strips_pii(applicant_with_pii):
    applicant, inst = applicant_with_pii
    call = _make_blind_call()
    app = Application.objects.create(
        call=call,
        applicant=applicant,
        institution=inst,
        proposal=(
            "Alice Wonder, affiliated with Acme Research University, proposes..."
            " Contact: alice.wonder@applicant.example."
        ),
        anonymized_code="ANON-1234",
    )
    ApplicationDocument.objects.create(
        application=app,
        label="Alice-CV.pdf",
        file=ContentFile(b"%PDF", name="alice-cv.pdf"),
    )
    ApplicationDocument.objects.create(
        application=app,
        label="cover.pdf",
        file=ContentFile(b"%PDF", name="cover.pdf"),
    )

    ctx = blinded_application_context(app)
    assert isinstance(ctx, BlindedApplicationContext)
    assert ctx.reference == "ANON-1234"
    assert "Alice" not in ctx.proposal
    assert "Wonder" not in ctx.proposal
    assert "alice.wonder" not in ctx.proposal.lower()
    assert "Acme Research University" not in ctx.proposal
    # Documents are re-labelled to generic names.
    assert [label for label, _ in ctx.documents] == ["Document 1", "Document 2"]


@pytest.mark.django_db
def test_review_queue_hides_email_when_blind_review_enabled(
    client, reviewer, applicant_with_pii
):
    applicant, inst = applicant_with_pii
    call = _make_blind_call()
    app = Application.objects.create(
        call=call, applicant=applicant, institution=inst, anonymized_code="ANON-9"
    )
    Review.objects.create(application=app, reviewer=reviewer)
    client.force_login(reviewer)
    resp = client.get(reverse("rims_grants:review_queue"))
    body = resp.content.decode("utf-8")
    assert applicant.email not in body
    assert applicant.get_full_name() not in body
    assert "ANON-9" in body


@pytest.mark.django_db
def test_review_submit_hides_email_when_blind_review_enabled(
    client, reviewer, applicant_with_pii
):
    applicant, inst = applicant_with_pii
    call = _make_blind_call()
    app = Application.objects.create(
        call=call, applicant=applicant, institution=inst, anonymized_code="ANON-10"
    )
    review = Review.objects.create(application=app, reviewer=reviewer)
    client.force_login(reviewer)
    resp = client.get(reverse("rims_grants:review_submit", kwargs={"pk": review.pk}))
    body = resp.content.decode("utf-8")
    assert applicant.email not in body
    assert applicant.get_full_name() not in body
    assert "ANON-10" in body


@pytest.mark.django_db
def test_non_blind_call_still_shows_email(client, reviewer, applicant_with_pii):
    """Regression guard: we must not over-redact when blind_review is OFF."""
    applicant, inst = applicant_with_pii
    call = GrantCall.objects.create(
        title="Open call",
        slug=f"open-{int(timezone.now().timestamp())}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=10),
        status=GrantCall.Status.PUBLISHED,
        blind_review=False,
    )
    app = Application.objects.create(
        call=call, applicant=applicant, institution=inst
    )
    Review.objects.create(application=app, reviewer=reviewer)
    client.force_login(reviewer)
    resp = client.get(reverse("rims_grants:review_queue"))
    assert resp.status_code == 200
    assert applicant.email in resp.content.decode("utf-8")
