"""文风端点：学文风（异步长任务）+ 回炉 + 最新指纹读取
（C3 扩 / ARCH §5.4 style-auditor 提取轨 / §7.2 / §7.4 jobs / UX §6.9 / §8.3）。

独立 router（仿 outline.py），挂 `app.include_router`。三端点：

- `POST /projects/{id}/style` → 202 `{job_id}`：学文风走 jobs（M4-c）。立即写一行 job
  返 202，提取经 BackgroundTask `run_job` 跑（**自建独立 session**，请求 session 已关闭）。
  无凭据 → 流前 `LLM_UNAVAILABLE`（503，在调度 job 之前拦下，避免凭空写注定失败的 job）。
- `POST /projects/{id}/chapters/{no}/refine` → 200 `{original, refined}`：同步回炉，writer
  网关纯文本重写选中段；不写库（不变量 #3，作者采纳经既有 draft 自动保存合入）；末尾
  `commit()` 让 usage_ledger 落库（网关 ledger add-only，同 draft/review 纪律）。
- `GET /projects/{id}/style` → 200 最新指纹（完整 16 维 + 证据 + 版本，UX §6.9）。

不变量 #2：经 `get_*_gateway`（analyst/writer 档）注入，spec 只声明档位不传 model。
不变量 #3：提取/回炉 agent 只读不写其它表；文风指纹落库经此作者发起的学文风路径。
"""

from __future__ import annotations

import uuid
from typing import Annotated, Any

from fastapi import APIRouter, BackgroundTasks, Depends, Request, Response
from sqlalchemy.ext.asyncio import AsyncSession
from ww_agents import refiner_spec, style_extract_spec
from ww_core.domain import JobRepo, ProjectRepo, StyleFingerprintWriteRepo
from ww_core.domain.style_repo import SqlStyleFingerprintWriteRepo
from ww_core.orchestrator import run_style_extraction
from ww_db import get_session
from ww_llm_gateway import Gateway
from ww_llm_gateway.types import Block, LlmRequest, Scope
from ww_shared import AppError, ErrorCode, ErrorEnvelope

from ww_api.logging_config import get_logger
from ww_api.schemas.style import (
    DimensionEntry,
    RefineRequest,
    RefineResponse,
    StyleFingerprintResponse,
    StyleLearnRequest,
    StyleLearnResponse,
)
from ww_api.services.credentials import STUB_OWNER_ID, SqlCredentialStore
from ww_api.services.foreshadow_scan import SessionFactory
from ww_api.services.job_runner import run_job
from ww_api.services.project_deps import (
    build_gateway_for_tier,
    get_job_repo,
    get_project_repo,
    get_refine_gateway,
    get_session_factory,
    get_style_extract_gateway,
    get_style_write_repo,
)

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

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

ProjectRepoDep = Annotated[ProjectRepo, Depends(get_project_repo)]
JobRepoDep = Annotated[JobRepo, Depends(get_job_repo)]
StyleWriteRepoDep = Annotated[StyleFingerprintWriteRepo, Depends(get_style_write_repo)]
StyleExtractGatewayDep = Annotated[Gateway, Depends(get_style_extract_gateway)]
RefineGatewayDep = Annotated[Gateway, Depends(get_refine_gateway)]
SessionDep = Annotated[AsyncSession, Depends(get_session)]
SessionFactoryDep = Annotated[SessionFactory, Depends(get_session_factory)]

_JOB_KIND_STYLE_LEARN = "style_learn"


def _split_fingerprint(result: Any) -> tuple[dict[str, str], dict[str, list[str]]]:
    """把 `StyleFingerprintResult` 拆成 DB 两列：`{name:value}` + `{name:[evidence]}`。"""
    dimensions = {dim.name: dim.value for dim in result.dimensions}
    evidence = {dim.name: list(dim.evidence) for dim in result.dimensions}
    return dimensions, evidence


def _make_style_learn_work(project_id: uuid.UUID, samples_text: str) -> Any:
    """构造学文风后台工作闭包：在 `run_job` 自建的独立 session 上跑提取 + 落库。

    `work(session)` 自建 analyst 网关（从凭据）+ 写侧 repo（用 `run_job` 传入的独立
    session）→ `run_style_extraction` → 拆指纹 → `append`（版本化）→ 返回 result 摘要
    `{version, dims_count}`。提交归 `run_job`（业务写 + job done 同一事务一次 commit）。
    """

    async def work(session: AsyncSession) -> dict[str, Any]:
        store = SqlCredentialStore(session)
        gateway = await build_gateway_for_tier(session, store, "analyst")
        result = await run_style_extraction(
            style_extract_spec,
            samples_text=samples_text,
            gateway=gateway,
            user_id=STUB_OWNER_ID,
            project_id=project_id,
        )
        dimensions, evidence = _split_fingerprint(result)
        repo = SqlStyleFingerprintWriteRepo(session)
        version = await repo.append(project_id, dimensions_json=dimensions, evidence_json=evidence)
        return {"version": version, "dims_count": len(dimensions)}

    return work


@router.post("/{project_id}/style", status_code=202)
async def learn_style(
    project_id: uuid.UUID,
    body: StyleLearnRequest,
    request: Request,
    response: Response,
    background_tasks: BackgroundTasks,
    project_repo: ProjectRepoDep,
    job_repo: JobRepoDep,
    gateway: StyleExtractGatewayDep,  # 凭据探测（无凭据 → dep 解析阶段 503）
    session: SessionDep,
    session_factory: SessionFactoryDep,
) -> StyleLearnResponse:
    """学文风：写一行 job 返 202，提取经 BackgroundTask 异步跑。

    项目不存在 → 404；无凭据 → 503（在调度 job 前拦下）。`mode` 不影响落库逻辑
    （写侧始终 append 新版本，version+1 自动），仅供前端区分首学/更新语义。
    """
    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}")

    # `gateway` 已在 dep 解析阶段验过凭据（无凭据 → 503）；提取本体在 BackgroundTask
    # 里用独立 session 重新构网关跑，此处不复用它（请求 session 即将关闭）。
    samples_text = "\n\n".join(body.samples)

    job = await job_repo.create(project_id, _JOB_KIND_STYLE_LEARN)
    await session.commit()  # job 行需在 202 返回前持久化（供前端立即轮询）。

    background_tasks.add_task(
        run_job,
        session_factory,
        job.id,
        _make_style_learn_work(project_id, samples_text),
        request_id=request_id,
    )

    log.info(
        "style_learn_scheduled",
        project_id=str(project_id),
        request_id=request_id,
        job_id=str(job.id),
        sample_count=len(body.samples),
        mode=body.mode,
    )
    response.status_code = 202
    return StyleLearnResponse(job_id=job.id)


def _merge_dimensions(dimensions: dict[str, Any], evidence: dict[str, Any]) -> list[DimensionEntry]:
    """把 DB 两列并行 dict 合并为 `list[DimensionEntry]`（按维度名稳定排序，便于前端展示）。"""
    return [
        DimensionEntry(
            name=name,
            value=str(dimensions[name]),
            evidence=[str(e) for e in (evidence.get(name) or [])],
        )
        for name in sorted(dimensions)
    ]


@router.get(
    "/{project_id}/style",
    responses={404: {"model": ErrorEnvelope, "description": "项目或指纹不存在"}},
)
async def get_style(
    project_id: uuid.UUID,
    project_repo: ProjectRepoDep,
    style_repo: StyleWriteRepoDep,
) -> StyleFingerprintResponse:
    """取最新文风指纹（完整 16 维 + 证据 + 版本）。项目不存在 → 404；无指纹 → 404。"""
    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}")

    latest = await style_repo.latest(project_id)
    if latest is None:
        raise AppError(ErrorCode.NOT_FOUND, f"no style fingerprint for project: {project_id}")
    return StyleFingerprintResponse(
        dimensions=_merge_dimensions(latest.dimensions, latest.evidence),
        version=latest.version,
    )


def _build_refine_input(segment: str, instruction: str | None) -> str:
    """组回炉输入：待重写段 + 可选指令（保留上下文语气，只重写该段）。"""
    parts = [f"【待重写段落】\n{segment}"]
    if instruction:
        parts.append(f"【改写指令】\n{instruction}")
    return "\n\n".join(parts)


@router.post("/{project_id}/chapters/{chapter_no}/refine")
async def refine_segment(
    project_id: uuid.UUID,
    chapter_no: int,
    body: RefineRequest,
    request: Request,
    project_repo: ProjectRepoDep,
    gateway: RefineGatewayDep,
    session: SessionDep,
) -> RefineResponse:
    """同步回炉：writer 网关纯文本重写选中段，返回 {original, refined}。

    项目不存在 → 404；无凭据 → 503。不写库（不变量 #3）；末尾 `commit()` 让网关
    usage_ledger 落库（add-only，否则记账静默丢失，同 draft/review 纪律）。
    """
    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}")

    req = LlmRequest(
        tier=refiner_spec.tier,  # 不变量 #2：writer 档，不传 model
        system=[Block(text=refiner_spec.system_prompt, cache=True)],
        input=_build_refine_input(body.segment, body.instruction),
        output_schema=None,  # refiner 纯文本（无结构化 schema）
        scope=Scope(user_id=STUB_OWNER_ID, project_id=project_id),
    )
    resp = await gateway.run(req)

    # 提交边界：网关 ledger add-only → 端点末尾 commit（否则记账静默丢失，见 gotcha）。
    await session.commit()

    log.info(
        "refine_done",
        project_id=str(project_id),
        chapter_no=chapter_no,
        request_id=request_id,
        segment_len=len(body.segment),
        refined_len=len(resp.text),
        has_instruction=body.instruction is not None,
    )
    return RefineResponse(original=body.segment, refined=resp.text)
