"""章节草稿/验收 Repository 协议 + SQLAlchemy async 实现（ARCH §3.3 / §5.5 / §7.2）。

- 草稿：自动保存把流式产出的文本落到 `chapters`（status='draft'）。幂等：同一
  (project_id, chapter_no) 的草稿重复保存覆盖同一行、版次固定为草稿版次。
- 验收晋升（M2/T2.3）：`promote_to_accepted` 计算该章 max(version)+1，插入 status='accepted'
  的**新行**（草稿行保留，符合「多版本行」§3.3）。真正「何时晋升 + 从终稿提炼 digest」
  在 T2.4 验收事务里组合调用——本 repo 只提供确定性的版次计算 + 插入能力。
"""

from __future__ import annotations

import uuid
from dataclasses import dataclass
from typing import Protocol

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from ww_db.models import Chapter

# 草稿固定版次（accepted 版本提升属 M2，不在此处递增）。
DRAFT_STATUS = "draft"
DRAFT_VERSION = 1
ACCEPTED_STATUS = "accepted"


@dataclass(frozen=True)
class ChapterDraftView:
    """只读章节草稿快照（snake_case）。"""

    project_id: uuid.UUID
    chapter_no: int
    volume: int
    content: str
    status: str
    version: int


@dataclass(frozen=True)
class ChapterView:
    """只读章节版本快照（任意 status/version；验收晋升返回此形）。"""

    project_id: uuid.UUID
    chapter_no: int
    volume: int
    content: str
    status: str
    version: int


class ChapterRepo(Protocol):
    """章节草稿读写 + 验收晋升接口（按 project_id 隔离）。

    写方法（save_draft/promote_to_accepted）**只 flush 不 commit**——提交交调用方
    （端点/验收事务）统一一次，保证原子性与全仓「仓储只 flush」契约一致。
    """

    async def save_draft(
        self, project_id: uuid.UUID, chapter_no: int, *, text: str, volume: int = 1
    ) -> ChapterDraftView: ...

    async def get_draft(
        self, project_id: uuid.UUID, chapter_no: int
    ) -> ChapterDraftView | None: ...

    async def max_version(self, project_id: uuid.UUID, chapter_no: int) -> int:
        """该章现有最大 version（无任何行则 0）——验收取 max+1。"""
        ...

    async def promote_to_accepted(
        self, project_id: uuid.UUID, chapter_no: int, *, content: str, volume: int = 1
    ) -> ChapterView:
        """从终稿晋升：插入 status='accepted'、version=max+1 的新行（草稿行保留）。"""
        ...

    async def latest_accepted(self, project_id: uuid.UUID, chapter_no: int) -> ChapterView | None:
        """该章最新（最高 version）的 accepted 版本，无则 None。"""
        ...


def _to_view(row: Chapter) -> ChapterDraftView:
    return ChapterDraftView(
        project_id=row.project_id,
        chapter_no=row.chapter_no,
        volume=row.volume,
        content=row.content or "",
        status=row.status,
        version=row.version,
    )


def _to_chapter_view(row: Chapter) -> ChapterView:
    return ChapterView(
        project_id=row.project_id,
        chapter_no=row.chapter_no,
        volume=row.volume,
        content=row.content or "",
        status=row.status,
        version=row.version,
    )


class SqlChapterRepo:
    """SQLAlchemy 实现：草稿幂等 upsert（显式 read-modify-write，版次固定）。"""

    def __init__(self, session: AsyncSession) -> None:
        self._s = session

    async def _find_draft(self, project_id: uuid.UUID, chapter_no: int) -> Chapter | None:
        return (
            await self._s.execute(
                select(Chapter).where(
                    Chapter.project_id == project_id,
                    Chapter.chapter_no == chapter_no,
                    Chapter.version == DRAFT_VERSION,
                )
            )
        ).scalar_one_or_none()

    async def save_draft(
        self, project_id: uuid.UUID, chapter_no: int, *, text: str, volume: int = 1
    ) -> ChapterDraftView:
        # 唯一约束 (project_id, chapter_no, version)；草稿版次固定 → 覆盖同一行，幂等。
        # **只 flush 不 commit**：提交交调用方（端点）统一一次，与全仓「仓储只 flush」契约一致。
        existing = await self._find_draft(project_id, chapter_no)
        if existing is None:
            row = Chapter(
                project_id=project_id,
                volume=volume,
                chapter_no=chapter_no,
                content=text,
                status=DRAFT_STATUS,
                version=DRAFT_VERSION,
            )
            self._s.add(row)
            await self._s.flush()
            await self._s.refresh(row)
            return _to_view(row)
        existing.content = text
        existing.status = DRAFT_STATUS
        await self._s.flush()
        await self._s.refresh(existing)
        return _to_view(existing)

    async def get_draft(self, project_id: uuid.UUID, chapter_no: int) -> ChapterDraftView | None:
        row = await self._find_draft(project_id, chapter_no)
        if row is None:
            return None
        return _to_view(row)

    async def max_version(self, project_id: uuid.UUID, chapter_no: int) -> int:
        # 消除 read-then-insert 的 TOCTOU 竞态：`SELECT version ... FOR UPDATE` 锁住该章
        # 现有行（PG 不允许聚合函数与 FOR UPDATE 并用，故选具体行加锁再 Python 取 max）。
        # 并发验收同一章时，后到者阻塞到前者提交后才读到更新后的 max（避免撞唯一约束抛 500）。
        rows = (
            await self._s.execute(
                select(Chapter.version)
                .where(
                    Chapter.project_id == project_id,
                    Chapter.chapter_no == chapter_no,
                )
                .with_for_update()
            )
        ).scalars()
        versions = list(rows)
        return max(versions) if versions else 0

    async def promote_to_accepted(
        self, project_id: uuid.UUID, chapter_no: int, *, content: str, volume: int = 1
    ) -> ChapterView:
        # 取 max+1 晋升新 accepted 行；草稿行保留（多版本行，§3.3）。
        # 唯一约束 (project_id, chapter_no, version) 由 DB 强制。
        # **只 flush 不 commit**：验收事务（T2.4）把晋升+digest+裁决留痕作为单事务提交。
        next_version = (await self.max_version(project_id, chapter_no)) + 1
        row = Chapter(
            project_id=project_id,
            volume=volume,
            chapter_no=chapter_no,
            content=content,
            status=ACCEPTED_STATUS,
            version=next_version,
        )
        self._s.add(row)
        await self._s.flush()
        await self._s.refresh(row)
        return _to_chapter_view(row)

    async def latest_accepted(self, project_id: uuid.UUID, chapter_no: int) -> ChapterView | None:
        row = (
            await self._s.execute(
                select(Chapter)
                .where(
                    Chapter.project_id == project_id,
                    Chapter.chapter_no == chapter_no,
                    Chapter.status == ACCEPTED_STATUS,
                )
                .order_by(Chapter.version.desc())
                .limit(1)
            )
        ).scalar_one_or_none()
        if row is None:
            return None
        return _to_chapter_view(row)
