"""Repository 协议的 SQLAlchemy async 实现（ARCH §3.5）。

ORM 行 → 只读视图的映射层；统一按 project_id 过滤（数据隔离，多租户化时此层加 owner_id）。
单测用内存 fake（见 tests），此实现由 mypy 静态校验、运行时（T1.4）注入真实 session。
"""

from __future__ import annotations

import uuid

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from ww_db.models import (
    ChapterDigest,
    Character,
    Foreshadow,
    Outline,
    Project,
    Rule,
    StyleFingerprint,
    WorldEntity,
)

from ww_core.domain.repositories import (
    CharacterView,
    DigestView,
    ForeshadowView,
    MemoryRepos,
    OutlineView,
    ProjectSpecView,
    RuleView,
    StyleView,
    WorldEntityView,
)
from ww_core.domain.review_repo import SqlReviewRepo


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

    async def get(self, project_id: uuid.UUID, chapter_no: int) -> OutlineView | None:
        row = (
            await self._s.execute(
                select(Outline).where(
                    Outline.project_id == project_id, Outline.chapter_no == chapter_no
                )
            )
        ).scalar_one_or_none()
        if row is None:
            return None
        return OutlineView(
            volume=row.volume,
            chapter_no=row.chapter_no,
            beats=row.beats or {},
            foreshadow_windows=list(row.foreshadow_windows or []),
        )

    async def list_for_project(self, project_id: uuid.UUID) -> list[OutlineView]:
        rows = (
            await self._s.execute(
                select(Outline).where(Outline.project_id == project_id).order_by(Outline.chapter_no)
            )
        ).scalars()
        return [
            OutlineView(
                volume=r.volume,
                chapter_no=r.chapter_no,
                beats=r.beats or {},
                foreshadow_windows=list(r.foreshadow_windows or []),
            )
            for r in rows
        ]


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

    async def list_for_project(self, project_id: uuid.UUID) -> list[CharacterView]:
        rows = (
            await self._s.execute(select(Character).where(Character.project_id == project_id))
        ).scalars()
        return [
            CharacterView(
                name=r.name,
                role=r.role,
                traits=r.traits,
                appearance=r.appearance,
                motive=r.motive,
                backstory=r.backstory,
                arc=r.arc,
                speech_tics=r.speech_tics,
                tags=list(r.tags or []),
                relations=list(r.relations or []),
                first_chapter=r.first_chapter,
                latest_state=r.latest_state,
            )
            for r in rows
        ]


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

    async def list_for_project(self, project_id: uuid.UUID) -> list[WorldEntityView]:
        rows = (
            await self._s.execute(select(WorldEntity).where(WorldEntity.project_id == project_id))
        ).scalars()
        return [
            WorldEntityView(
                type=r.type,
                name=r.name,
                rules=r.rules,
                first_chapter=r.first_chapter,
                latest_state=r.latest_state,
            )
            for r in rows
        ]


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

    async def recent(self, project_id: uuid.UUID, k: int) -> list[DigestView]:
        rows = (
            await self._s.execute(
                select(ChapterDigest)
                .where(ChapterDigest.project_id == project_id)
                .order_by(ChapterDigest.chapter_no.desc())
                .limit(k)
            )
        ).scalars()
        return [DigestView(chapter_no=r.chapter_no, facts=r.facts) for r in rows]


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

    async def list_for_codes(self, project_id: uuid.UUID, codes: list[str]) -> list[ForeshadowView]:
        if not codes:
            return []
        rows = (
            await self._s.execute(
                select(Foreshadow).where(
                    Foreshadow.project_id == project_id, Foreshadow.code.in_(codes)
                )
            )
        ).scalars()
        return [
            ForeshadowView(
                code=r.code,
                title=r.title,
                status=r.status,
                planted_at=r.planted_at,
                expected_close_from=r.expected_close_from,
                expected_close_to=r.expected_close_to,
                content=r.content,
            )
            for r in rows
        ]


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

    async def latest(self, project_id: uuid.UUID) -> StyleView | None:
        row = (
            await self._s.execute(
                select(StyleFingerprint)
                .where(StyleFingerprint.project_id == project_id)
                .order_by(StyleFingerprint.version.desc())
                .limit(1)
            )
        ).scalar_one_or_none()
        if row is None:
            return None
        return StyleView(dimensions=row.dimensions_json)


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

    async def all_for_project(self, project_id: uuid.UUID) -> list[RuleView]:
        # 全局规则(project_id 为空) + 本作品规则——合并优先级在 assemble.merge_rules 处理
        rows = (
            await self._s.execute(
                select(Rule).where((Rule.project_id == project_id) | (Rule.project_id.is_(None)))
            )
        ).scalars()
        return [RuleView(level=r.level, content=r.content) for r in rows]


class SqlProjectSpecRepo:
    """读项目书级蓝本（premise/logline/theme/title）供 assemble 注入缓存前缀。

    按 project_id 读（不带 owner_id：assemble 已在 project 维度过滤，owner 隔离归路由层）。
    """

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

    async def spec(self, project_id: uuid.UUID) -> ProjectSpecView | None:
        row = (
            await self._s.execute(select(Project).where(Project.id == project_id))
        ).scalar_one_or_none()
        if row is None:
            return None
        return ProjectSpecView(
            title=row.title,
            logline=row.logline,
            premise=row.premise,
            theme=row.theme,
            genre=row.genre,
            tone=row.tone,
            ending_type=row.ending_type,
            narrative_pov=row.narrative_pov,
        )


def sql_memory_repos(session: AsyncSession) -> MemoryRepos:
    """用一个 AsyncSession 装配全部 9 个 SQLAlchemy repo（T1.4 注入点）。

    `review=SqlReviewRepo`：供 assemble 反读上一已验收章的 continuity 冲突裁决（灵感②）。
    """
    return MemoryRepos(
        outline=SqlOutlineRepo(session),
        character=SqlCharacterRepo(session),
        world_entity=SqlWorldEntityRepo(session),
        digest=SqlDigestRepo(session),
        foreshadow=SqlForeshadowRepo(session),
        style=SqlStyleRepo(session),
        rules=SqlRulesRepo(session),
        project=SqlProjectSpecRepo(session),
        review=SqlReviewRepo(session),
    )
