"""PRD §5.1 FRFA-AM029 — reviewer self-declared conflict of interest.

When a reviewer declares a conflict on an assigned application:
  - The COI row is upserted with declared_by + declared_at set to the reviewer.
  - The application disappears from the reviewer's queue.
  - The reviewer can no longer load the read-only application surface.
  - The grants manager (call.created_by) is notified via REVIEWER_COI_DECLARED.
  - An audit row is written via write_audit_log.delay.
"""

from __future__ import annotations

from datetime import timedelta
from unittest.mock import patch

import pytest
from django.urls import reverse
from django.utils import timezone

from apps.core.notifications.models import Notification
from apps.rims.grants.models import (
    Application,
    ConflictOfInterest,
    GrantCall,
    Review,
)
from apps.rims.grants.services import submit_application


@pytest.fixture(autouse=True)
def _no_async_dispatch():
    with patch(
        "apps.core.notifications.services.dispatch_notification_channel.delay"
    ):
        yield


@pytest.fixture(autouse=True)
def _no_async_audit():
    with patch("apps.core.audit.tasks.write_audit_log.delay") as fake:
        yield fake


def _under_review_app(applicant, institution, manager, reviewer) -> Application:
    call = GrantCall.objects.create(
        title="COI call",
        slug=f"coi-call-{timezone.now().timestamp()}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=14),
        status=GrantCall.Status.PUBLISHED,
        review_deadline_days=14,
        created_by=manager,
    )
    app = Application.objects.create(
        call=call, applicant=applicant, institution=institution
    )
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    Review.objects.create(application=app, reviewer=reviewer)
    return app


@pytest.mark.django_db
def test_declare_coi_blocks_reviewer_and_notifies_manager(
    client, applicant_user, institution, grants_manager_user, reviewer_user
):
    """FRFA-AM029 happy path."""
    app = _under_review_app(
        applicant_user, institution, grants_manager_user, reviewer_user
    )
    review = Review.objects.get(application=app, reviewer=reviewer_user)
    client.force_login(reviewer_user)

    response = client.post(
        reverse("rims_grants:review_declare_coi", kwargs={"pk": review.pk}),
        data={
            "notes": "Prior collaboration on the same lab — cannot judge impartially.",
            "confirm": "on",
        },
    )
    assert response.status_code == 302
    assert response.url.endswith(reverse("rims_grants:review_queue"))

    coi = ConflictOfInterest.objects.get(application=app, reviewer=reviewer_user)
    assert coi.has_conflict is True
    assert coi.declared_by_id == reviewer_user.pk
    assert coi.declared_at is not None
    assert "prior collaboration" in coi.notes.lower()

    # Manager (call.created_by) should receive the REVIEWER_COI_DECLARED notification.
    assert Notification.objects.filter(
        recipient=grants_manager_user,
        verb=Notification.Verb.REVIEWER_COI_DECLARED,
    ).exists()


@pytest.mark.django_db
def test_short_note_is_rejected_with_specific_reason(
    client, applicant_user, institution, grants_manager_user, reviewer_user
):
    """Notes shorter than NOTES_MIN_LENGTH must be rejected with a specific reason."""
    app = _under_review_app(
        applicant_user, institution, grants_manager_user, reviewer_user
    )
    review = Review.objects.get(application=app, reviewer=reviewer_user)
    client.force_login(reviewer_user)

    response = client.post(
        reverse("rims_grants:review_declare_coi", kwargs={"pk": review.pk}),
        data={"notes": "too short", "confirm": "on"},
    )
    assert response.status_code == 200  # re-renders the form with errors
    # Nothing persisted.
    assert not ConflictOfInterest.objects.filter(
        application=app, reviewer=reviewer_user, has_conflict=True
    ).exists()


@pytest.mark.django_db
def test_declared_coi_removes_app_from_reviewer_queue(
    client, applicant_user, institution, grants_manager_user, reviewer_user
):
    """The reviewer queue must hide the application once COI is declared."""
    app = _under_review_app(
        applicant_user, institution, grants_manager_user, reviewer_user
    )
    review = Review.objects.get(application=app, reviewer=reviewer_user)
    client.force_login(reviewer_user)

    # Before declaration: review shows up.
    response = client.get(reverse("rims_grants:review_queue") + "?status=all")
    assert response.status_code == 200
    assert review in list(response.context["reviews"])

    # Declare COI.
    client.post(
        reverse("rims_grants:review_declare_coi", kwargs={"pk": review.pk}),
        data={
            "notes": "I am the applicant's PhD supervisor — clear conflict here.",
            "confirm": "on",
        },
    )

    # After declaration: review hidden.
    response = client.get(reverse("rims_grants:review_queue") + "?status=all")
    assert review not in list(response.context["reviews"])
    assert response.context["coi_blocked_count"] == 1


@pytest.mark.django_db
def test_declared_coi_blocks_application_read_view(
    client, applicant_user, institution, grants_manager_user, reviewer_user
):
    """After declaring COI, the reviewer can no longer load the application."""
    app = _under_review_app(
        applicant_user, institution, grants_manager_user, reviewer_user
    )
    review = Review.objects.get(application=app, reviewer=reviewer_user)
    client.force_login(reviewer_user)

    client.post(
        reverse("rims_grants:review_declare_coi", kwargs={"pk": review.pk}),
        data={
            "notes": "Co-author on the cited paper — cannot review without bias.",
            "confirm": "on",
        },
    )
    response = client.get(reverse("rims_grants:review_application", kwargs={"pk": review.pk}))
    assert response.status_code == 404


@pytest.mark.django_db
def test_submitted_review_cannot_declare_coi(
    client, applicant_user, institution, grants_manager_user, reviewer_user
):
    """Once scored, the COI route should refuse — conflicts must be raised first."""
    app = _under_review_app(
        applicant_user, institution, grants_manager_user, reviewer_user
    )
    review = Review.objects.get(application=app, reviewer=reviewer_user)
    review.submitted_at = timezone.now()
    review.save(update_fields=["submitted_at"])

    client.force_login(reviewer_user)
    response = client.get(reverse("rims_grants:review_declare_coi", kwargs={"pk": review.pk}))
    assert response.status_code == 302
    assert response.url.endswith(reverse("rims_grants:review_queue"))
