"""PRD §5.4 FRFM008 / FRFM009 / NFRFM002 — multi-currency budget consolidation."""
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.tasks import persist_audit_log
from apps.rims.finance.models import Budget, BudgetLine, ExchangeRate
from apps.rims.finance.services import consolidate_multi_currency_budget
from apps.rims.grants.models import Award, Application, 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 _make_budget(applicant_user, institution, currency: str = "UGX") -> Budget:
    call = GrantCall.objects.create(
        title=f"6B-fx call {timezone.now().timestamp()}",
        slug=f"6b-fx-{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("1000000"),
        currency=currency,
        awarded_at=timezone.now(),
        project_end_date=timezone.now().date() + timedelta(days=365),
    )
    budget = Budget.objects.create(
        award=award, name="FX test budget", currency=currency,
    )
    BudgetLine.objects.create(budget=budget, label="Salaries", amount=Decimal("400000"))
    BudgetLine.objects.create(budget=budget, label="Equipment", amount=Decimal("250000"))
    return budget


@pytest.mark.django_db
def test_same_currency_passthrough(applicant_user, institution):
    budget = _make_budget(applicant_user, institution, currency="UGX")
    rows = consolidate_multi_currency_budget(budget, target_currency="UGX")
    assert len(rows) == 2
    for row in rows:
        assert row["converted_amount"] == row["original_amount"]
        assert row["fx_rate"] == Decimal("1")


@pytest.mark.django_db
def test_cross_currency_applies_rate(applicant_user, institution):
    budget = _make_budget(applicant_user, institution, currency="UGX")
    ExchangeRate.objects.create(
        from_currency="UGX",
        to_currency="USD",
        rate=Decimal("0.00027"),  # ~1 UGX -> 0.00027 USD
        effective_date=date.today(),
    )
    rows = consolidate_multi_currency_budget(budget, target_currency="USD")
    salaries = next(r for r in rows if r["line"].label == "Salaries")
    # 400000 * 0.00027 = 108.00
    assert salaries["converted_amount"] == Decimal("108.00")
    assert salaries["fx_rate"] == Decimal("0.00027")


@pytest.mark.django_db
def test_missing_rate_returns_none_converted(applicant_user, institution):
    budget = _make_budget(applicant_user, institution, currency="UGX")
    # No ExchangeRate row for UGX->EUR
    rows = consolidate_multi_currency_budget(budget, target_currency="EUR")
    for row in rows:
        assert row["converted_amount"] is None
        assert row["fx_rate"] is None
