"""PRD §5.1 FRFA-AM013–017 — Programme Officer document-completeness gate.

With `GrantCall.programme_officer_screening_enabled=True`:
- Eligible applications pause at SUBMITTED with `eligibility_passed=True`.
- PO explicitly advances via :func:`program_officer_advance` to:
    - UNDER_REVIEW (ELIGIBLE_READY)
    - REJECTED_INELIGIBLE (REJECTED_INCOMPLETE, with required note)
    - SUBMITTED + `revision_requested_at` (REVISION_REQUESTED, with required note)

With the flag False (legacy default) the old auto-advance behaviour is
preserved — covered by existing test_services.py / test_phase1_prd_flows.py.
"""
from datetime import timedelta

import pytest
from django.core.exceptions import ValidationError
from django.urls import reverse
from django.utils import timezone

from apps.core.permissions.roles import UserRole
from apps.rims.grants.models import Application, EligibilityRule, GrantCall
from apps.rims.grants.services import (
    ProgramOfficerDecision,
    program_officer_advance,
    submit_application,
)


def _screened_call():
    return GrantCall.objects.create(
        title="PO gate call",
        slug=f"po-gate-{int(timezone.now().timestamp())}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=10),
        status=GrantCall.Status.PUBLISHED,
        programme_officer_screening_enabled=True,
    )


@pytest.mark.django_db
def test_submission_pauses_at_submitted_when_po_gate_enabled(applicant_user, institution):
    call = _screened_call()
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    assert app.status == Application.Status.SUBMITTED
    assert app.eligibility_passed is True


@pytest.mark.django_db
def test_po_advance_to_eligible_ready_unlocks_review_queue(applicant_user, institution):
    call = _screened_call()
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    program_officer_advance(app, ProgramOfficerDecision.ELIGIBLE_READY)
    assert Application.objects.get(pk=app.pk).status == Application.Status.UNDER_REVIEW


@pytest.mark.django_db
def test_po_reject_incomplete_requires_note_and_routes_to_ineligible(
    applicant_user, institution
):
    call = _screened_call()
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)

    # Note is required.
    with pytest.raises(ValidationError):
        program_officer_advance(app, ProgramOfficerDecision.REJECTED_INCOMPLETE)

    program_officer_advance(
        app,
        ProgramOfficerDecision.REJECTED_INCOMPLETE,
        note="Missing signed CV and letter of support.",
    )
    fresh = Application.objects.get(pk=app.pk)
    assert fresh.status == Application.Status.REJECTED_INELIGIBLE
    assert "Missing signed CV" in fresh.eligibility_notes


@pytest.mark.django_db
def test_po_request_revision_sets_revision_fields(applicant_user, institution):
    call = _screened_call()
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    program_officer_advance(
        app,
        ProgramOfficerDecision.REVISION_REQUESTED,
        note="Please attach a revised budget by Friday.",
    )
    fresh = Application.objects.get(pk=app.pk)
    assert fresh.status == Application.Status.SUBMITTED
    assert fresh.revision_requested_at is not None
    assert "revised budget" in fresh.revision_note


@pytest.mark.django_db
def test_po_can_advance_a_flagged_application(applicant_user, institution):
    """Eligibility is advisory: a flagged application (eligibility_passed=False)
    pauses at SUBMITTED under a PO-gated call and the PO may still advance it to
    review after screening the documents — the flag is not a hard gate."""
    call = _screened_call()
    EligibilityRule.objects.create(
        call=call,
        rule_type=EligibilityRule.RuleType.DEGREE,
        params={"levels": ["phd"]},
        is_blocking=True,
    )
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    assert app.status == Application.Status.SUBMITTED
    assert app.eligibility_passed is False  # flagged
    program_officer_advance(app, ProgramOfficerDecision.ELIGIBLE_READY)
    assert Application.objects.get(pk=app.pk).status == Application.Status.UNDER_REVIEW


@pytest.mark.django_db
def test_po_advance_rejects_unknown_decision(applicant_user, institution):
    call = _screened_call()
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    with pytest.raises(ValidationError):
        program_officer_advance(app, "nonsense_decision")


@pytest.mark.django_db
def test_po_advance_refuses_non_submitted_application(applicant_user, institution):
    """A PO cannot advance an application that is already past SUBMITTED."""
    call = GrantCall.objects.create(
        title="Auto-advance",
        slug=f"auto-{int(timezone.now().timestamp())}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=10),
        status=GrantCall.Status.PUBLISHED,
        programme_officer_screening_enabled=False,
    )
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    assert app.status == Application.Status.UNDER_REVIEW
    with pytest.raises(ValidationError):
        program_officer_advance(app, ProgramOfficerDecision.ELIGIBLE_READY)


@pytest.mark.django_db
def test_po_screen_view_end_to_end(client, applicant_user, institution, grants_manager_user):
    call = _screened_call()
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    client.force_login(grants_manager_user)
    url = reverse("rims_grants:application_po_screen", kwargs={"pk": app.pk})
    resp = client.post(
        url,
        {"decision": ProgramOfficerDecision.ELIGIBLE_READY, "note": ""},
        follow=False,
    )
    assert resp.status_code in (302, 303)
    assert Application.objects.get(pk=app.pk).status == Application.Status.UNDER_REVIEW
