"""PRD §5.1 FRFA-AA007 / FRFA-AA012 — tranche schedule + reconciliation regression."""

from __future__ import annotations

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

import pytest
from django.utils import timezone

from apps.rims.finance.models import Budget, BudgetLine
from apps.rims.grants.models import (
    Application,
    Award,
    AwardTranche,
    FundingRecord,
    GrantCall,
    TrancheBudgetAllocation,
)
from apps.rims.grants.services import (
    award_application,
    generate_tranche_schedule,
    reconcile_award_tranches,
    reconcile_tranche,
    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 _setup_award(applicant_user, institution, grants_manager_user, finance_user, *, suffix, amount=Decimal("4000")):
    fr = FundingRecord.objects.create(
        title=f"Tranche FR {suffix}",
        amount=Decimal("100000"),
        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,
    )
    call = GrantCall.objects.create(
        title=f"Tranche call {suffix}",
        slug=f"tranche-{suffix}",
        opens_at=timezone.now() - timedelta(days=1),
        closes_at=timezone.now() + timedelta(days=14),
        status=GrantCall.Status.PUBLISHED,
        funding_record=fr,
        budget_ceiling=Decimal("10000"),
    )
    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 = award_application(app, amount, date.today() + timedelta(days=365))
    return award


@pytest.mark.django_db
def test_generate_tranche_schedule_creates_four_quarterly_tranches(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA007 — default even-split creates 4 quarterly tranches summing to amount."""
    award = _setup_award(
        applicant_user, institution, grants_manager_user, finance_user, suffix="quart"
    )
    created = generate_tranche_schedule(award, actor=grants_manager_user)
    assert len(created) == 4
    total = sum((t.amount for t in created), Decimal("0"))
    assert total == award.amount
    # Last tranche should fall on or before project_end_date.
    last_due = max((t.due_on for t in created if t.due_on), default=None)
    assert last_due is not None


@pytest.mark.django_db
def test_generate_tranche_schedule_respects_existing_total(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA007 — re-running with partial existing tranches only fills the gap."""
    award = _setup_award(
        applicant_user, institution, grants_manager_user, finance_user, suffix="partial"
    )
    AwardTranche.objects.create(award=award, label="Existing", amount=Decimal("3000"))
    created = generate_tranche_schedule(award, actor=grants_manager_user)
    total = (
        sum((t.amount for t in created), Decimal("0"))
        + Decimal("3000")
    )
    assert total == award.amount


@pytest.mark.django_db
def test_reconcile_tranche_balanced_when_allocations_match(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA012 — allocations summing to tranche amount with no over-allocation is balanced."""
    award = _setup_award(
        applicant_user, institution, grants_manager_user, finance_user, suffix="bal"
    )
    budget = Budget.objects.create(award=award, name="Main", currency=award.currency)
    line = BudgetLine.objects.create(
        budget=budget, label="Personnel", amount=Decimal("4000"), unit_cost=Decimal("4000")
    )
    tranche = AwardTranche.objects.create(award=award, label="T1", amount=Decimal("1000"))
    TrancheBudgetAllocation.objects.create(
        tranche=tranche, budget_line=line, amount=Decimal("1000")
    )

    report = reconcile_tranche(tranche)
    assert report["balanced"] is True
    assert report["variance"] == Decimal("0.00")
    assert report["allocated_amount"] == Decimal("1000.00")


@pytest.mark.django_db
def test_reconcile_tranche_variance_when_under_allocated(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA012 — under-allocation shows a positive variance and balanced=False."""
    award = _setup_award(
        applicant_user, institution, grants_manager_user, finance_user, suffix="under"
    )
    budget = Budget.objects.create(award=award, name="Main", currency=award.currency)
    line = BudgetLine.objects.create(
        budget=budget, label="Personnel", amount=Decimal("4000"), unit_cost=Decimal("4000")
    )
    tranche = AwardTranche.objects.create(award=award, label="T1", amount=Decimal("1000"))
    TrancheBudgetAllocation.objects.create(
        tranche=tranche, budget_line=line, amount=Decimal("700")
    )
    report = reconcile_tranche(tranche)
    assert report["balanced"] is False
    assert report["variance"] == Decimal("300.00")


@pytest.mark.django_db
def test_reconcile_tranche_flags_over_allocated_budget_line(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA012 — over-allocated budget line surfaces a warning."""
    award = _setup_award(
        applicant_user, institution, grants_manager_user, finance_user, suffix="over"
    )
    budget = Budget.objects.create(award=award, name="Main", currency=award.currency)
    line = BudgetLine.objects.create(
        budget=budget, label="Personnel", amount=Decimal("1000"), unit_cost=Decimal("1000")
    )
    tranche_a = AwardTranche.objects.create(award=award, label="A", amount=Decimal("700"))
    tranche_b = AwardTranche.objects.create(award=award, label="B", amount=Decimal("500"))
    TrancheBudgetAllocation.objects.create(
        tranche=tranche_a, budget_line=line, amount=Decimal("700")
    )
    TrancheBudgetAllocation.objects.create(
        tranche=tranche_b, budget_line=line, amount=Decimal("500")
    )
    report = reconcile_tranche(tranche_b)
    assert report["budget_line_warnings"]
    assert report["balanced"] is False


@pytest.mark.django_db
def test_reconcile_award_tranches_aggregates_across_tranches(
    applicant_user, institution, grants_manager_user, finance_user
):
    """FRFA-AA012 — award-level reconciliation aggregates per-tranche reports."""
    award = _setup_award(
        applicant_user, institution, grants_manager_user, finance_user, suffix="agg"
    )
    budget = Budget.objects.create(award=award, name="Main", currency=award.currency)
    line = BudgetLine.objects.create(
        budget=budget, label="Personnel", amount=Decimal("4000"), unit_cost=Decimal("4000")
    )
    t1 = AwardTranche.objects.create(award=award, label="T1", amount=Decimal("1000"))
    t2 = AwardTranche.objects.create(award=award, label="T2", amount=Decimal("3000"))
    TrancheBudgetAllocation.objects.create(
        tranche=t1, budget_line=line, amount=Decimal("1000")
    )
    TrancheBudgetAllocation.objects.create(
        tranche=t2, budget_line=line, amount=Decimal("3000")
    )
    aggregate = reconcile_award_tranches(award)
    assert aggregate["balanced"] is True
    assert aggregate["tranche_total"] == Decimal("4000")
    assert aggregate["allocated_total"] == Decimal("4000")
    assert len(aggregate["rows"]) == 2
