"""作者↔AI 往复聊天记录 Repository（append-only 侧表；AC-2 / ai-chat-history-plan §3.1）。

`ai_messages` 是「说了什么」的真源，**非手稿真源**：assemble()/agent 永不读它（守 #1/#3/#6）。
两操作：
- `append`：一次交换的多条 bubble 作为**一批**落库，批内 `seq` = enumerate 0 基位置、同
  `thread_id`；只 `flush()`（提交交端点事务）。Postgres `now()` 事务起始恒定 → 同批共享
  `created_at`，靠 `seq` 批内定序；跨批 `created_at` 不同自然定序。
- `list_for_chapter`：按 project(+可选 chapter_no) 过滤，`(created_at DESC, seq DESC)`
  newest-first + `limit/offset` 分页。`chapter_no` 非 None → 本章 ∪ 项目级(NULL)；None →
  仅项目级。可选 `kind` 过滤。

视图 frozen、snake_case、与 ORM 解耦（路由不碰 SQLAlchemy 行）。JSONB `meta` 写入建**新
dict**（不原地 mutate，见 memory/gotchas）。
"""

from __future__ import annotations

import uuid
from datetime import datetime
from typing import Any, Protocol

from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from ww_db.models import AiMessage


class AiTurnRow(BaseModel):
    """一批中的一条消息（批级字段 thread_id/chapter_no/kind/tool_key 作 `append` 形参）。"""

    role: str
    content: str
    meta: dict[str, Any] = Field(default_factory=dict)


class AiMessageView(BaseModel):
    """聊天留痕只读快照（snake_case，frozen）——镜像端点出参字段。"""

    model_config = {"frozen": True}

    id: uuid.UUID
    project_id: uuid.UUID
    chapter_no: int | None
    thread_id: uuid.UUID
    seq: int
    kind: str
    tool_key: str | None
    role: str
    content: str
    meta: dict[str, Any]
    created_at: datetime


class AiMessageRepo(Protocol):
    """聊天留痕读写接口（append 批 + newest-first 列表；append 只 flush，端点提交）。"""

    async def append(
        self,
        project_id: uuid.UUID,
        *,
        thread_id: uuid.UUID,
        chapter_no: int | None,
        kind: str,
        tool_key: str | None,
        rows: list[AiTurnRow],
    ) -> list[AiMessageView]:
        """一批消息 append（seq = 批内 0 基位置；同 thread_id）。只 flush 不 commit。"""
        ...

    async def list_for_chapter(
        self,
        project_id: uuid.UUID,
        chapter_no: int | None,
        *,
        kind: str | None = None,
        limit: int,
        offset: int,
    ) -> list[AiMessageView]:
        """按 project(+chapter_no) 过滤，newest-first（created_at DESC, seq DESC）分页。"""
        ...


def _to_view(row: AiMessage) -> AiMessageView:
    return AiMessageView(
        id=row.id,
        project_id=row.project_id,
        chapter_no=row.chapter_no,
        thread_id=row.thread_id,
        seq=row.seq,
        kind=row.kind,
        tool_key=row.tool_key,
        role=row.role,
        content=row.content,
        meta=dict(row.meta or {}),
        created_at=row.created_at,
    )


class SqlAiMessageRepo:
    """SQLAlchemy 实现：批 append + newest-first 列表（只 flush 不 commit）。"""

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

    async def append(
        self,
        project_id: uuid.UUID,
        *,
        thread_id: uuid.UUID,
        chapter_no: int | None,
        kind: str,
        tool_key: str | None,
        rows: list[AiTurnRow],
    ) -> list[AiMessageView]:
        orm_rows = [
            AiMessage(
                project_id=project_id,
                chapter_no=chapter_no,
                thread_id=thread_id,
                seq=i,  # 批内 0 基位置：同批共享 created_at，靠 seq 稳定定序。
                kind=kind,
                tool_key=tool_key,
                role=row.role,
                content=row.content,
                meta=dict(row.meta),  # 新 dict，避免原地 mutate（JSONB gotcha）。
            )
            for i, row in enumerate(rows)
        ]
        self._s.add_all(orm_rows)
        await self._s.flush()
        for row_ in orm_rows:
            await self._s.refresh(row_)  # 回填 id / created_at。
        return [_to_view(r) for r in orm_rows]

    async def list_for_chapter(
        self,
        project_id: uuid.UUID,
        chapter_no: int | None,
        *,
        kind: str | None = None,
        limit: int,
        offset: int,
    ) -> list[AiMessageView]:
        stmt = select(AiMessage).where(AiMessage.project_id == project_id)
        if chapter_no is not None:
            # 本章 ∪ 项目级(NULL)——正是抽屉 union。
            stmt = stmt.where(
                (AiMessage.chapter_no == chapter_no) | (AiMessage.chapter_no.is_(None))
            )
        else:
            stmt = stmt.where(AiMessage.chapter_no.is_(None))  # 工具箱页仅项目级。
        if kind is not None:
            stmt = stmt.where(AiMessage.kind == kind)
        stmt = (
            stmt.order_by(AiMessage.created_at.desc(), AiMessage.seq.desc())
            .limit(limit)
            .offset(offset)
        )
        rows = (await self._s.execute(stmt)).scalars().all()
        return [_to_view(r) for r in rows]
