"""PRD §5.4 FRFM027 — rolling-budget Reforecast generation."""
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.core.audit.models import AuditLog
from apps.core.audit.tasks import persist_audit_log
from apps.rims.finance.models import (
    Budget,
    BudgetLine,
    Expenditure,
    PaymentSchedule,
    Reforecast,
    ReforecastLine,
)
from apps.rims.finance.services_reforecast import (
    burn_rate_for,
    generate_reforecast,
    project_tranche_inflows,
)
from apps.rims.grants.models import Application, Award, GrantCall


@pytest.fixture(autouse=True)
def _sync_audit():
    with patch("apps.core.audit.tasks.write_audit_log.delay", side_effect=lambda **kw: persist_audit_log(**kw)):
        with patch(
            "apps.core.audit.mixins.transaction.on_commit",
            side_effect=lambda fn: fn(),
        ):
            yield


def _budget_with_lines(applicant_user, institution) -> Budget:
    call = GrantCall.objects.create(
        title=f"6D-rf call {timezone.now().timestamp()}",
        slug=f"6d-rf-{int(timezone.now().timestamp() * 1000) % 10_000_000}",
        call_type="grant",
        opens_at=timezone.now() - timedelta(days=200),
        closes_at=timezone.now() + timedelta(days=10),
        status=GrantCall.Status.PUBLISHED,
    )
    app = Application.objects.create(call=call, applicant=applicant_user, institution=institution)
    award = Award.objects.create(
        application=app,
        amount=Decimal("50000"),
        currency="USD",
        awarded_at=timezone.now(),
        project_end_date=timezone.now().date() + timedelta(days=365),
    )
    budget = Budget.objects.create(
        award=award, name="Rolling budget", currency="USD",
    )
    BudgetLine.objects.create(budget=budget, label="Personnel", amount=Decimal("30000"))
    BudgetLine.objects.create(budget=budget, label="Operations", amount=Decimal("20000"))
    return budget


@pytest.mark.django_db
def test_blended_methodology_creates_reforecast_lines(applicant_user, institution, finance_user):
    """PRD §5.4 FRFM027 — blended methodology produces N lines * M buckets rows."""
    budget = _budget_with_lines(applicant_user, institution)
    rf = generate_reforecast(budget, horizon_months=6, actor=finance_user)
    assert rf.pk
    assert rf.methodology == Reforecast.Methodology.BLENDED
    expected_rows = budget.lines.count() * 6
    assert ReforecastLine.objects.filter(reforecast=rf).count() == expected_rows
    assert AuditLog.objects.filter(action="REFORECAST_GENERATED").exists()


@pytest.mark.django_db
def test_burn_rate_methodology_uses_historical(applicant_user, institution, finance_user):
    """PRD §5.4 FRFM027 — burn_rate methodology projects forward at historical pace."""
    budget = _budget_with_lines(applicant_user, institution)
    line = budget.lines.first()
    Expenditure.objects.create(
        budget_line=line,
        amount=Decimal("600"),
        incurred_on=timezone.now().date() - timedelta(days=30),
    )
    Expenditure.objects.create(
        budget_line=line,
        amount=Decimal("600"),
        incurred_on=timezone.now().date() - timedelta(days=60),
    )
    rf = generate_reforecast(
        budget,
        horizon_months=3,
        methodology=Reforecast.Methodology.BURN_RATE,
        actor=finance_user,
    )
    line_rows = ReforecastLine.objects.filter(reforecast=rf, budget_line=line)
    assert all(r.forecasted_amount == r.historical_burn for r in line_rows)


@pytest.mark.django_db
def test_tranche_inflows_aggregated_by_bucket(applicant_user, institution, finance_user):
    """PRD §5.4 FRFM027 — project_tranche_inflows sums PaymentSchedule per month."""
    budget = _budget_with_lines(applicant_user, institution)
    today = timezone.now().date()
    PaymentSchedule.objects.create(
        budget=budget,
        due_on=today + timedelta(days=10),
        amount=Decimal("5000"),
        label="Tranche A",
    )
    PaymentSchedule.objects.create(
        budget=budget,
        due_on=today + timedelta(days=15),
        amount=Decimal("3000"),
        label="Tranche B",
    )
    inflows = project_tranche_inflows(budget, horizon_months=3, as_of=today)
    total = sum(inflows.values())
    assert total == Decimal("8000")


@pytest.mark.django_db
def test_burn_rate_for_returns_average(applicant_user, institution, finance_user):
    """PRD §5.4 FRFM027 — burn_rate_for averages expended over the lookback."""
    budget = _budget_with_lines(applicant_user, institution)
    line = budget.lines.first()
    Expenditure.objects.create(
        budget_line=line,
        amount=Decimal("1200"),
        incurred_on=timezone.now().date() - timedelta(days=30),
    )
    burn = burn_rate_for(line, lookback_months=6)
    # 1200 / 6 = 200
    assert burn == Decimal("200.00")
