"""大纲生成端点（C3 扩 / ARCH §5.4 outliner / §7.2 POST /outline；不变量 #2/#3）。

POST /projects/:id/outline：作者显式发起大纲生成 + 持久化。

流程：组确定性上下文（设定 + 已登记伏笔 + 人物 + 世界观）→ `run_outline`（analyst 网关，
结构化 `OutlineResult`）→ 逐章 upsert `outline` 表 → 端点末尾一次 `commit()`。

提交边界：网关 ledger 只 flush（run_outline 产 1 条 usage）、`OutlineWriteRepo.upsert_chapter`
只 flush → 端点末尾 `await session.commit()`（否则 usage_ledger + outline 行静默丢失，见 gotcha）。

不变量 #2：经 `build_gateway_for_tier(..., "analyst")` 注入；outliner_spec 只声明档位不传 model。
不变量 #3：outliner `writes=["outline"]` 经此**作者发起**端点落地，非 AI 静默写其它表；
run_outline 节点本身只读不写库。无凭据 → `LLM_UNAVAILABLE`（友好提示，仿 draft/review）。
"""

from __future__ import annotations

import uuid
from typing import Annotated

from fastapi import APIRouter, Depends, Request
from sqlalchemy.ext.asyncio import AsyncSession
from ww_agents import outliner_spec
from ww_core.domain import ForeshadowLedgerRepo, OutlineWriteRepo, ProjectRepo
from ww_core.domain.repositories import MemoryRepos, OutlineRepo
from ww_core.orchestrator import run_outline
from ww_db import get_session
from ww_llm_gateway import Gateway
from ww_shared import AppError, ErrorCode

from ww_api.logging_config import get_logger
from ww_api.schemas.outline import (
    ForeshadowWindowView,
    OutlineChapterView,
    OutlineGenerateRequest,
    OutlineResponse,
)
from ww_api.services.credentials import STUB_OWNER_ID
from ww_api.services.outline_context import build_outline_context
from ww_api.services.project_deps import (
    get_foreshadow_repo,
    get_memory_repos,
    get_outline_gateway,
    get_outline_read_repo,
    get_outline_write_repo,
    get_project_repo,
)

log = get_logger("ww.api.outline")

router = APIRouter(prefix="/projects", tags=["outline"])

ProjectRepoDep = Annotated[ProjectRepo, Depends(get_project_repo)]
ForeshadowRepoDep = Annotated[ForeshadowLedgerRepo, Depends(get_foreshadow_repo)]
MemoryReposDep = Annotated[MemoryRepos, Depends(get_memory_repos)]
OutlineWriteRepoDep = Annotated[OutlineWriteRepo, Depends(get_outline_write_repo)]
OutlineReadRepoDep = Annotated[OutlineRepo, Depends(get_outline_read_repo)]
OutlineGatewayDep = Annotated[Gateway, Depends(get_outline_gateway)]
SessionDep = Annotated[AsyncSession, Depends(get_session)]


@router.post("/{project_id}/outline")
async def generate_outline(
    project_id: uuid.UUID,
    body: OutlineGenerateRequest,
    request: Request,
    project_repo: ProjectRepoDep,
    foreshadow_repo: ForeshadowRepoDep,
    memory_repos: MemoryReposDep,
    outline_repo: OutlineWriteRepoDep,
    gateway: OutlineGatewayDep,
    session: SessionDep,
) -> OutlineResponse:
    """生成大纲并逐章持久化。无凭据 → LLM_UNAVAILABLE；项目不存在 → NOT_FOUND。"""
    request_id = getattr(request.state, "request_id", None)

    project = await project_repo.get(STUB_OWNER_ID, project_id)
    if project is None:
        raise AppError(ErrorCode.NOT_FOUND, f"project not found: {project_id}")

    foreshadow = await foreshadow_repo.list_by_status(project_id, None)
    characters = await memory_repos.character.list_for_project(project_id)
    world_entities = await memory_repos.world_entity.list_for_project(project_id)
    context = build_outline_context(
        project=project,
        foreshadow=foreshadow,
        characters=characters,
        world_entities=world_entities,
    )

    log.info(
        "outline_generate_start",
        project_id=str(project_id),
        request_id=request_id,
        volume=body.volume,
        context_len=len(context),
        foreshadow_count=len(foreshadow),
    )

    # run_outline 产结构化大纲（analyst 网关；网关失败上抛，无凭据 → LLM_UNAVAILABLE）。
    result = await run_outline(
        outliner_spec,
        context=context,
        gateway=gateway,
        user_id=STUB_OWNER_ID,
        project_id=project_id,
    )

    chapters: list[OutlineChapterView] = []
    for chapter in result.chapters:
        windows = [w.model_dump() for w in chapter.foreshadow_windows]
        await outline_repo.upsert_chapter(
            project_id,
            volume=body.volume,
            chapter_no=chapter.no,
            beats=chapter.beats,
            foreshadow_windows=windows,
        )
        chapters.append(
            OutlineChapterView(
                no=chapter.no,
                volume=body.volume,
                beats=list(chapter.beats),
                foreshadow_windows=[ForeshadowWindowView(**w) for w in windows],
            )
        )

    # 提交边界：网关 ledger + outline upsert 均只 flush → 端点末尾一次 commit。
    await session.commit()

    log.info(
        "outline_generate_done",
        project_id=str(project_id),
        request_id=request_id,
        chapter_count=len(chapters),
    )
    return OutlineResponse(chapters=chapters)


@router.get("/{project_id}/outline")
async def get_outline(
    project_id: uuid.UUID,
    request: Request,
    project_repo: ProjectRepoDep,
    outline_repo: OutlineReadRepoDep,
    volume: int | None = None,
) -> OutlineResponse:
    """读取已持久化的大纲（逐章，按 chapter_no 升序）。

    可选 `?volume=N`：只返回该卷的章节（前端按卷切换）；不传 → 全部章节（向后兼容）。
    项目不存在 → 404；项目存在但尚无大纲（或该卷无章节）→ 200 空列表（非 404）。
    DB `outline.beats` 是 JSONB `{"beats": [...]}` → 解包成裸 `list[str]`（与 POST 响应同形，
    前端 OpenAPI 类型对齐）。读侧复用 C5 assemble 的 `OutlineRepo`，不写库。
    """
    request_id = getattr(request.state, "request_id", None)

    project = await project_repo.get(STUB_OWNER_ID, project_id)
    if project is None:
        raise AppError(ErrorCode.NOT_FOUND, f"project not found: {project_id}")

    views = await outline_repo.list_for_project(project_id)
    if volume is not None:
        views = [v for v in views if v.volume == volume]
    chapters = [
        OutlineChapterView(
            no=view.chapter_no,
            volume=view.volume,
            beats=list(view.beats.get("beats", [])),
            foreshadow_windows=[ForeshadowWindowView(**w) for w in view.foreshadow_windows],
        )
        for view in views
    ]

    log.info(
        "outline_read",
        project_id=str(project_id),
        request_id=request_id,
        volume=volume,
        chapter_count=len(chapters),
    )
    return OutlineResponse(chapters=chapters)
