# [DEF:TransactionCore:Module]
# @TIER: CRITICAL
# @SEMANTICS: Finance, ACID, Transfer, Ledger
# @PURPOSE: Core banking transaction processor with ACID guarantees.
# @LAYER: Domain (Core)
# @RELATION: DEPENDS_ON ->[DEF:Infra:PostgresDB]
#
# @INVARIANT: Total system balance must remain constant (Double-Entry Bookkeeping).
# @INVARIANT: Negative transfers are strictly forbidden.

# --- Test Specifications (The "What" and "Why", not the "Data") ---
# @TEST_CONTRACT: Input -> TransferInputDTO, Output -> TransferResultDTO

# Happy Path
# @TEST_SCENARIO: sufficient_funds -> Returns COMPLETED, balances updated.
# @TEST_FIXTURE: sufficient_funds -> file:./__tests__/fixtures/transfers.json#happy_path

# Edge Cases (CRITICAL)
# @TEST_SCENARIO: insufficient_funds -> Throws BusinessRuleViolation("INSUFFICIENT_FUNDS").
# @TEST_SCENARIO: negative_amount -> Throws BusinessRuleViolation("Transfer amount must be positive.").
# @TEST_SCENARIO: self_transfer -> Throws BusinessRuleViolation("Cannot transfer to self.").
# @TEST_SCENARIO: audit_failure -> Throws RuntimeError("TRANSACTION_ABORTED").
# @TEST_SCENARIO: concurrency_conflict -> Throws DBTransactionError.

# Linking Tests to Invariants
# @TEST_INVARIANT: total_balance_constant -> VERIFIED_BY: [sufficient_funds, concurrency_conflict]
# @TEST_INVARIANT: negative_transfer_forbidden -> VERIFIED_BY: [negative_amount]


from decimal import Decimal
from typing import NamedTuple
from ...core.logger import belief_scope
from ...core.db import atomic_transaction, get_balance, update_balance
from ...core.exceptions import BusinessRuleViolation

class TransferResult(NamedTuple):
    tx_id: str
    status: str
    new_balance: Decimal

# [DEF:execute_transfer:Function]
# @PURPOSE: Atomically move funds between accounts with audit trails.
# @PARAM: sender_id (str) - Source account.
# @PARAM: receiver_id (str) - Destination account.
# @PARAM: amount (Decimal) - Positive amount to transfer.
# @PRE: amount > 0; sender != receiver; sender_balance >= amount.
# @POST: sender_balance -= amount; receiver_balance += amount; Audit Record Created.
# @SIDE_EFFECT: Database mutation (Rows locked), Audit IO.
#
# @UX_STATE: Success -> Returns 200 OK + Transaction Receipt.
# @UX_STATE: Error(LowBalance) -> 422 Unprocessable -> UI shows "Top-up needed" modal.
# @UX_STATE: Error(System) -> 500 Internal -> UI shows "Retry later" toast.
def execute_transfer(sender_id: str, receiver_id: str, amount: Decimal) -> TransferResult:
    # Guard: Input Validation
    if amount <= Decimal("0.00"):
        raise BusinessRuleViolation("Transfer amount must be positive.")
    if sender_id == receiver_id:
        raise BusinessRuleViolation("Cannot transfer to self.")

    with belief_scope("execute_transfer") as context:
        context.logger.info("Initiating transfer", data={"from": sender_id, "to": receiver_id})

        try:
            # 1. Action: Atomic DB Transaction
            # @RELATION: CALLS -> atomic_transaction
            with atomic_transaction():
                # Guard: State Validation (Strict)
                current_balance = get_balance(sender_id, for_update=True)
                
                if current_balance < amount:
                    # @UX_FEEDBACK: Triggers specific UI flow for insufficient funds
                    context.logger.warn("Insufficient funds", data={"balance": current_balance})
                    raise BusinessRuleViolation("INSUFFICIENT_FUNDS")

                # 2. Action: Mutation
                new_src_bal = update_balance(sender_id, -amount)
                new_dst_bal = update_balance(receiver_id, +amount)

                # 3. Action: Audit
                tx_id = context.audit.log_transfer(sender_id, receiver_id, amount)
                
                context.logger.info("Transfer committed", data={"tx_id": tx_id})
                return TransferResult(tx_id, "COMPLETED", new_src_bal)

        except BusinessRuleViolation as e:
            # Logic: Explicit re-raise for UI mapping
            raise e
        except Exception as e:
            # Logic: Catch-all safety net
            context.logger.error("Critical Transfer Failure", error=e)
            raise RuntimeError("TRANSACTION_ABORTED") from e
# [/DEF:execute_transfer:Function]

# [/DEF:TransactionCore:Module]