"""规则**写侧** Repository（C3 扩 / PRODUCT_SPEC §7 `POST /projects/:id/rules`）。

读侧（`all_for_project`，供 assemble 注入 + `merge_rules` 四级合并）已在
`ww_core.memory.sql_repositories.SqlRulesRepo` 提供（C5，不动；其 `RuleView` 不带 id，
专供缓存前缀拼装）。本模块加**写**能力 + **带 id 的列表 / 删除**——前端规则页需要稳定
handle（id）来删除某条规则（不变量 #3：删规则是作者显式动作，不经 AI）。命名加 `Write`
前缀避免与读侧 repo 歧义（同 `OutlineWriteRepo`/`DigestAppendRepo` 先例）。

`level` ∈ global/genre/style/project（合法性由端点/schema 校验，repo 只写）。
**提交边界**：`create`/`delete` 只 `flush()` 不 `commit()`——提交交端点事务（与项目其它
写侧 repo 一致，见 memory/gotchas）。
"""

from __future__ import annotations

import uuid
from typing import Protocol

from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from ww_db.models import Rule


class RuleWriteView(BaseModel):
    """规则写入后的只读快照（snake_case，frozen）。"""

    model_config = {"frozen": True}

    id: uuid.UUID
    project_id: uuid.UUID | None
    level: str
    content: str


class RuleListItemView(BaseModel):
    """规则列表项（带 id，供前端规则页删除用；snake_case，frozen）。"""

    model_config = {"frozen": True}

    id: uuid.UUID
    level: str
    content: str


class RuleWriteRepo(Protocol):
    """规则写侧接口（绑 project_id；只 flush 不 commit）。"""

    async def create(self, project_id: uuid.UUID, *, level: str, content: str) -> RuleWriteView: ...

    async def list_for_project(self, project_id: uuid.UUID) -> list[RuleListItemView]: ...

    async def delete(self, project_id: uuid.UUID, rule_id: uuid.UUID) -> bool: ...


class SqlRuleWriteRepo:
    """SQLAlchemy 实现：插一行 `rules` / 列表（带 id）/ 删一行（均只 flush 不 commit）。"""

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

    async def create(self, project_id: uuid.UUID, *, level: str, content: str) -> RuleWriteView:
        row = Rule(project_id=project_id, level=level, content=content)
        self._s.add(row)
        await self._s.flush()
        await self._s.refresh(row)
        return RuleWriteView(
            id=row.id, project_id=row.project_id, level=row.level, content=row.content
        )

    async def list_for_project(self, project_id: uuid.UUID) -> list[RuleListItemView]:
        """本作品规则 + 全局规则（project_id 为空），带 id（前端删除 handle）。

        与读侧 `SqlRulesRepo.all_for_project` 同范围，但回传 id；排序确定性
        （level 优先级在 assemble.merge_rules 处理，这里只保证稳定返回）。
        """
        rows = (
            await self._s.execute(
                select(Rule).where((Rule.project_id == project_id) | (Rule.project_id.is_(None)))
            )
        ).scalars()
        return [RuleListItemView(id=r.id, level=r.level, content=r.content) for r in rows]

    async def delete(self, project_id: uuid.UUID, rule_id: uuid.UUID) -> bool:
        """删除属于该项目的某条规则；删到行返回 True，无匹配返回 False（→ 端点 404）。

        以 `(id, project_id)` 双条件定位——既防误删它项目规则，也使删全局规则（project_id
        为空）经此端点天然不可达（项目维度规则页只删本作品规则，不变量 #3 作者显式动作）。
        先取行再 `session.delete`（避免 bulk-delete 的 rowcount 类型坑），只 flush，提交交端点。
        """
        row = (
            await self._s.execute(
                select(Rule).where(Rule.id == rule_id, Rule.project_id == project_id)
            )
        ).scalar_one_or_none()
        if row is None:
            return False
        await self._s.delete(row)
        await self._s.flush()
        return True
