"""PRD §5.4 FRFM046 / FRFM049 — General-ledger posting primitives.

A DisbursementExecution flipped to EXECUTED triggers ``post_gl_entries``,
which writes a double-entry pair: debit the expense account (BudgetLine
account_code or budget account_reference) and credit the cash/bank account.
The daily Celery beat (`revalidate_gl_entries`) re-checks every recent
posted row and flips broken pairs to ``flagged``.
"""
from __future__ import annotations

from decimal import Decimal
from typing import Iterable

from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils import timezone

from apps.rims.finance.models import DisbursementExecution, GLEntry


_CASH_ACCOUNT_DEFAULT = "1000-CASH"


def _expense_account_for(execution: DisbursementExecution) -> str:
    """Pick the most-specific account code available."""
    req = execution.request
    if req.account_reference:
        return req.account_reference[:32]
    if req.budget.account_reference:
        return req.budget.account_reference[:32]
    return "6000-DISB"


@transaction.atomic
def post_gl_entries(execution: DisbursementExecution, *, actor=None) -> list[GLEntry]:
    """PRD §5.4 FRFM046 — write a debit + credit pair for an executed payment.

    Returns the two GLEntry rows in order [debit, credit].
    """
    req = execution.request
    amount = Decimal(req.amount or 0)
    if amount <= 0:
        raise ValidationError("GL posting requires a positive amount.")
    currency = req.budget.currency or "USD"
    expense_acct = _expense_account_for(execution)
    narrative = f"Disbursement #{req.pk} — {req.budget.name[:60]}"

    debit = GLEntry.objects.create(
        execution=execution,
        account_code=expense_acct,
        debit=amount,
        credit=Decimal("0"),
        currency=currency,
        narrative=narrative,
        posted_by=actor,
        validation_status=GLEntry.ValidationStatus.POSTED,
    )
    credit = GLEntry.objects.create(
        execution=execution,
        account_code=_CASH_ACCOUNT_DEFAULT,
        debit=Decimal("0"),
        credit=amount,
        currency=currency,
        narrative=narrative,
        posted_by=actor,
        validation_status=GLEntry.ValidationStatus.POSTED,
    )
    try:
        from apps.rims.grants.audit_helper import audit_rims

        audit_rims(
            actor=actor,
            action="GL_POSTED",
            target_model="DisbursementExecution",
            object_id=execution.pk,
            object_repr=str(execution),
            changes={
                "amount": str(amount),
                "currency": currency,
                "debit_account": expense_acct,
                "credit_account": _CASH_ACCOUNT_DEFAULT,
                "gl_entry_ids": [debit.pk, credit.pk],
            },
        )
    except Exception:  # pragma: no cover
        pass
    return [debit, credit]


def revalidate_gl_entry(entry: GLEntry) -> GLEntry:
    """PRD §5.4 FRFM049 — re-validate balance + currency on a single GL entry.

    Flips ``validation_status`` to ``flagged`` if the paired sibling entry
    cannot be located or the totals do not balance.
    """
    siblings = GLEntry.objects.filter(execution=entry.execution).exclude(pk=entry.pk)
    total_debit = sum((Decimal(e.debit or 0) for e in siblings), Decimal(entry.debit or 0))
    total_credit = sum((Decimal(e.credit or 0) for e in siblings), Decimal(entry.credit or 0))
    if total_debit != total_credit:
        entry.validation_status = GLEntry.ValidationStatus.FLAGGED
        entry.validation_notes = (
            f"Unbalanced: debit={total_debit} credit={total_credit}"
        )
        entry.save(update_fields=["validation_status", "validation_notes"])
        try:
            from apps.rims.grants.audit_helper import audit_rims

            audit_rims(
                actor=None,
                action="GL_FLAGGED",
                target_model="GLEntry",
                object_id=entry.pk,
                object_repr=str(entry),
                changes={"reason": "unbalanced", "debit": str(total_debit), "credit": str(total_credit)},
            )
        except Exception:  # pragma: no cover
            pass
    return entry


@transaction.atomic
def void_gl_entry(entry: GLEntry, *, actor, reason: str) -> GLEntry:
    """PRD §5.4 FRFM046 — mark a GL entry voided.

    Voiding preserves the row (audit chain) but flips the status so reports
    exclude it. Reversal flows (services.reverse_payment_batch) call this
    before any cascade that would touch the parent execution.
    """
    entry.validation_status = GLEntry.ValidationStatus.VOIDED
    entry.validation_notes = (reason or "")[:500]
    entry.save(update_fields=["validation_status", "validation_notes"])
    try:
        from apps.rims.grants.audit_helper import audit_rims

        audit_rims(
            actor=actor,
            action="GL_VOIDED",
            target_model="GLEntry",
            object_id=entry.pk,
            object_repr=str(entry),
            changes={"reason": (reason or "")[:200]},
        )
    except Exception:  # pragma: no cover
        pass
    return entry
