"""PRD §5.4 FRFM008 — ExchangeRate admin (FinanceApprovalMixin RBAC + audit emit)."""
from __future__ import annotations

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

import pytest
from django.contrib.auth import get_user_model
from django.urls import reverse

from apps.core.audit.models import AuditLog
from apps.core.audit.tasks import persist_audit_log
from apps.core.authentication.models import MFADevice
from apps.core.permissions.roles import UserRole
from apps.rims.finance.models import ExchangeRate
from apps.rims.finance.services import upsert_exchange_rate

User = get_user_model()


@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


@pytest.fixture
def finance_manager_user(db, institution):
    u = User.objects.create_user(email="finmgr.6b@example.com", password="pw-6b")
    u.role = UserRole.FINANCE_MANAGER
    u.save()
    MFADevice.objects.get_or_create(
        user=u,
        device_type=MFADevice.DeviceType.TOTP,
        defaults={"name": "Test TOTP", "secret": "JBSWY3DPEHPK3PXP", "is_verified": True},
    )
    return u


@pytest.mark.django_db
def test_upsert_creates_rate_and_emits_audit(finance_manager_user):
    rate = upsert_exchange_rate(
        actor=finance_manager_user,
        from_currency="USD",
        to_currency="UGX",
        rate=Decimal("3712.50"),
        effective_date=date(2026, 5, 18),
        source="BoU 2026-05-18",
    )
    assert rate.pk
    assert AuditLog.objects.filter(action="EXCHANGE_RATE_UPDATED").exists()


@pytest.mark.django_db
def test_upsert_is_idempotent_on_same_triple(finance_manager_user):
    first = upsert_exchange_rate(
        actor=finance_manager_user,
        from_currency="USD",
        to_currency="UGX",
        rate=Decimal("3712.50"),
        effective_date=date(2026, 5, 18),
    )
    second = upsert_exchange_rate(
        actor=finance_manager_user,
        from_currency="USD",
        to_currency="UGX",
        rate=Decimal("3800.00"),
        effective_date=date(2026, 5, 18),
    )
    assert first.pk == second.pk
    assert ExchangeRate.objects.filter(
        from_currency="USD", to_currency="UGX", effective_date=date(2026, 5, 18)
    ).count() == 1
    second.refresh_from_db()
    assert second.rate == Decimal("3800.00000000")


@pytest.mark.django_db
def test_finance_officer_cannot_create_rate(client, finance_user):
    """PRD §5.4 FRFM008 RBAC — FinanceApprovalMixin blocks FINANCE_OFFICER from
    write paths; only FINANCE_MANAGER tier can edit the FX roster."""
    MFADevice.objects.get_or_create(
        user=finance_user,
        device_type=MFADevice.DeviceType.TOTP,
        defaults={"name": "Test TOTP", "secret": "JBSWY3DPEHPK3PXP", "is_verified": True},
    )
    client.force_login(finance_user)
    resp = client.post(
        reverse("rims_finance:exchange_rate_create"),
        {
            "from_currency": "USD",
            "to_currency": "UGX",
            "rate": "3700",
            "effective_date": "2026-05-18",
            "source": "test",
        },
    )
    assert resp.status_code in (302, 403)
    # Even if redirected, no rate should be created.
    assert not ExchangeRate.objects.filter(
        from_currency="USD", to_currency="UGX", effective_date=date(2026, 5, 18)
    ).exists()


@pytest.mark.django_db
def test_finance_manager_can_create_rate_via_view(client, finance_manager_user):
    client.force_login(finance_manager_user)
    resp = client.post(
        reverse("rims_finance:exchange_rate_create"),
        {
            "from_currency": "USD",
            "to_currency": "UGX",
            "rate": "3712.50",
            "effective_date": "2026-05-18",
            "source": "BoU 2026-05-18",
        },
    )
    assert resp.status_code == 302
    assert ExchangeRate.objects.filter(
        from_currency="USD", to_currency="UGX", effective_date=date(2026, 5, 18)
    ).exists()
