"""API routes for exploratory evaluation sessions (探索式评测, v0.9).

领域逻辑（账本规则、状态机、巡检）在 exploration/lifecycle.py 与
patrol.py；本层只做 HTTP 翻译：解析请求、调用领域操作、把领域异常
映射为状态码（NotFound→404、Guardrail→409、Channel→502）。预算违规
与状态违规的拒绝文案由领域层给出，拒绝本身就是对常驻智能体的反馈。
"""

from typing import Any

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlmodel import Session

from agenteval.exploration import lifecycle, patrol
from agenteval.exploration.errors import (
    ExplorationChannelError,
    ExplorationGuardrailError,
    ExplorationNotFoundError,
)
from agenteval.exploration.models import ExplorationTrigger
from agenteval.storage.repository import (
    CampaignRepository,
    ExplorationMessageRepository,
    ExplorationSessionRepository,
)
from agenteval.web.deps import get_db

router = APIRouter()


class CreateSessionRequest(BaseModel):
    campaign_id: str
    persona: dict[str, Any]
    goal: str = Field(min_length=1)
    seed_ref: dict[str, Any] | None = None
    triggered_by: ExplorationTrigger = ExplorationTrigger.AUTO


class SendMessageRequest(BaseModel):
    content: str = Field(min_length=1)


class CloseSessionRequest(BaseModel):
    experience: dict[str, Any]


def _translate(exc: Exception) -> HTTPException:
    if isinstance(exc, ExplorationNotFoundError):
        return HTTPException(status_code=404, detail=str(exc))
    if isinstance(exc, ExplorationGuardrailError):
        return HTTPException(status_code=409, detail=exc.reason)
    return HTTPException(status_code=502, detail=exc.reason)


@router.get("/patrol")
async def patrol_endpoint(session: Session = Depends(get_db)) -> dict:
    """Stateless patrol for the resident agent — see exploration/patrol.py."""
    return patrol.patrol_report(session)


@router.post("/sessions")
async def create_session(
    request: CreateSessionRequest,
    session: Session = Depends(get_db),
) -> dict:
    try:
        session_obj = lifecycle.open_session(
            session,
            campaign_id=request.campaign_id,
            persona=request.persona,
            goal=request.goal,
            seed_ref=request.seed_ref,
            triggered_by=request.triggered_by,
        )
    except (ExplorationNotFoundError, ExplorationGuardrailError) as exc:
        raise _translate(exc) from exc
    return session_obj.model_dump(mode="json")


@router.get("/campaigns/{campaign_id}/sessions")
async def list_campaign_sessions(
    campaign_id: str,
    session: Session = Depends(get_db),
) -> dict:
    if not CampaignRepository(session).get(campaign_id):
        raise HTTPException(status_code=404, detail="campaign not found")
    sessions = ExplorationSessionRepository(session).list_by_campaign(campaign_id)
    return {"sessions": [s.model_dump(mode="json") for s in sessions]}


@router.get("/sessions/{session_id}/messages")
async def list_session_messages(
    session_id: str,
    session: Session = Depends(get_db),
) -> dict:
    if not ExplorationSessionRepository(session).get(session_id):
        raise HTTPException(status_code=404, detail="exploration session not found")
    messages = ExplorationMessageRepository(session).list_by_session(session_id)
    return {"messages": [m.model_dump(mode="json") for m in messages]}


@router.post("/sessions/{session_id}/messages")
async def send_session_message(
    session_id: str,
    request: SendMessageRequest,
    session: Session = Depends(get_db),
) -> dict:
    try:
        return await lifecycle.conduct_turn(session, session_id=session_id, content=request.content)
    except (ExplorationNotFoundError, ExplorationGuardrailError, ExplorationChannelError) as exc:
        raise _translate(exc) from exc


@router.post("/sessions/{session_id}/close")
async def close_session(
    session_id: str,
    request: CloseSessionRequest,
    session: Session = Depends(get_db),
) -> dict:
    try:
        updated = lifecycle.close_session(session, session_id=session_id, experience=request.experience)
    except (ExplorationNotFoundError, ExplorationGuardrailError) as exc:
        raise _translate(exc) from exc
    return updated.model_dump(mode="json")
