"""Application outbox 端口的 SQLAlchemy 持久化适配器。"""

from datetime import datetime

from sqlalchemy import or_, select, update
from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession

from app.application.outbox import ClaimedOutboxMessage, OutboxIntent
from app.db.base import execute_dml
from app.db.models.outbox import OutboxMessage


def _iso(value: datetime) -> str:
    """将带时区时间统一序列化为可排序 ISO 字符串。"""
    return value.isoformat()


class SqlAlchemyOutboxRepository:
    """使用调用方 Session 原子暂存并条件认领 outbox。"""

    def __init__(self, session: Session) -> None:
        """保存由调用方拥有的 SQLAlchemy Session。"""
        self._session = session

    def stage(self, intent: OutboxIntent, now: datetime) -> None:
        """加入当前事务并 flush，使唯一键冲突在业务 commit 前暴露。"""
        self._session.add(
            OutboxMessage(
                event_key=intent.event_key,
                topic=intent.topic,
                payload_version=intent.payload_version,
                payload=intent.payload,
                status="pending",
                attempt=0,
                next_retry_at=_iso(now),
                created_at=_iso(now),
            )
        )
        self._session.flush()

    def claim(
        self,
        now: datetime,
        lease_until: datetime,
    ) -> ClaimedOutboxMessage | None:
        """条件更新候选行；并发丢失竞争时返回 None。"""
        now_text = _iso(now)
        candidate = self._session.execute(
            select(OutboxMessage)
            .where(
                OutboxMessage.status.in_(("pending", "processing")),
                OutboxMessage.next_retry_at <= now_text,
                or_(
                    OutboxMessage.lease_until.is_(None),
                    OutboxMessage.lease_until <= now_text,
                ),
            )
            .order_by(OutboxMessage.id)
            .limit(1)
        ).scalars().first()
        if candidate is None:
            return None
        next_attempt = candidate.attempt + 1
        claimed = execute_dml(
            self._session,
            update(OutboxMessage)
            .where(
                OutboxMessage.id == candidate.id,
                OutboxMessage.attempt == candidate.attempt,
                or_(
                    OutboxMessage.lease_until.is_(None),
                    OutboxMessage.lease_until <= now_text,
                ),
            )
            .values(
                status="processing",
                attempt=next_attempt,
                lease_until=_iso(lease_until),
            ),
        )
        self._session.commit()
        if not claimed:
            return None
        return ClaimedOutboxMessage(
            message_id=candidate.id,
            event_key=candidate.event_key,
            topic=candidate.topic,
            payload=dict(candidate.payload),
            payload_version=candidate.payload_version,
            attempt=next_attempt,
        )

    def complete(self, message_id: int, completed_at: datetime) -> None:
        """持久化完成终态并释放 lease。"""
        self._session.execute(
            update(OutboxMessage)
            .where(OutboxMessage.id == message_id)
            .values(status="completed", completed_at=_iso(completed_at), lease_until=None)
        )
        self._session.commit()

    def complete_by_event_key(self, event_key: str, completed_at: datetime) -> None:
        """即时 post-commit 全部成功时按幂等键收口对应 intent。"""
        self._session.execute(
            update(OutboxMessage)
            .where(OutboxMessage.event_key == event_key)
            .values(status="completed", completed_at=_iso(completed_at), lease_until=None)
        )
        self._session.commit()

    def retry(
        self,
        message_id: int,
        *,
        next_retry_at: datetime,
        last_error: str,
        dead: bool,
    ) -> None:
        """持久化下一次退避或不可自动重试的 dead 终态。"""
        self._session.execute(
            update(OutboxMessage)
            .where(OutboxMessage.id == message_id)
            .values(
                status="dead" if dead else "pending",
                next_retry_at=_iso(next_retry_at),
                lease_until=None,
                last_error=last_error,
            )
        )
        self._session.commit()


class SqlAlchemyAsyncOutboxStager:
    """只负责把 outbox 意图加入调用方异步事务。"""

    def __init__(self, session: AsyncSession) -> None:
        """保存由异步订阅命令拥有的 Session。"""
        self._session = session

    async def stage(self, intent: OutboxIntent, now: datetime) -> None:
        """暂存并 flush，确保业务行与意图由同一次 commit 决定。"""
        self._session.add(
            OutboxMessage(
                event_key=intent.event_key,
                topic=intent.topic,
                payload_version=intent.payload_version,
                payload=intent.payload,
                status="pending",
                attempt=0,
                next_retry_at=_iso(now),
                created_at=_iso(now),
            )
        )
        await self._session.flush()

    async def complete_by_event_key(
        self,
        event_key: str,
        completed_at: datetime,
    ) -> None:
        """异步 post-commit 全部成功时按幂等键收口 intent。"""
        await self._session.execute(
            update(OutboxMessage)
            .where(OutboxMessage.event_key == event_key)
            .values(status="completed", completed_at=_iso(completed_at), lease_until=None)
        )
        await self._session.commit()
