"""Shared accounting-integrity checks used when cancelling (voiding) balance payments."""

from decimal import Decimal

from django.core.exceptions import ValidationError

_TOLERANCE = Decimal("0.01")


def _close(a, b):
    return abs(Decimal(str(a or 0)) - Decimal(str(b or 0))) <= _TOLERANCE


def resolve_balance_payment_transactions(
    payment,
    *,
    shop_id,
    source_type,
    payment_side_type,
    clearing_label,
    description_prefix,
):
    """
    Locate and strictly validate the two AccountTransactions created for a balance payment.

    Returns ``[payment_side_txn, clearing_side_txn]`` only when they form the exact expected,
    balanced, unreversed pair. Raises ValidationError otherwise. Never modifies any record.

    - Customer payments: payment-side CREDIT, clearing-side DEBIT.
    - Supplier payments: payment-side DEBIT, clearing-side CREDIT.
    Exact ``source_type``/``source_id`` matching is preferred; the historical description
    fallback is used only when no exact match exists and is held to identical validation.
    """
    from shop_management.models.data.account_transaction import (
        AccountTransaction,
        AccountTransactionType,
    )

    pid = payment.id
    txns = list(
        AccountTransaction.objects.select_for_update().filter(
            source_type=source_type, source_id=str(pid)
        )
    )
    historical = False
    if not txns:
        historical = True
        txns = list(
            AccountTransaction.objects.select_for_update()
            .filter(
                description__startswith=description_prefix,
                source_id="",
                reversed_transaction__isnull=True,
            )
            .exclude(source_type="REVERSAL")
        )
    kind = "historical " if historical else ""

    if not txns:
        raise ValidationError(
            f"Missing required accounting transactions for payment #{pid}. "
            "Cancellation cannot proceed safely."
        )
    if len(txns) != 2:
        raise ValidationError(
            f"Expected exactly 2 accounting transactions for {kind}payment #{pid}, found {len(txns)}."
        )
    for t in txns:
        if t.reversed_at is not None or t.reversal_entries.exists():
            raise ValidationError(
                f"Accounting transactions for {kind}payment #{pid} have already been reversed."
            )
        if t.reversed_transaction_id is not None or t.source_type == "REVERSAL":
            raise ValidationError(
                f"Accounting transaction #{t.id} for payment #{pid} is itself a reversal entry."
            )

    pay_amount = Decimal(str(payment.amount or 0))
    pay_side = [t for t in txns if t.account_id == payment.account_id]
    if len(pay_side) != 1:
        raise ValidationError(
            f"Exactly one accounting transaction must be posted on payment account "
            f"#{payment.account_id} for payment #{pid}."
        )
    pay_txn = pay_side[0]
    clearing_txn = next(t for t in txns if t.pk != pay_txn.pk)

    expected_clearing_type = (
        AccountTransactionType.DEBIT
        if payment_side_type == AccountTransactionType.CREDIT
        else AccountTransactionType.CREDIT
    )
    if pay_txn.transaction_type != payment_side_type:
        raise ValidationError(
            f"Payment-side transaction #{pay_txn.id} has unexpected direction "
            f"{pay_txn.transaction_type}; expected {payment_side_type}."
        )
    if clearing_txn.transaction_type != expected_clearing_type:
        raise ValidationError(
            f"Clearing-side transaction #{clearing_txn.id} has unexpected direction "
            f"{clearing_txn.transaction_type}; expected {expected_clearing_type}."
        )

    # Payment-side account/amount checks
    if pay_txn.account.shop_id != shop_id:
        raise ValidationError(f"Accounting transaction #{pay_txn.id} belongs to a different shop.")
    if pay_txn.account_currency_id != pay_txn.account.currency_id:
        raise ValidationError(f"Accounting transaction #{pay_txn.id} account currency is inconsistent.")
    if pay_txn.transaction_currency_id != payment.currency_id:
        raise ValidationError(f"Accounting transaction #{pay_txn.id} currency does not match payment currency.")
    if not _close(pay_txn.amount, pay_amount):
        raise ValidationError(f"Accounting transaction #{pay_txn.id} amount does not match payment amount.")
    if not _close(pay_txn.converted_amount, payment.converted_amount):
        raise ValidationError(
            f"Accounting transaction #{pay_txn.id} converted amount does not match payment."
        )

    # Clearing-side account/amount checks
    clearing_account = clearing_txn.account
    expected_name = f"{clearing_label} {payment.currency.title} Clearing"
    if clearing_account.shop_id != shop_id:
        raise ValidationError(f"Accounting transaction #{clearing_txn.id} belongs to a different shop.")
    if not clearing_account.is_system_account or clearing_account.name != expected_name:
        raise ValidationError(
            f"Accounting transaction #{clearing_txn.id} is not posted on the expected "
            f"clearing account '{expected_name}'."
        )
    if clearing_account.currency_id != payment.currency_id:
        raise ValidationError(f"Clearing account #{clearing_account.id} currency does not match payment currency.")
    if clearing_txn.account_currency_id != clearing_account.currency_id:
        raise ValidationError(f"Accounting transaction #{clearing_txn.id} account currency is inconsistent.")
    if clearing_txn.transaction_currency_id != payment.currency_id:
        raise ValidationError(f"Accounting transaction #{clearing_txn.id} currency does not match payment currency.")
    if not _close(clearing_txn.amount, pay_amount):
        raise ValidationError(f"Accounting transaction #{clearing_txn.id} amount does not match payment amount.")
    if not _close(clearing_txn.converted_amount, clearing_txn.amount):
        raise ValidationError(
            f"Clearing transaction #{clearing_txn.id} converted amount does not equal its amount."
        )

    # Balanced pair
    if not _close(pay_txn.amount, clearing_txn.amount) or (
        pay_txn.transaction_currency_id != clearing_txn.transaction_currency_id
    ):
        raise ValidationError(f"Accounting transactions for payment #{pid} do not form a balanced pair.")

    return [pay_txn, clearing_txn]
