"""PRD §5.1 FRFA-CO001 — close-out precondition catalogue.

Each test mutates exactly one underlying condition and asserts the matching
gate is the one flipped, leaving the other six gates passing. A final
happy-path test asserts all seven gates pass on a fully-resolved award.
"""
from __future__ import annotations

from datetime import timedelta
from decimal import Decimal

import pytest
from django.utils import timezone

from apps.rims.grants.closeout_helpers import (
    closeout_preconditions,
    get_or_create_closeout_record,
)
from apps.rims.grants.models import (
    Application,
    AssetVerification,
    Award,
    FinalFinancialReport,
    FinalNarrativeReport,
    GrantCall,
)
from apps.rims.grants.services import (
    award_application,
    shortlist_application,
    submit_application,
)


def _build_award(applicant_user, institution) -> Award:
    call = GrantCall.objects.create(
        title="CO001 preconditions call",
        slug=f"co001-pre-{int(timezone.now().timestamp() * 1000) % 10_000_000}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=10),
        status=GrantCall.Status.PUBLISHED,
    )
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    shortlist_application(app)
    award_application(
        app,
        Decimal("1000"),
        timezone.now().date() + timedelta(days=180),
        narrative="",
    )
    return Application.objects.get(pk=app.pk).award


def _all_checklist_done(closeout) -> None:
    items = list(closeout.checklist or [])
    for row in items:
        row["done"] = True
        row["evidence_note"] = "captured"
    closeout.checklist = items
    closeout.save(update_fields=["checklist", "updated_at"])


def _gates_by_key(award: Award) -> dict:
    return {g.key: g for g in closeout_preconditions(award)}


@pytest.fixture
def ready_award(db, applicant_user, institution):
    """Award with narrative + every required checklist item satisfied."""
    award = _build_award(applicant_user, institution)
    award.narrative = "Final narrative captured."
    award.save(update_fields=["narrative"])
    closeout = get_or_create_closeout_record(award)
    _all_checklist_done(closeout)
    return award


@pytest.mark.django_db
def test_all_gates_pass_on_ready_award(ready_award):
    gates = _gates_by_key(ready_award)
    assert set(gates) == {
        "narrative_present",
        "mandatory_checklist_done",
        "disbursement_within_budget",
        "no_pending_finance",
        "milestones_resolved",
        "reports_accepted",
        "assets_resolved",
    }
    blocking = [g for g in gates.values() if not g.passed]
    assert blocking == [], f"unexpected blockers: {[g.key for g in blocking]}"


@pytest.mark.django_db
def test_missing_narrative_flips_narrative_gate(ready_award):
    ready_award.narrative = ""
    ready_award.save(update_fields=["narrative"])
    gates = _gates_by_key(ready_award)
    assert gates["narrative_present"].passed is False
    assert "narrative" in gates["narrative_present"].blocker_message.lower()
    other_blocked = [k for k, g in gates.items() if not g.passed and k != "narrative_present"]
    assert other_blocked == []


@pytest.mark.django_db
def test_unsatisfied_required_checklist_item_flips_checklist_gate(ready_award):
    closeout = get_or_create_closeout_record(ready_award)
    items = list(closeout.checklist or [])
    items[0]["done"] = False
    items[0]["evidence_note"] = ""
    closeout.checklist = items
    closeout.save(update_fields=["checklist", "updated_at"])
    gates = _gates_by_key(ready_award)
    assert gates["mandatory_checklist_done"].passed is False
    assert gates["mandatory_checklist_done"].detail and "missing" in gates["mandatory_checklist_done"].detail
    other_blocked = [k for k, g in gates.items() if not g.passed and k != "mandatory_checklist_done"]
    assert other_blocked == []


@pytest.mark.django_db
def test_pending_financial_report_flips_reports_gate(ready_award):
    closeout = get_or_create_closeout_record(ready_award)
    pre_existing = FinalFinancialReport.objects.create(closeout=closeout, notes="pending finance review")
    pre_existing.raise_queries()
    pre_existing.save()
    gates = _gates_by_key(ready_award)
    assert gates["reports_accepted"].passed is False
    assert "financial" in gates["reports_accepted"].blocker_message.lower()


@pytest.mark.django_db
def test_pending_narrative_report_flips_reports_gate(ready_award):
    closeout = get_or_create_closeout_record(ready_award)
    pre_existing = FinalNarrativeReport.objects.create(closeout=closeout, notes="pending narrative review")
    pre_existing.request_revision()
    pre_existing.save()
    gates = _gates_by_key(ready_award)
    assert gates["reports_accepted"].passed is False
    assert "narrative" in gates["reports_accepted"].blocker_message.lower()


@pytest.mark.django_db
def test_unaccounted_asset_flips_assets_gate(ready_award):
    closeout = get_or_create_closeout_record(ready_award)
    AssetVerification.objects.create(
        closeout=closeout,
        asset_name="Laptop SN-CO001",
        status=AssetVerification.Status.UNACCOUNTED,
    )
    gates = _gates_by_key(ready_award)
    assert gates["assets_resolved"].passed is False
    assert "unaccounted" in gates["assets_resolved"].blocker_message.lower()


@pytest.mark.django_db
def test_disbursement_within_budget_gate_passes_default(ready_award):
    # A fresh award with no Budget rows has 0 disbursed vs 1000 award —
    # the overspend gate is structurally non-blocking. Asserting it here
    # documents the contract so a future change that breaks the snapshot
    # math gets caught.
    gates = _gates_by_key(ready_award)
    assert gates["disbursement_within_budget"].passed is True
    assert gates["no_pending_finance"].passed is True


@pytest.mark.django_db
def test_milestones_gate_passes_without_project(ready_award):
    # No Project attached to this Award in the fixture, so the milestones
    # gate is structurally a pass. Documents the no-project branch.
    assert getattr(ready_award, "project", None) is None
    gates = _gates_by_key(ready_award)
    assert gates["milestones_resolved"].passed is True
