"""角色**写侧** Repository（C3 扩 / ARCH §5.4 character-gen writes=characters / §6.5 入库）。

读侧（`list_for_project`，供 assemble 注入）已在
`ww_core.memory.sql_repositories.SqlCharacterRepo` + `domain.repositories.CharacterRepo`
提供（C5 稳定，不动）。本模块加**写**能力，命名加 `Write` 前缀避歧义（同 `OutlineWriteRepo` 先例）。

**schema → DB 列形变（T5.1 gotcha，关键）**：`ww_agents.CharacterCard` 贴生成产物
（`traits`/`speech_tics` 是 `list[str]`、`arc` 是 `str`），但 `characters` 表的对应列是
JSONB **dict**（`traits`/`arc`/`speech_tics`），`tags`/`relations` 才是 JSONB **list**。
入库时本 repo 做转换层：
- `traits: list[str]`  → `{"items": [...]}`（JSONB dict）
- `speech_tics: list[str]` → `{"items": [...]}`（JSONB dict）
- `arc: str` → `{"text": "..."}`（JSONB dict）
- `tags: list` / `relations: list[dict]` → 直落 JSONB list
- `name` / `role` / `appearance` / `motive` / `backstory` → Text 列直落

**提交边界**：`create` 只 `flush()` 不 `commit()`——提交交端点事务（入库端点末尾一次
`commit()`，与网关 ledger 一并落库；与项目其它写侧 repo 一致，见 memory/gotchas）。
"""

from __future__ import annotations

import uuid
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 Character


class CharacterWriteView(BaseModel):
    """角色写入后的只读快照（snake_case，frozen）。"""

    model_config = {"frozen": True}

    id: uuid.UUID
    name: str
    role: str | None = None


def _traits_to_jsonb(items: list[str]) -> dict[str, Any]:
    """`list[str]` → JSONB dict（DB 列形）。空列表也包成 `{"items": []}` 形稳定。"""
    return {"items": list(items)}


def _arc_to_jsonb(arc: str) -> dict[str, Any]:
    """弧光一句话 `str` → JSONB dict（DB 列形）。"""
    return {"text": arc}


class CharacterWriteRepo(Protocol):
    """角色写侧接口（按 project_id 隔离；只 flush 不 commit）。

    入参贴 `ww_agents.CharacterCard`（生成产物形）；实现负责 schema → DB 列形变。

    **幂等（按 (project_id, name) upsert）**：同名重复入库 → 更新既有卡而非插重复行。
    不加 DB UNIQUE 约束（线上库已存历史重复行，约束迁移会失败）；改在 app 层
    get-by-(project_id, name) → update else insert（QA #8）。
    """

    async def create(
        self,
        project_id: uuid.UUID,
        *,
        name: str,
        role: str,
        traits: list[str],
        appearance: str,
        motive: str,
        backstory: str,
        arc: str,
        speech_tics: list[str],
        tags: list[Any],
        relations: list[dict[str, Any]],
    ) -> CharacterWriteView: ...


class SqlCharacterWriteRepo:
    """SQLAlchemy 实现：upsert 一行 `characters`（schema→DB 形变；只 flush 不 commit）。

    幂等：先按 (project_id, name) 查既有行——命中则原地更新该行字段，否则插新行。
    避免同名角色重复入库产生重复卡（QA #8；不加 DB 约束，纯 app 层）。
    """

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

    async def create(
        self,
        project_id: uuid.UUID,
        *,
        name: str,
        role: str,
        traits: list[str],
        appearance: str,
        motive: str,
        backstory: str,
        arc: str,
        speech_tics: list[str],
        tags: list[Any],
        relations: list[dict[str, Any]],
    ) -> CharacterWriteView:
        existing = (
            await self._s.execute(
                select(Character).where(Character.project_id == project_id, Character.name == name)
            )
        ).scalar_one_or_none()
        if existing is not None:
            existing.role = role
            existing.traits = _traits_to_jsonb(traits)
            existing.appearance = appearance
            existing.motive = motive
            existing.backstory = backstory
            existing.arc = _arc_to_jsonb(arc)
            existing.speech_tics = _traits_to_jsonb(speech_tics)
            existing.tags = list(tags)
            existing.relations = [dict(r) for r in relations]
            await self._s.flush()
            await self._s.refresh(existing)
            return CharacterWriteView(id=existing.id, name=existing.name, role=existing.role)
        row = Character(
            project_id=project_id,
            name=name,
            role=role,
            traits=_traits_to_jsonb(traits),
            appearance=appearance,
            motive=motive,
            backstory=backstory,
            arc=_arc_to_jsonb(arc),
            speech_tics=_traits_to_jsonb(speech_tics),
            tags=list(tags),
            relations=[dict(r) for r in relations],
        )
        self._s.add(row)
        await self._s.flush()
        await self._s.refresh(row)
        return CharacterWriteView(id=row.id, name=row.name, role=row.role)


# ---- request-shaping helper（schema 字段名导出供测试/端点共用）----


class CharacterWriteFields(BaseModel):
    """从 `CharacterCard` 拆出的入库字段（端点把 schema 卡转成此 kwargs 形）。"""

    name: str
    role: str
    traits: list[str] = Field(default_factory=list)
    appearance: str = ""
    motive: str = ""
    backstory: str
    arc: str
    speech_tics: list[str] = Field(default_factory=list)
    tags: list[Any] = Field(default_factory=list)
    relations: list[dict[str, Any]] = Field(default_factory=list)
