"""API routes for intelligent evaluation (智能评估).

领域逻辑（状态机）在 intelligent_eval/lifecycle.py；本层只做 HTTP 翻译：
NotFound→404、TransitionError→409。
"""

from typing import Any

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

from agenteval.intelligent_eval import lifecycle
from agenteval.intelligent_eval.lifecycle import (
    IntelligentEvalChannelError,
    IntelligentEvalNotFoundError,
    IntelligentEvalTransitionError,
)
from agenteval.intelligent_eval.read_model import IntelligentEvalReadModel
from agenteval.intelligent_eval.report import ReportModel, render_report_markdown
from agenteval.web.deps import get_db

router = APIRouter()


class CreateEvalRequest(BaseModel):
    name: str = Field(min_length=1)
    target_id: str = Field(min_length=1)
    goal: str = Field(min_length=1)
    seeds: dict[str, Any] = Field(default_factory=dict)
    intent: str = ""
    role_description: str = ""
    time_window_hours: int = Field(default=24, ge=1)


class SubmitPlanRequest(BaseModel):
    plan: dict[str, Any]


class RejectRequest(BaseModel):
    feedback: str = Field(min_length=1)


class CreateSessionRequest(BaseModel):
    persona: dict[str, Any] = Field(default_factory=dict)
    goal: str = Field(min_length=1)
    dimension: str | None = None


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


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


class SubmitReportRequest(BaseModel):
    report: ReportModel


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


def _eval_response(ev, session: Session) -> dict:
    """Serialize one stable intelligent-evaluation read projection."""

    projection = IntelligentEvalReadModel(session).list_item(ev)
    return projection.model_dump(mode="json")


@router.post("")
async def create_eval(request: CreateEvalRequest, session: Session = Depends(get_db)) -> dict:
    try:
        ev = lifecycle.create_eval(
            session,
            name=request.name,
            target_id=request.target_id,
            goal=request.goal,
            seeds=request.seeds,
            intent=request.intent,
            role_description=request.role_description,
            time_window_hours=request.time_window_hours,
        )
    except IntelligentEvalNotFoundError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    return _eval_response(ev, session)


@router.get("")
async def list_evals(session: Session = Depends(get_db)) -> dict:
    evals = lifecycle.list_evals(session)
    reader = IntelligentEvalReadModel(session)
    return {"intelligent_evals": [item.model_dump(mode="json") for item in reader.list_items(evals)]}


@router.get("/{eval_id}")
async def get_eval(eval_id: str, session: Session = Depends(get_db)) -> dict:
    projection = IntelligentEvalReadModel(session).detail_by_id(eval_id)
    if projection is None:
        raise HTTPException(status_code=404, detail=f"intelligent eval {eval_id} not found")
    return projection.model_dump(mode="json")


@router.put("/{eval_id}/plan")
async def submit_plan(eval_id: str, request: SubmitPlanRequest, session: Session = Depends(get_db)) -> dict:
    try:
        ev = lifecycle.submit_plan(session, eval_id, request.plan)
    except (IntelligentEvalNotFoundError, IntelligentEvalTransitionError) as exc:
        raise _translate(exc) from exc
    return _eval_response(ev, session)


@router.post("/{eval_id}/approve")
async def approve(eval_id: str, session: Session = Depends(get_db)) -> dict:
    try:
        ev = lifecycle.approve(session, eval_id)
    except (IntelligentEvalNotFoundError, IntelligentEvalTransitionError) as exc:
        raise _translate(exc) from exc
    return _eval_response(ev, session)


@router.post("/{eval_id}/reject")
async def reject(eval_id: str, request: RejectRequest, session: Session = Depends(get_db)) -> dict:
    try:
        ev = lifecycle.reject(session, eval_id, request.feedback)
    except (IntelligentEvalNotFoundError, IntelligentEvalTransitionError) as exc:
        raise _translate(exc) from exc
    return _eval_response(ev, session)


@router.post("/{eval_id}/cancel")
async def cancel(eval_id: str, session: Session = Depends(get_db)) -> dict:
    try:
        ev = lifecycle.cancel(session, eval_id)
    except (IntelligentEvalNotFoundError, IntelligentEvalTransitionError) as exc:
        raise _translate(exc) from exc
    return _eval_response(ev, session)


@router.put("/{eval_id}/report")
async def submit_report(eval_id: str, request: SubmitReportRequest, session: Session = Depends(get_db)) -> dict:
    try:
        ev = lifecycle.submit_report(session, eval_id, request.report.model_dump())
    except (IntelligentEvalNotFoundError, IntelligentEvalTransitionError) as exc:
        raise _translate(exc) from exc
    return _eval_response(ev, session)


@router.get("/{eval_id}/report")
async def get_report(eval_id: str, session: Session = Depends(get_db)) -> dict:
    report = IntelligentEvalReadModel(session).report_by_eval(eval_id)
    if report is None:
        raise HTTPException(status_code=404, detail="report not submitted yet")
    return report[1]


@router.get("/{eval_id}/report/markdown", response_class=PlainTextResponse)
async def get_report_markdown(eval_id: str, session: Session = Depends(get_db)) -> PlainTextResponse:
    report = IntelligentEvalReadModel(session).report_by_eval(eval_id)
    if report is None:
        raise HTTPException(status_code=404, detail="report not submitted yet")
    name, payload = report
    markdown = render_report_markdown(payload, name=name, eval_id=eval_id)
    return PlainTextResponse(markdown, media_type="text/markdown; charset=utf-8")


@router.post("/{eval_id}/sessions")
async def create_session(eval_id: str, request: CreateSessionRequest, session: Session = Depends(get_db)) -> dict:
    try:
        obj = lifecycle.open_session(
            session,
            eval_id=eval_id,
            persona=request.persona,
            goal=request.goal,
            dimension=request.dimension,
        )
    except (IntelligentEvalNotFoundError, IntelligentEvalTransitionError) as exc:
        raise _translate(exc) from exc
    return obj.model_dump(mode="json")


@router.get("/{eval_id}/sessions")
async def list_sessions(eval_id: str, session: Session = Depends(get_db)) -> dict:
    sessions = IntelligentEvalReadModel(session).sessions_by_eval(eval_id)
    if sessions is None:
        raise HTTPException(status_code=404, detail=f"intelligent eval {eval_id} not found")
    return {"sessions": [s.model_dump(mode="json") for s in sessions]}


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


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


@router.get("/{eval_id}/sessions/{session_id}/messages")
async def list_messages(eval_id: str, session_id: str, session: Session = Depends(get_db)) -> dict:
    messages = IntelligentEvalReadModel(session).messages_by_session(eval_id, session_id)
    if messages is None:
        raise HTTPException(status_code=404, detail=f"intelligent eval session {session_id} not found")
    return {"messages": [m.model_dump(mode="json") for m in messages]}


@router.get("/tasks/next")
async def get_next_task(session: Session = Depends(get_db)) -> dict:
    """Get next pending task for OpenClaw workers.

    Returns the highest-priority pending task, or None if no tasks available.
    """
    from agenteval.intelligent_eval import task_queue

    task = task_queue.get_next_task(session)
    if task is None:
        return {"task": None}

    # Load eval details
    from agenteval.storage.db import IntelligentEvalDB

    eval_db = session.get(IntelligentEvalDB, task.eval_id)
    if eval_db is None:
        return {"task": None}

    return {
        "task": {
            "id": task.id,
            "eval_id": task.eval_id,
            "priority": task.priority,
            "reason": task.reason,
            "eval": {
                "id": eval_db.id,
                "name": eval_db.name,
                "status": eval_db.status,
                "plan": eval_db.get_plan(),
                "started_at": eval_db.started_at.isoformat() if eval_db.started_at else None,
            },
        }
    }


@router.post("/tasks/{task_id}/assign")
async def assign_task(task_id: str, cron_id: str, session: Session = Depends(get_db)) -> dict:
    """Assign a task to a cron worker."""
    from agenteval.intelligent_eval import task_queue

    success = task_queue.assign_task(task_id, cron_id, session)
    if not success:
        raise HTTPException(status_code=404, detail="task not found or already assigned")
    return {"success": True}


@router.post("/tasks/{task_id}/complete")
async def complete_task(
    task_id: str,
    success: bool,
    error: str | None = None,
    session: Session = Depends(get_db),
) -> dict:
    """Mark a task as completed or failed."""
    from agenteval.intelligent_eval import task_queue

    completed = task_queue.complete_task(task_id, success, error, session)
    if not completed:
        raise HTTPException(status_code=404, detail="task not found")
    return {"success": True}


class DecisionLogRequest(BaseModel):
    decision_type: str = Field(min_length=1)  # execute_session / wait / start_analysis
    reason: str = Field(min_length=1)
    context: dict[str, Any] = Field(default_factory=dict)
    cron_id: str = Field(min_length=1)


@router.post("/{eval_id}/decision-logs")
async def create_decision_log(
    eval_id: str,
    request: DecisionLogRequest,
    session: Session = Depends(get_db),
) -> dict:
    """Create a decision log entry for an intelligent eval."""
    from agenteval.storage.db import IntelligentEvalDecisionLogDB

    # Verify eval exists
    from agenteval.storage.db import IntelligentEvalDB

    eval_db = session.get(IntelligentEvalDB, eval_id)
    if eval_db is None:
        raise HTTPException(status_code=404, detail=f"intelligent eval {eval_id} not found")

    # Create decision log
    log = IntelligentEvalDecisionLogDB(
        eval_id=eval_id,
        decision_type=request.decision_type,
        reason=request.reason,
        cron_id=request.cron_id,
    )
    log.set_context(request.context)
    session.add(log)
    session.commit()
    session.refresh(log)

    return {
        "id": log.id,
        "eval_id": log.eval_id,
        "decision_type": log.decision_type,
        "reason": log.reason,
        "context": log.get_context(),
        "cron_id": log.cron_id,
        "created_at": log.created_at.isoformat() if log.created_at else None,
    }


@router.get("/{eval_id}/decision-logs")
async def list_decision_logs(eval_id: str, session: Session = Depends(get_db)) -> dict:
    """List all decision logs for an evaluation."""
    from sqlmodel import select

    from agenteval.storage.db import IntelligentEvalDB, IntelligentEvalDecisionLogDB

    # Verify eval exists
    eval_db = session.get(IntelligentEvalDB, eval_id)
    if eval_db is None:
        raise HTTPException(status_code=404, detail=f"intelligent eval {eval_id} not found")

    # Get all decision logs
    logs = session.exec(
        select(IntelligentEvalDecisionLogDB)
        .where(IntelligentEvalDecisionLogDB.eval_id == eval_id)
        .order_by(IntelligentEvalDecisionLogDB.created_at.desc())
    ).all()

    return {
        "logs": [
            {
                "id": log.id,
                "eval_id": log.eval_id,
                "decision_type": log.decision_type,
                "reason": log.reason,
                "context": log.get_context(),
                "cron_id": log.cron_id,
                "created_at": log.created_at.isoformat() if log.created_at else None,
            }
            for log in logs
        ]
    }


@router.get("/{eval_id}/config-snapshots")
async def list_config_snapshots(eval_id: str, session: Session = Depends(get_db)) -> dict:
    """List all config snapshots for an evaluation."""
    from agenteval.intelligent_eval import config_snapshot
    from agenteval.storage.db import IntelligentEvalDB

    # Verify eval exists
    eval_db = session.get(IntelligentEvalDB, eval_id)
    if eval_db is None:
        raise HTTPException(status_code=404, detail=f"intelligent eval {eval_id} not found")

    snapshots = config_snapshot.list_snapshots(eval_id, session)
    return {
        "snapshots": [
            {
                "id": s.id,
                "eval_id": s.eval_id,
                "snapshot_type": s.snapshot_type,
                "goal": s.goal,
                "seeds": s.get_seeds(),
                "intent": s.intent,
                "role_description": s.role_description,
                "time_window_hours": s.time_window_hours,
                "plan": s.get_plan(),
                "created_at": s.created_at.isoformat() if s.created_at else None,
                "created_by": s.created_by,
            }
            for s in snapshots
        ]
    }


@router.get("/{eval_id}/config-snapshots/{snapshot_id}")
async def get_config_snapshot(eval_id: str, snapshot_id: str, session: Session = Depends(get_db)) -> dict:
    """Get a single config snapshot."""
    from agenteval.intelligent_eval import config_snapshot
    from agenteval.storage.db import IntelligentEvalDB

    # Verify eval exists
    eval_db = session.get(IntelligentEvalDB, eval_id)
    if eval_db is None:
        raise HTTPException(status_code=404, detail=f"intelligent eval {eval_id} not found")

    snapshot = config_snapshot.get_snapshot(snapshot_id, session)
    if snapshot is None or snapshot.eval_id != eval_id:
        raise HTTPException(status_code=404, detail=f"snapshot {snapshot_id} not found")

    return {
        "id": snapshot.id,
        "eval_id": snapshot.eval_id,
        "snapshot_type": snapshot.snapshot_type,
        "goal": snapshot.goal,
        "seeds": snapshot.get_seeds(),
        "intent": snapshot.intent,
        "role_description": snapshot.role_description,
        "time_window_hours": snapshot.time_window_hours,
        "plan": snapshot.get_plan(),
        "created_at": snapshot.created_at.isoformat() if snapshot.created_at else None,
        "created_by": snapshot.created_by,
    }


class CompareSnapshotsRequest(BaseModel):
    snapshot_id_1: str = Field(min_length=1)
    snapshot_id_2: str = Field(min_length=1)


@router.post("/{eval_id}/config-snapshots/compare")
async def compare_config_snapshots(
    eval_id: str,
    request: CompareSnapshotsRequest,
    session: Session = Depends(get_db),
) -> dict:
    """Compare two config snapshots and return differences."""
    from agenteval.intelligent_eval import config_snapshot
    from agenteval.storage.db import IntelligentEvalDB

    # Verify eval exists
    eval_db = session.get(IntelligentEvalDB, eval_id)
    if eval_db is None:
        raise HTTPException(status_code=404, detail=f"intelligent eval {eval_id} not found")

    # Get both snapshots
    snapshot1 = config_snapshot.get_snapshot(request.snapshot_id_1, session)
    snapshot2 = config_snapshot.get_snapshot(request.snapshot_id_2, session)

    if snapshot1 is None or snapshot1.eval_id != eval_id:
        raise HTTPException(status_code=404, detail=f"snapshot {request.snapshot_id_1} not found")
    if snapshot2 is None or snapshot2.eval_id != eval_id:
        raise HTTPException(status_code=404, detail=f"snapshot {request.snapshot_id_2} not found")

    # Compare snapshots
    diffs = config_snapshot.compare_snapshots(snapshot1, snapshot2)

    return {
        "snapshot_1": {
            "id": snapshot1.id,
            "snapshot_type": snapshot1.snapshot_type,
            "created_at": snapshot1.created_at.isoformat() if snapshot1.created_at else None,
        },
        "snapshot_2": {
            "id": snapshot2.id,
            "snapshot_type": snapshot2.snapshot_type,
            "created_at": snapshot2.created_at.isoformat() if snapshot2.created_at else None,
        },
        "differences": diffs,
    }
