"""本章注入覆盖（B0 可控版）的写/读 Repository（ARCH §3.4 作者手动 pin 兜底）。

作者在「本章注入（透明）」面板里对确定性自动选择做微调——把某实体强制纳入（pin）、
强制剔除（excluded）、或覆盖近况回看章数（recent_n）。这是不变量 #6「确定性选择」的
**作者兜底**：override 本身是确定性输入的一部分（同一 (project_id, chapter_no) 存一行），
故「看到的=写章用的」仍成立——draft 端点与注入读端点都读同一覆盖。

**提交边界**：`upsert` 只 `flush()` 不 `commit()`——提交交端点（与 outline/rule 等写侧一致，
见 memory/gotchas）。唯一约束 `(project_id, chapter_no)`：显式 read-modify-write upsert。

**与 beats 隔离**：单独建 `chapter_injection` 表存覆盖，**不复用 outline 行**——复用会污染
beats/prompt（已否决，见 memory/decisions）。
"""

from __future__ import annotations

import uuid
from typing import Protocol

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


class EntityRef(BaseModel):
    """一个被作者点名 pin/排除的实体引用（kind + name；frozen 便于入 set 去重）。"""

    model_config = {"frozen": True}

    kind: str
    name: str


class InjectionOverride(BaseModel):
    """作者对本章确定性注入的覆盖（pin 强制纳入 / excluded 强制剔除 / recent_n 覆盖回看章数）。

    全可选——空覆盖等价于「纯自动选择」。recent_n=None → 用默认 RECENT_DIGEST_COUNT。
    """

    model_config = {"frozen": True}

    pinned: list[EntityRef] = Field(default_factory=list)
    excluded: list[EntityRef] = Field(default_factory=list)
    recent_n: int | None = None


class InjectionOverrideRepo(Protocol):
    """注入覆盖读/写接口（project_id 隔离；唯一 (project_id, chapter_no)；只 flush 不 commit）。"""

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

    async def upsert(
        self, project_id: uuid.UUID, chapter_no: int, override: InjectionOverride
    ) -> InjectionOverride: ...


def _refs(raw: object) -> list[EntityRef]:
    """JSONB list[{kind,name}] → list[EntityRef]（容错：跳过形不对的项，不抛）。"""
    if not isinstance(raw, list):
        return []
    out: list[EntityRef] = []
    for item in raw:
        if isinstance(item, dict) and "kind" in item and "name" in item:
            out.append(EntityRef(kind=str(item["kind"]), name=str(item["name"])))
    return out


def _to_override(row: ChapterInjection) -> InjectionOverride:
    return InjectionOverride(
        pinned=_refs(row.pinned),
        excluded=_refs(row.excluded),
        recent_n=row.recent_n,
    )


class SqlInjectionOverrideRepo:
    """SQLAlchemy 实现：显式 read-modify-write upsert（只 flush 不 commit）。"""

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

    async def get(self, project_id: uuid.UUID, chapter_no: int) -> InjectionOverride | None:
        row = (
            await self._s.execute(
                select(ChapterInjection).where(
                    ChapterInjection.project_id == project_id,
                    ChapterInjection.chapter_no == chapter_no,
                )
            )
        ).scalar_one_or_none()
        return _to_override(row) if row is not None else None

    async def upsert(
        self, project_id: uuid.UUID, chapter_no: int, override: InjectionOverride
    ) -> InjectionOverride:
        # JSONB 写入建新对象重赋值（避免原地 mutate 不被 ORM 侦测，见 gotcha）。
        pinned_payload = [r.model_dump() for r in override.pinned]
        excluded_payload = [r.model_dump() for r in override.excluded]
        row = (
            await self._s.execute(
                select(ChapterInjection).where(
                    ChapterInjection.project_id == project_id,
                    ChapterInjection.chapter_no == chapter_no,
                )
            )
        ).scalar_one_or_none()
        if row is None:
            row = ChapterInjection(
                project_id=project_id,
                chapter_no=chapter_no,
                pinned=pinned_payload,
                excluded=excluded_payload,
                recent_n=override.recent_n,
            )
            self._s.add(row)
        else:
            row.pinned = pinned_payload
            row.excluded = excluded_payload
            row.recent_n = override.recent_n
        await self._s.flush()
        await self._s.refresh(row)
        return _to_override(row)
