"""PRD §5.1 FRFA-AA025 — deferred budget assignment for Scholarship /
Fellowship / Challenge call types.

Covers:
- `award_application` flags the award budget_deferred=True for non-Grant call types.
- `finalize_deferred_budget` validates total == award.amount and fills the Award.
- Disbursements are blocked while `budget_deferred and budget_finalized_at is None`.
- After finalisation, standard disbursement flow proceeds.
"""
from datetime import date, timedelta
from decimal import Decimal

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

from apps.rims.finance.models import Budget, BudgetLine, DisbursementRequest
from apps.rims.finance.services import (
    finalize_deferred_budget,
    submit_disbursement_request,
)
from apps.rims.grants.models import Application, GrantCall
from apps.rims.grants.services import (
    award_application,
    shortlist_application,
    submit_application,
)


def _award_for(call_type, applicant_user, institution):
    call = GrantCall.objects.create(
        title=f"{call_type} call",
        slug=f"def-{call_type}-{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,
        call_type=call_type,
    )
    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("900"), date.today() + timedelta(days=180), narrative="")
    return Application.objects.get(pk=app.pk).award


@pytest.mark.django_db
@pytest.mark.parametrize(
    "call_type,is_deferred",
    [
        (GrantCall.CallType.GRANT, False),
        (GrantCall.CallType.SCHOLARSHIP, True),
        (GrantCall.CallType.FELLOWSHIP, True),
        (GrantCall.CallType.CHALLENGE, True),
    ],
)
def test_award_budget_deferred_matches_call_type(
    applicant_user, institution, call_type, is_deferred
):
    award = _award_for(call_type, applicant_user, institution)
    assert award.budget_deferred is is_deferred
    assert award.budget_finalized_at is None


@pytest.mark.django_db
def test_disbursement_blocked_when_deferred_budget_not_finalized(
    applicant_user, institution
):
    award = _award_for(GrantCall.CallType.SCHOLARSHIP, applicant_user, institution)
    budget = Budget.objects.create(award=award, name="Pre-finalise placeholder")
    BudgetLine.objects.create(budget=budget, label="Line", amount=Decimal("900"))
    req = DisbursementRequest.objects.create(
        budget=budget,
        amount=Decimal("100"),
        status=DisbursementRequest.Status.DRAFT,
    )
    with pytest.raises(ValidationError) as exc_info:
        submit_disbursement_request(req)
    assert "deferred budget" in str(exc_info.value).lower()


@pytest.mark.django_db
def test_finalize_deferred_budget_happy_path(applicant_user, institution, finance_user):
    award = _award_for(GrantCall.CallType.FELLOWSHIP, applicant_user, institution)
    budget = finalize_deferred_budget(
        award,
        [
            {"label": "Stipend", "amount": "500"},
            {"label": "Travel", "amount": "400"},
        ],
        actor=finance_user,
    )
    award.refresh_from_db()
    assert award.budget_finalized_at is not None
    assert award.budget_finalized_by_id == finance_user.pk
    assert budget.lines.count() == 2
    assert sum(l.amount for l in budget.lines.all()) == Decimal("900")


@pytest.mark.django_db
def test_finalize_deferred_budget_rejects_mismatched_total(applicant_user, institution, finance_user):
    award = _award_for(GrantCall.CallType.SCHOLARSHIP, applicant_user, institution)
    with pytest.raises(ValidationError):
        finalize_deferred_budget(
            award,
            [{"label": "A", "amount": "500"}, {"label": "B", "amount": "200"}],
            actor=finance_user,
        )
    award.refresh_from_db()
    assert award.budget_finalized_at is None


@pytest.mark.django_db
def test_finalize_deferred_budget_refused_for_grant_call_type(applicant_user, institution, finance_user):
    award = _award_for(GrantCall.CallType.GRANT, applicant_user, institution)
    with pytest.raises(ValidationError):
        finalize_deferred_budget(
            award,
            [{"label": "Line", "amount": "900"}],
            actor=finance_user,
        )


@pytest.mark.django_db
def test_disbursement_allowed_after_deferred_budget_finalized(
    applicant_user, institution, finance_user
):
    award = _award_for(GrantCall.CallType.CHALLENGE, applicant_user, institution)
    budget = finalize_deferred_budget(
        award,
        [{"label": "Prize", "amount": "900"}],
        actor=finance_user,
    )
    req = DisbursementRequest.objects.create(
        budget=budget,
        amount=Decimal("100"),
        status=DisbursementRequest.Status.DRAFT,
    )
    submit_disbursement_request(req)
    req.refresh_from_db()
    assert req.status == DisbursementRequest.Status.SUBMITTED
