"""把独立事务商业结算绑定到调用方业务事务终态。"""

from __future__ import annotations

import logging
from collections.abc import Callable

from sqlalchemy import event
from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)
_CALLBACK_BATCH_KEY = "commercial_transaction_callback_batch"


def bind_commercial_transaction_outcome(
    db: Session,
    *,
    on_commit: Callable[[], None],
    on_rollback: Callable[[], None],
    operation_name: str,
) -> None:
    """业务提交后才结算；业务回滚只释放预占，不生成用量事实。"""

    # 纯文件删除可能发生在尚未执行 SQL 的 Session 中。显式取得连接以建立
    # 外层事务，确保随后 rollback/commit 一定产生可观察的终态回调。
    db.connection()
    batch = db.info.get(_CALLBACK_BATCH_KEY)
    if not isinstance(batch, dict) or batch.get("settled") is True:
        batch = {"settled": False, "callbacks": []}
        db.info[_CALLBACK_BATCH_KEY] = batch
        _listen_for_transaction_outcome(db, batch)
    callbacks = batch["callbacks"]
    if not isinstance(callbacks, list):  # pragma: no cover - defensive corruption guard
        raise RuntimeError("商业事务回调批次状态损坏。")
    callbacks.append((operation_name, on_commit, on_rollback))


def _listen_for_transaction_outcome(db: Session, batch: dict) -> None:
    def settle(outcome: str) -> None:
        if batch["settled"]:
            return
        batch["settled"] = True
        entries = list(batch["callbacks"])
        if outcome == "rolled_back":
            entries.reverse()
        callback_index = 1 if outcome == "committed" else 2
        for operation_name, on_commit, on_rollback in entries:
            callback = (on_commit, on_rollback)[callback_index - 1]
            try:
                callback()
            except Exception:
                # 业务事务已经进入不可逆终态。商业预占保持可审计状态，交由补偿器处理；
                # 不能把结算异常伪装成业务事务失败并诱发客户端重复写入。
                logger.exception(
                    "commercial_transaction_callback_failed operation=%s outcome=%s",
                    operation_name,
                    outcome,
                )

    def after_commit(_session: Session) -> None:
        settle("committed")

    def after_rollback(_session: Session) -> None:
        settle("rolled_back")

    event.listen(db, "after_commit", after_commit, once=True)
    event.listen(db, "after_rollback", after_rollback, once=True)
