"""PRD §5.4 FRFM042/043 — retry queue FSM + Celery sweep + abandonment."""
from __future__ import annotations

from datetime import 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,
    DisbursementExecution,
    DisbursementRequest,
    PaymentRetry,
)
from apps.rims.finance.services import (
    abandon_payment_retry,
    attempt_payment_retry,
    mark_execution_failed,
    queue_payment_retry,
)
from apps.rims.finance.tasks import process_payment_retry_queue
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 _exec(applicant_user, institution, finance_user) -> DisbursementExecution:
    call = GrantCall.objects.create(
        title=f"6E-q call {timezone.now().timestamp()}",
        slug=f"6e-q-{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("3000"),
        currency="USD",
        awarded_at=timezone.now(),
        project_end_date=timezone.now().date() + timedelta(days=365),
    )
    budget = Budget.objects.create(award=award, name="Queue budget", currency="USD")
    BudgetLine.objects.create(budget=budget, label="L", amount=Decimal("3000"))
    request = DisbursementRequest.objects.create(
        budget=budget,
        amount=Decimal("500"),
        requested_by=finance_user,
        status=DisbursementRequest.Status.APPROVED,
    )
    return DisbursementExecution.objects.create(
        request=request,
        status=DisbursementExecution.Status.SCHEDULED,
    )


@pytest.mark.django_db
def test_queue_attempt_abandon_transitions(applicant_user, institution, finance_user):
    """PRD §5.4 FRFM042/043 — FSM: QUEUED → RETRYING → ABANDONED."""
    execution = _exec(applicant_user, institution, finance_user)
    retry = queue_payment_retry(execution)
    assert retry.status == PaymentRetry.Status.QUEUED

    attempt_payment_retry(retry, actor=finance_user)
    retry.refresh_from_db()
    assert retry.status == PaymentRetry.Status.RETRYING
    assert retry.attempts == 1
    assert AuditLog.objects.filter(action="PAYMENT_RETRY_ATTEMPTED").exists()

    abandon_payment_retry(retry, actor=finance_user, reason="No response from bank")
    retry.refresh_from_db()
    assert retry.status == PaymentRetry.Status.ABANDONED
    assert retry.resolved_by == finance_user
    assert AuditLog.objects.filter(action="PAYMENT_RETRY_ABANDONED").exists()


@pytest.mark.django_db
def test_celery_sweep_advances_queued_to_retrying(applicant_user, institution, finance_user):
    """PRD §5.4 FRFM043 — process_payment_retry_queue bumps QUEUED rows."""
    execution = _exec(applicant_user, institution, finance_user)
    mark_execution_failed(
        execution,
        actor=finance_user,
        failure_category=DisbursementExecution.FailureCategory.WRONG_ACCOUNT,
        failure_reason="Account number mismatch",
    )
    count = process_payment_retry_queue()
    assert count == 1
    PaymentRetry.objects.filter(execution=execution).update()  # refresh
    retry = PaymentRetry.objects.get(execution=execution)
    assert retry.status == PaymentRetry.Status.RETRYING
