"""PRD §5.1 FRFA-AA001 / FRFA-AA004 — Phase 4 slice 4A regression suite.

Covers the four FRFA-AA001 award-creation preconditions and the FRFA-AA004
amendment-driven letter regeneration with versioned snapshots.
"""

from __future__ import annotations

from datetime import date, timedelta
from decimal import Decimal
from unittest.mock import patch

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

from apps.rims.grants.models import (
    Application,
    Award,
    AwardAmendment,
    AwardLetterVersion,
    FundingRecord,
    GrantCall,
)
from apps.rims.grants.services import (
    approve_award_amendment,
    award_application,
    check_award_preconditions,
    create_award_amendment,
    shortlist_application,
    submit_application,
)


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


def _make_funding(grants_manager_user, finance_user, *, suffix, amount=Decimal("100000")):
    return FundingRecord.objects.create(
        title=f"FR {suffix}",
        amount=amount,
        start_date=date.today() - timedelta(days=10),
        end_date=date.today() + timedelta(days=365),
        grant_manager=grants_manager_user,
        finance_officer=finance_user,
        status=FundingRecord.Status.APPROVED,
    )


def _make_call(funding, *, suffix, ceiling=Decimal("10000")):
    return GrantCall.objects.create(
        title=f"Call {suffix}",
        slug=f"call-{suffix}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=14),
        status=GrantCall.Status.PUBLISHED,
        funding_record=funding,
        budget_ceiling=ceiling,
    )


def _make_shortlisted_app(call, applicant_user, institution):
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    submit_application(app)
    app = Application.objects.get(pk=app.pk)
    shortlist_application(app)
    return Application.objects.get(pk=app.pk)


@pytest.mark.django_db
def test_award_preconditions_block_when_funding_balance_insufficient(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA001 — uncommitted balance must cover the requested award amount."""
    fr = _make_funding(grants_manager_user, finance_user, suffix="bal", amount=Decimal("100000"))
    call = _make_call(fr, suffix="bal", ceiling=Decimal("500"))
    app = _make_shortlisted_app(call, applicant_user, institution)

    with pytest.raises(ValidationError) as exc:
        award_application(app, Decimal("1000"), date.today() + timedelta(days=180))
    assert "FRFA-AA001" in str(exc.value)
    assert "remains" in str(exc.value) or "available" in str(exc.value).lower() or "Need" in str(exc.value)


@pytest.mark.django_db
def test_award_preconditions_block_when_grants_manager_unassigned(
    applicant_user, institution, finance_user
):
    """FRFA-AA001 — funding record must have a grants manager assigned."""
    fr = FundingRecord.objects.create(
        title="FR no-gm",
        amount=Decimal("100000"),
        start_date=date.today() - timedelta(days=10),
        end_date=date.today() + timedelta(days=365),
        finance_officer=finance_user,
        status=FundingRecord.Status.APPROVED,
    )
    call = _make_call(fr, suffix="nogm")
    app = _make_shortlisted_app(call, applicant_user, institution)

    with pytest.raises(ValidationError) as exc:
        award_application(app, Decimal("1000"), date.today() + timedelta(days=180))
    assert "FRFA-AA001" in str(exc.value)
    assert "grants manager" in str(exc.value).lower()


@pytest.mark.django_db
def test_award_preconditions_block_when_finance_officer_unassigned(
    applicant_user, institution, grants_manager_user
):
    """FRFA-AA001 — funding record must have a finance officer assigned."""
    fr = FundingRecord.objects.create(
        title="FR no-fo",
        amount=Decimal("100000"),
        start_date=date.today() - timedelta(days=10),
        end_date=date.today() + timedelta(days=365),
        grant_manager=grants_manager_user,
        status=FundingRecord.Status.APPROVED,
    )
    call = _make_call(fr, suffix="nofo")
    app = _make_shortlisted_app(call, applicant_user, institution)

    with pytest.raises(ValidationError) as exc:
        award_application(app, Decimal("1000"), date.today() + timedelta(days=180))
    assert "FRFA-AA001" in str(exc.value)
    assert "finance officer" in str(exc.value).lower()


@pytest.mark.django_db
def test_award_preconditions_pass_with_all_four_met(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA001 happy path — all four preconditions met, award is created."""
    fr = _make_funding(grants_manager_user, finance_user, suffix="ok")
    call = _make_call(fr, suffix="ok", ceiling=Decimal("10000"))
    app = _make_shortlisted_app(call, applicant_user, institution)

    award = award_application(app, Decimal("1000"), date.today() + timedelta(days=180))
    assert award.pk is not None
    assert award.status == Award.Status.ACTIVE


@pytest.mark.django_db
def test_check_award_preconditions_probe_returns_per_check_status(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA001 — the non-raising probe surfaces every check for the UI."""
    fr = _make_funding(grants_manager_user, finance_user, suffix="probe")
    call = _make_call(fr, suffix="probe", ceiling=Decimal("10000"))
    app = _make_shortlisted_app(call, applicant_user, institution)

    probe = check_award_preconditions(app, Decimal("1000"))
    assert set(probe.keys()) == {
        "funding",
        "balance",
        "grants_manager",
        "finance_officer",
        "letter_template",
    }
    assert all(item["ok"] for item in probe.values()), probe


@pytest.mark.django_db
def test_initial_award_creates_letter_version_v1(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA004 — award creation stamps AwardLetterVersion v1."""
    fr = _make_funding(grants_manager_user, finance_user, suffix="v1")
    call = _make_call(fr, suffix="v1")
    app = _make_shortlisted_app(call, applicant_user, institution)

    award = award_application(app, Decimal("1000"), date.today() + timedelta(days=180))
    versions = AwardLetterVersion.objects.filter(award=award)
    assert versions.count() == 1
    v1 = versions.first()
    assert v1.version_number == 1
    assert v1.triggering_amendment_id is None
    assert v1.file.size > 0


@pytest.mark.django_db
def test_amendment_approval_regenerates_letter_as_v2(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA004 — approving an amendment regenerates the letter and stamps v2.

    The new version is linked to the triggering amendment for the audit trail.
    """
    fr = _make_funding(grants_manager_user, finance_user, suffix="amend")
    call = _make_call(fr, suffix="amend", ceiling=Decimal("10000"))
    app = _make_shortlisted_app(call, applicant_user, institution)
    award = award_application(app, Decimal("1000"), date.today() + timedelta(days=180))

    amendment = create_award_amendment(
        award,
        change_summary="Increase amount to 1500",
        previous_terms={"amount": "1000"},
        new_terms={"amount": "1500"},
        actor=grants_manager_user,
    )
    approve_award_amendment(amendment, actor=grants_manager_user)

    versions = list(AwardLetterVersion.objects.filter(award=award).order_by("version_number"))
    assert len(versions) == 2
    assert versions[0].version_number == 1
    assert versions[0].triggering_amendment_id is None
    assert versions[1].version_number == 2
    assert versions[1].triggering_amendment_id == amendment.pk
    award.refresh_from_db()
    assert award.amount == Decimal("1500")
    # Current letter file is mirrored from the latest version.
    assert "v2" in award.letter_pdf_file.name
