"""
Unit of Work Pattern
====================
ADR-027: Incident-Approval 同步架構

PostgreSQL 事務管理器，確保多表操作原子性。

設計原則:
- 使用 async context manager
- 支援顯式 commit/rollback
- 與 FastAPI Depends 相容

版本: v1.0
建立: 2026-03-26 (台北時區)
"""

from typing import Any

import structlog
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

logger = structlog.get_logger(__name__)


class UnitOfWork:
    """
    PostgreSQL 事務管理器 (Unit of Work Pattern)

    用途:
    - 確保多個資料庫操作在同一事務中
    - 支援手動 commit/rollback
    - 與 Saga Pattern 配合處理外部系統失敗

    使用方式:
        async with UnitOfWork(session_factory) as uow:
            # 所有操作在同一事務
            incident = await repo.create(uow.session, data)
            approval = await repo.create(uow.session, data)

            # 外部系統操作
            try:
                await redis.set(key, value)
            except RedisError:
                await uow.rollback()  # Saga 補償
                raise

    Warning:
        - 不要在 UnitOfWork 內使用 get_db_context()，會造成嵌套事務
        - Redis 操作失敗時必須手動呼叫 rollback()
    """

    def __init__(self, session_factory: async_sessionmaker[AsyncSession]):
        """
        初始化 UnitOfWork

        Args:
            session_factory: SQLAlchemy async session factory
        """
        self._session_factory = session_factory
        self._session: AsyncSession | None = None
        self._committed = False

    @property
    def session(self) -> AsyncSession:
        """
        取得當前 Session

        Raises:
            RuntimeError: 在 context manager 外部呼叫
        """
        if self._session is None:
            raise RuntimeError("UnitOfWork must be used within async context manager")
        return self._session

    async def __aenter__(self) -> "UnitOfWork":
        """進入事務"""
        self._session = self._session_factory()
        self._committed = False
        logger.debug("uow_started")
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: Any,
    ) -> None:
        """
        離開事務

        行為:
        - 有例外: 自動 rollback
        - 無例外且未手動 commit: 自動 commit
        - 已手動 commit: 不做任何事
        """
        if exc_type is not None:
            # 有例外，rollback
            await self.rollback()
            logger.warning(
                "uow_rollback_on_exception",
                exc_type=exc_type.__name__ if exc_type else None,
                exc_val=str(exc_val) if exc_val else None,
            )
        elif not self._committed:
            # 無例外且未手動 commit，自動 commit
            await self.commit()

        # 關閉 session
        if self._session:
            await self._session.close()
            self._session = None

    async def commit(self) -> None:
        """
        提交事務

        Note:
            - 呼叫後 _committed = True，__aexit__ 不會再次 commit
            - 可多次呼叫，但只有第一次有效
        """
        if self._session and not self._committed:
            await self._session.commit()
            self._committed = True
            logger.debug("uow_committed")

    async def rollback(self) -> None:
        """
        回滾事務

        用於:
        - Saga Pattern: 外部系統 (Redis) 失敗時補償
        - 手動回滾: 業務邏輯需要

        Note:
            - 呼叫後事務已回滾，後續操作會在新事務中
            - _committed 設為 True 防止 __aexit__ 再次操作
        """
        if self._session:
            await self._session.rollback()
            self._committed = True  # 防止 __aexit__ 再次 commit
            logger.debug("uow_rolled_back")

    async def flush(self) -> None:
        """
        Flush 但不 commit

        用於:
        - 取得資料庫生成的 ID (before commit)
        - 驗證資料庫約束
        """
        if self._session:
            await self._session.flush()
