"""FastAPI routes for code review sessions, findings, and convergences."""

import asyncio
import json
from typing import Any

from fastapi import APIRouter, Body, Depends, HTTPException, Query

from peon_mcp.common.constants import VALID_PERSPECTIVES, VALID_SEVERITIES
from peon_mcp.common.dependencies import get_db
from peon_mcp.common.schemas import PaginatedResponse
from peon_mcp.db import row_to_dict
from peon_mcp.reviews.schemas import (
    AdvancePipelineResponse,
    CancelReviewResponse,
    CompleteReviewPerspectiveRequest,
    CompleteReviewPerspectiveResponse,
    CreateReviewRequest,
    CreateReviewResponse,
    CreateTaskFromReviewRequest,
    PipelineStatusResponse,
    RetryPipelineResponse,
    ReviewFindingResponse,
    ReviewSessionDetailResponse,
    ReviewSessionResponse,
    SimpleCreateReviewSessionRequest,
    SubmitReviewFindingRequest,
)
from peon_mcp.tasks.schemas import TaskResponse
from peon_mcp.webhooks.engine import EventType, emit_event

# Import review module
try:
    from peon_mcp.reviews.orchestration import (
        _update_pipeline_stage,
        advance_pipeline,
        create_review_tasks,
    )
    REVIEW_MODULE_AVAILABLE = True
except ImportError:
    REVIEW_MODULE_AVAILABLE = False
    create_review_tasks = None
    advance_pipeline = None
    _update_pipeline_stage = None

_STAGE_ORDER = ["reviewing", "fixing", "verifying", "complete"]


def _pipeline_stage_status(current_stage: str, target_stage: str) -> str:
    """Return the status of target_stage relative to the current pipeline stage."""
    if current_stage == "failed":
        return "failed"
    try:
        c_idx = _STAGE_ORDER.index(current_stage)
        t_idx = _STAGE_ORDER.index(target_stage)
    except ValueError:
        return "pending"
    if current_stage == "complete":
        return "complete"
    if t_idx < c_idx:
        return "complete"
    if t_idx == c_idx:
        return "in_progress"
    return "pending"

router = APIRouter(tags=["Reviews"])


@router.post("/api/tasks/{task_id}/reviews", response_model=CreateReviewResponse, status_code=201)
async def create_review_for_task(
    task_id: int,
    body: CreateReviewRequest | None = Body(default=None),
    db=Depends(get_db),
):
    """Create a code review session for a task and spawn review tasks."""
    perspectives = (body.perspectives if body else ["security", "maintainability", "architecture"])

    # Validate task exists and has pr_url
    task_rows = await db.execute_fetchall(
        "SELECT project_id, feature_id, pr_url, title FROM tasks WHERE id = ?",
        (task_id,)
    )
    if not task_rows:
        raise HTTPException(404, detail="Task not found")

    task = row_to_dict(task_rows[0])
    if not task.get("pr_url"):
        raise HTTPException(400, detail="Cannot create review: task must have a pr_url set")

    # Check if review module is available
    if not REVIEW_MODULE_AVAILABLE:
        raise HTTPException(
            501,
            detail="Review feature not yet available. The review module is being added in a separate PR."
        )

    # Create review session
    cursor = await db.execute(
        """INSERT INTO review_sessions
           (project_id, task_id, pr_url, pr_title, status, perspectives)
           VALUES (?, ?, ?, '', 'pending', ?)""",
        (task["project_id"], task_id, task["pr_url"], json.dumps(perspectives))
    )
    await db.commit()
    session_id = cursor.lastrowid

    # Create review tasks (one per perspective) — no background process needed
    result = await create_review_tasks(db, session_id)

    return {
        "session_id": session_id,
        "status": result.get("status", "in_progress"),
        "tasks_created": result.get("tasks_created", 0),
        "task_ids": result.get("task_ids", []),
        "message": f"Review session {session_id} created with {result.get('tasks_created', 0)} review tasks"
    }


@router.get("/api/projects/{project_id}/reviews", response_model=PaginatedResponse[ReviewSessionResponse])
async def list_review_sessions(
    project_id: str,
    status: str | None = Query(default=None),
    limit: int = Query(default=50, ge=1, le=200),
    offset: int = Query(default=0, ge=0),
    db=Depends(get_db),
):
    """List review sessions for a project with optional status filter."""
    query = "SELECT * FROM review_sessions WHERE project_id = ?"
    params: list = [project_id]

    # Optional status filter
    if status:
        query += " AND status = ?"
        params.append(status)

    # Get total count for pagination metadata
    count_query = query.replace("SELECT *", "SELECT COUNT(*)")
    count_rows = await db.execute_fetchall(count_query, params)
    total = count_rows[0][0] if count_rows else 0

    query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
    params.extend([limit, offset])

    rows = await db.execute_fetchall(query, params)
    items = []
    for r in rows:
        item = row_to_dict(r)
        if isinstance(item.get("perspectives"), str):
            try:
                item["perspectives"] = json.loads(item["perspectives"])
            except (json.JSONDecodeError, TypeError):
                item["perspectives"] = []
        items.append(item)

    return {
        "items": items,
        "total": total,
        "limit": limit,
        "offset": offset,
        "has_next": (offset + len(items)) < total,
    }


@router.get("/api/reviews/{review_id}", response_model=ReviewSessionDetailResponse)
async def get_review_session(review_id: int, db=Depends(get_db)):
    """Get full review session with findings grouped by perspective and convergences."""
    # Get session
    session_rows = await db.execute_fetchall(
        "SELECT * FROM review_sessions WHERE id = ?",
        (review_id,)
    )
    if not session_rows:
        raise HTTPException(404, detail="Review session not found")

    session = row_to_dict(session_rows[0])
    # Parse JSON string fields
    for field in ("perspectives",):
        if isinstance(session.get(field), str):
            try:
                session[field] = json.loads(session[field])
            except (json.JSONDecodeError, TypeError):
                session[field] = []

    # Get all findings for this session
    findings_rows = await db.execute_fetchall(
        "SELECT * FROM review_findings WHERE session_id = ? ORDER BY created_at ASC",
        (review_id,)
    )

    # Group findings by perspective
    findings_by_perspective: dict[str, Any] = {}
    all_findings = []
    severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}

    for row in findings_rows:
        finding = row_to_dict(row)
        all_findings.append(finding)

        perspective = finding["perspective"]
        if perspective not in findings_by_perspective:
            findings_by_perspective[perspective] = {
                "agent_status": finding["agent_status"],
                "findings": [],
                "finding_count": 0
            }

        findings_by_perspective[perspective]["findings"].append(finding)
        findings_by_perspective[perspective]["finding_count"] += 1

        # Count by severity
        severity = finding.get("severity", "info")
        if severity in severity_counts:
            severity_counts[severity] += 1

    # Get convergences
    convergence_rows = await db.execute_fetchall(
        "SELECT * FROM review_convergences WHERE session_id = ? ORDER BY convergence_count DESC, max_severity ASC",
        (review_id,)
    )
    convergences = []
    for r in convergence_rows:
        conv = row_to_dict(r)
        for field in ("finding_ids", "perspectives"):
            if isinstance(conv.get(field), str):
                try:
                    conv[field] = json.loads(conv[field])
                except (json.JSONDecodeError, TypeError):
                    conv[field] = []
        convergences.append(conv)

    # Get fix tasks linked to this session
    fix_task_rows = await db.execute_fetchall(
        "SELECT * FROM tasks WHERE review_session_id = ? AND task_type = 'fix' ORDER BY created_at ASC",
        (review_id,)
    )
    fix_tasks = [row_to_dict(r) for r in fix_task_rows]

    # Get verify task linked to this session
    verify_task_rows = await db.execute_fetchall(
        "SELECT * FROM tasks WHERE review_session_id = ? AND task_type = 'verify' ORDER BY created_at ASC LIMIT 1",
        (review_id,)
    )
    verify_task = row_to_dict(verify_task_rows[0]) if verify_task_rows else None

    # Build response
    response = {
        **session,
        "findings_by_perspective": findings_by_perspective,
        "convergences": convergences,
        "summary": {
            "total_findings": len(all_findings),
            "by_severity": severity_counts,
            "convergence_count": len(convergences)
        },
        "fix_tasks": fix_tasks,
        "verify_task": verify_task,
    }

    return response


@router.post("/api/reviews", response_model=ReviewSessionResponse, status_code=201)
async def simple_create_review_session(
    body: SimpleCreateReviewSessionRequest,
    db=Depends(get_db),
):
    """Create a review session without spawning review tasks.

    This is the MCP-compatible endpoint that just creates the session record.
    Unlike POST /api/tasks/{id}/reviews, it does NOT spawn background review tasks.
    """
    project_id = body.project_id.strip()
    task_id = body.task_id

    # Validate project exists
    project_rows = await db.execute_fetchall(
        "SELECT 1 FROM projects WHERE id = ?", (project_id,)
    )
    if not project_rows:
        raise HTTPException(404, detail=f"No project found with id '{project_id}'")

    # Validate task exists and has pr_url
    task_rows = await db.execute_fetchall(
        "SELECT pr_url FROM tasks WHERE id = ?", (task_id,)
    )
    if not task_rows:
        raise HTTPException(404, detail=f"No task found with id {task_id}")

    pr_url = task_rows[0]["pr_url"]
    if not pr_url:
        raise HTTPException(400, detail=f"Task {task_id} does not have a pr_url. Cannot create review session without a PR to review.")

    final_perspectives = body.perspectives or ["security", "maintainability", "architecture"]

    # Validate perspectives
    invalid = set(final_perspectives) - VALID_PERSPECTIVES
    if invalid:
        raise HTTPException(400, detail=f"Invalid perspectives: {', '.join(sorted(invalid))}. Must be one of: {', '.join(sorted(VALID_PERSPECTIVES))}")

    perspectives_json = json.dumps(final_perspectives)
    cursor = await db.execute(
        """INSERT INTO review_sessions (
            project_id, task_id, pr_url, pr_title, perspectives, status, fix_policy
        ) VALUES (?, ?, ?, '', ?, 'pending', ?)""",
        (project_id, task_id, pr_url, perspectives_json, body.fix_policy),
    )
    session_id = cursor.lastrowid

    # Create review_agents records so advance_pipeline can track perspective completion
    for perspective in final_perspectives:
        await db.execute(
            "INSERT INTO review_agents (session_id, perspective, agent_status) VALUES (?, ?, 'pending')",
            (session_id, perspective),
        )

    await db.commit()

    rows = await db.execute_fetchall(
        "SELECT * FROM review_sessions WHERE id = ?", (session_id,)
    )
    item = row_to_dict(rows[0])
    if isinstance(item.get("perspectives"), str):
        try:
            item["perspectives"] = json.loads(item["perspectives"])
        except (json.JSONDecodeError, TypeError):
            item["perspectives"] = []
    return item


@router.post("/api/reviews/{review_id}/findings", response_model=ReviewFindingResponse, status_code=201)
async def submit_review_finding(
    review_id: int,
    body: SubmitReviewFindingRequest,
    db=Depends(get_db),
):
    """Submit a review finding from a perspective agent."""
    # Validate severity
    if body.severity not in VALID_SEVERITIES:
        raise HTTPException(400, detail=f"Invalid severity '{body.severity}'. Must be one of: {', '.join(sorted(VALID_SEVERITIES))}")

    # Validate session exists
    session_rows = await db.execute_fetchall(
        "SELECT 1 FROM review_sessions WHERE id = ?", (review_id,)
    )
    if not session_rows:
        raise HTTPException(404, detail=f"No review session found with id {review_id}")

    final_confidence = body.confidence if body.confidence is not None else 0.8
    if final_confidence < 0.0 or final_confidence > 1.0:
        raise HTTPException(400, detail=f"Confidence must be between 0.0 and 1.0, got {final_confidence}")

    cursor = await db.execute(
        """INSERT INTO review_findings (
            session_id, perspective, agent_status, severity, category,
            file_path, line_start, line_end, title, description,
            suggestion, confidence
        ) VALUES (?, ?, 'in_progress', ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
        (
            review_id, body.perspective, body.severity, body.category,
            body.file_path, body.line_start, body.line_end, body.title,
            body.description, body.suggestion, final_confidence,
        ),
    )
    await db.commit()

    rows = await db.execute_fetchall(
        "SELECT * FROM review_findings WHERE id = ?", (cursor.lastrowid,)
    )
    return row_to_dict(rows[0])


@router.post("/api/reviews/{review_id}/perspectives/{perspective}/complete", response_model=CompleteReviewPerspectiveResponse)
async def complete_review_perspective(
    review_id: int,
    perspective: str,
    body: CompleteReviewPerspectiveRequest | None = Body(default=None),
    db=Depends(get_db),
):
    """Mark a perspective agent's review as complete and advance the pipeline."""
    # Validate session exists and get project_id + perspectives
    session_rows = await db.execute_fetchall(
        "SELECT project_id, perspectives FROM review_sessions WHERE id = ?", (review_id,)
    )
    if not session_rows:
        raise HTTPException(404, detail=f"No review session found with id {review_id}")

    session = session_rows[0]
    project_id = session["project_id"]
    try:
        all_perspectives = json.loads(session["perspectives"] or "[]")
    except (json.JSONDecodeError, TypeError):
        all_perspectives = []

    tokens_used = (body.tokens_used if body else None) or 0

    # Mark all findings for this perspective as completed
    cursor = await db.execute(
        """UPDATE review_findings
           SET agent_status = 'completed'
           WHERE session_id = ? AND perspective = ?""",
        (review_id, perspective),
    )
    await db.commit()

    # Mark the review_agents record as completed
    await db.execute(
        """UPDATE review_agents
           SET agent_status = 'completed', completed_at = CURRENT_TIMESTAMP
           WHERE session_id = ? AND perspective = ?""",
        (review_id, perspective),
    )
    await db.commit()

    # Try to advance the pipeline (no-op if other perspectives still pending)
    pipeline_advanced: dict | None = None
    if advance_pipeline is not None:
        try:
            pipeline_advanced = await advance_pipeline(db, review_id)
        except Exception:
            pass  # Pipeline advancement is best-effort; don't fail the request

    response = {
        "session_id": review_id,
        "perspective": perspective,
        "findings_updated": cursor.rowcount,
        "tokens_used": tokens_used,
        "status": "completed",
        "pipeline_advanced": pipeline_advanced,
    }

    # Check if all perspectives are now complete
    if all_perspectives:
        completed_rows = await db.execute_fetchall(
            """SELECT DISTINCT perspective FROM review_findings
               WHERE session_id = ? AND agent_status = 'completed'""",
            (review_id,),
        )
        completed_perspectives = {r["perspective"] for r in completed_rows}
        all_complete = set(all_perspectives) <= completed_perspectives

        if all_complete:
            # Emit session_completed event
            asyncio.create_task(emit_event(
                db, project_id, EventType.REVIEW_SESSION_COMPLETED,
                {"session_id": review_id, "perspectives": all_perspectives}
            ))

            # Also emit per-convergence events if any exist
            convergence_rows = await db.execute_fetchall(
                "SELECT * FROM review_convergences WHERE session_id = ?", (review_id,)
            )
            for conv_row in convergence_rows:
                conv = dict(conv_row)
                for field in ("finding_ids", "perspectives"):
                    if isinstance(conv.get(field), str):
                        try:
                            conv[field] = json.loads(conv[field])
                        except (json.JSONDecodeError, TypeError):
                            conv[field] = []
                asyncio.create_task(emit_event(
                    db, project_id, EventType.REVIEW_CONVERGENCE_FOUND,
                    {"session_id": review_id, "convergence": conv}
                ))

    return response


@router.post("/api/reviews/{review_id}/cancel", response_model=CancelReviewResponse)
async def cancel_review_session(review_id: int, db=Depends(get_db)):
    """Cancel an in-progress review session."""
    # Check session exists and is cancellable
    session_rows = await db.execute_fetchall(
        "SELECT status FROM review_sessions WHERE id = ?",
        (review_id,)
    )
    if not session_rows:
        raise HTTPException(404, detail="Review session not found")

    current_status = session_rows[0]["status"]
    if current_status not in ("pending", "in_progress"):
        raise HTTPException(400, detail=(
            f"Cannot cancel session with status '{current_status}'. Only pending or in_progress sessions can be cancelled."
        ))

    # Update status to cancelled
    await db.execute(
        "UPDATE review_sessions SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP WHERE id = ?",
        (review_id,)
    )
    await db.commit()

    return {
        "status": "cancelled",
        "message": f"Review session {review_id} cancelled"
    }


@router.post("/api/reviews/{review_id}/create-task", response_model=TaskResponse, status_code=201)
async def create_task_from_review(review_id: int, body: CreateTaskFromReviewRequest, db=Depends(get_db)):
    """Create a peon task from a convergence or finding."""
    convergence_id = body.convergence_id
    finding_id = body.finding_id

    if not convergence_id and not finding_id:
        raise HTTPException(400, detail="Must provide either convergence_id or finding_id")

    if convergence_id and finding_id:
        raise HTTPException(400, detail="Cannot provide both convergence_id and finding_id")

    # Get session to get project_id and task_id (for feature_id)
    session_rows = await db.execute_fetchall(
        "SELECT s.project_id, s.task_id, t.feature_id FROM review_sessions s JOIN tasks t ON s.task_id = t.id WHERE s.id = ?",
        (review_id,)
    )
    if not session_rows:
        raise HTTPException(404, detail="Review session not found")

    session = row_to_dict(session_rows[0])
    project_id = session["project_id"]
    feature_id = session["feature_id"]

    # Resolve the target branch for fix tasks
    target_branch = "main"
    if feature_id:
        feat_rows = await db.execute_fetchall(
            "SELECT branch FROM features WHERE id = ?", (feature_id,)
        )
        if feat_rows and feat_rows[0]["branch"]:
            target_branch = feat_rows[0]["branch"]

    # Map severity to priority
    severity_to_priority = {
        "critical": "critical",
        "high": "high",
        "medium": "medium",
        "low": "low",
        "info": "low"
    }

    task_id: int

    if convergence_id:
        # Create task from convergence
        conv_rows = await db.execute_fetchall(
            "SELECT * FROM review_convergences WHERE id = ? AND session_id = ?",
            (convergence_id, review_id)
        )
        if not conv_rows:
            raise HTTPException(404, detail="Convergence not found in this session")

        conv = row_to_dict(conv_rows[0])

        # Check if task already created
        if conv.get("created_task_id"):
            raise HTTPException(400, detail=f"Task already created for this convergence (task #{conv['created_task_id']})")

        # Create task
        title = f"[Review] {conv['category']} issue in {conv['file_path']}"
        description = f"**Convergence from code review (session #{review_id})**\n\n"
        description += f"**Category:** {conv['category']}\n"
        description += f"**File:** {conv['file_path']}\n"
        description += f"**Severity:** {conv['max_severity']}\n"
        description += f"**Flagged by {conv['convergence_count']} perspectives:** {', '.join(json.loads(conv['perspectives']))}\n\n"
        description += f"**Summary:**\n{conv['summary']}\n\n"
        description += f"**Branch Target:** Branch from `{target_branch}` and create PR targeting `{target_branch}`."

        priority = severity_to_priority.get(conv["max_severity"], "medium")

        cursor = await db.execute(
            "INSERT INTO tasks (project_id, feature_id, title, description, priority, status) VALUES (?, ?, ?, ?, ?, 'todo')",
            (project_id, feature_id, title, description, priority)
        )
        await db.commit()
        task_id = cursor.lastrowid

        # Update convergence with created_task_id
        await db.execute(
            "UPDATE review_convergences SET created_task_id = ? WHERE id = ?",
            (task_id, convergence_id)
        )
        await db.commit()

    else:  # finding_id
        # Create task from finding
        finding_rows = await db.execute_fetchall(
            "SELECT * FROM review_findings WHERE id = ? AND session_id = ?",
            (finding_id, review_id)
        )
        if not finding_rows:
            raise HTTPException(404, detail="Finding not found in this session")

        finding = row_to_dict(finding_rows[0])

        # Check if task already created
        if finding.get("created_task_id"):
            raise HTTPException(400, detail=f"Task already created for this finding (task #{finding['created_task_id']})")

        # Create task
        title = f"[Review] {finding['title']}"
        description = f"**Finding from code review (session #{review_id})**\n\n"
        description += f"**Perspective:** {finding['perspective']}\n"
        description += f"**Category:** {finding['category']}\n"
        description += f"**File:** {finding['file_path']}"
        if finding.get("line_start"):
            description += f" (lines {finding['line_start']}"
            if finding.get("line_end") and finding["line_end"] != finding["line_start"]:
                description += f"-{finding['line_end']}"
            description += ")"
        description += f"\n**Severity:** {finding['severity']}\n\n"
        description += f"**Description:**\n{finding['description']}\n\n"
        if finding.get("suggestion"):
            description += f"**Suggestion:**\n{finding['suggestion']}\n\n"
        description += f"**Branch Target:** Branch from `{target_branch}` and create PR targeting `{target_branch}`."

        priority = severity_to_priority.get(finding["severity"], "medium")

        cursor = await db.execute(
            "INSERT INTO tasks (project_id, feature_id, title, description, priority, status) VALUES (?, ?, ?, ?, ?, 'todo')",
            (project_id, feature_id, title, description, priority)
        )
        await db.commit()
        task_id = cursor.lastrowid

        # Update finding with created_task_id
        await db.execute(
            "UPDATE review_findings SET created_task_id = ? WHERE id = ?",
            (task_id, finding_id)
        )
        await db.commit()

    # Return created task
    task_rows = await db.execute_fetchall(
        "SELECT * FROM tasks WHERE id = ?",
        (task_id,)
    )
    return row_to_dict(task_rows[0])


@router.get("/api/reviews/{review_id}/pipeline", response_model=PipelineStatusResponse)
async def get_review_pipeline_status(review_id: int, db=Depends(get_db)):
    """Get detailed pipeline status for a review session.

    Returns a structured breakdown of each pipeline stage with counts and task
    statuses so callers can understand where work currently stands.
    """
    session_rows = await db.execute_fetchall(
        "SELECT id, pipeline_stage, fix_policy FROM review_sessions WHERE id = ?",
        (review_id,),
    )
    if not session_rows:
        raise HTTPException(404, detail="Review session not found")

    session = row_to_dict(session_rows[0])
    pipeline_stage = session.get("pipeline_stage") or "reviewing"
    fix_policy = session.get("fix_policy") or "auto"

    # --- reviewing stage info ---
    agent_rows = await db.execute_fetchall(
        "SELECT agent_status FROM review_agents WHERE session_id = ?",
        (review_id,),
    )
    perspective_count = len(agent_rows)

    findings_rows = await db.execute_fetchall(
        "SELECT COUNT(*) AS c FROM review_findings WHERE session_id = ?",
        (review_id,),
    )
    findings_count = findings_rows[0]["c"] if findings_rows else 0

    convergence_rows = await db.execute_fetchall(
        "SELECT COUNT(*) AS c FROM review_convergences WHERE session_id = ?",
        (review_id,),
    )
    convergence_count = convergence_rows[0]["c"] if convergence_rows else 0

    # --- fixing stage info ---
    fix_task_rows = await db.execute_fetchall(
        "SELECT status FROM tasks WHERE review_session_id = ? AND task_type = 'fix'",
        (review_id,),
    )
    fix_total = len(fix_task_rows)
    fix_done = sum(1 for r in fix_task_rows if r["status"] == "done")
    fix_pending = fix_total - fix_done

    # --- verifying stage info ---
    verify_rows = await db.execute_fetchall(
        """SELECT id, status FROM tasks
           WHERE review_session_id = ? AND task_type = 'verify'
           ORDER BY created_at DESC LIMIT 1""",
        (review_id,),
    )
    verify_task = row_to_dict(verify_rows[0]) if verify_rows else None

    stages = {
        "reviewing": {
            "status": _pipeline_stage_status(pipeline_stage, "reviewing"),
            "perspectives": perspective_count,
            "findings": findings_count,
            "convergences": convergence_count,
        },
        "fixing": {
            "status": _pipeline_stage_status(pipeline_stage, "fixing"),
            "total_tasks": fix_total,
            "done": fix_done,
            "pending": fix_pending,
        },
        "verifying": {
            "status": _pipeline_stage_status(pipeline_stage, "verifying"),
            "verify_task_id": verify_task["id"] if verify_task else None,
            "verify_task_status": verify_task["status"] if verify_task else None,
        },
        "complete": {
            "status": "complete" if pipeline_stage == "complete" else "pending",
        },
    }

    return {
        "session_id": review_id,
        "pipeline_stage": pipeline_stage,
        "fix_policy": fix_policy,
        "stages": stages,
    }


@router.post("/api/reviews/{review_id}/advance", response_model=AdvancePipelineResponse)
async def advance_review_pipeline(review_id: int, db=Depends(get_db)):
    """Manually attempt to advance the pipeline to the next stage.

    Idempotent — safe to call multiple times. Returns the advance result
    including whether the pipeline moved and which stage it transitioned to.
    """
    session_rows = await db.execute_fetchall(
        "SELECT 1 FROM review_sessions WHERE id = ?",
        (review_id,),
    )
    if not session_rows:
        raise HTTPException(404, detail="Review session not found")

    if advance_pipeline is None:
        raise HTTPException(501, detail="Review orchestration module not available")

    result = await advance_pipeline(db, review_id)
    return result


@router.post("/api/reviews/{review_id}/retry", response_model=RetryPipelineResponse)
async def retry_review_pipeline(review_id: int, db=Depends(get_db)):
    """Reset a failed pipeline stage and retry.

    If the pipeline is in 'failed' state, inspects which tasks failed and
    resets them to 'todo' so the pipeline can continue.

    - Failed verify task: reset to 'todo', pipeline stage reverts to 'verifying'
    - Failed fix tasks: reset to 'todo', pipeline stage reverts to 'fixing'
    """
    session_rows = await db.execute_fetchall(
        "SELECT id, pipeline_stage FROM review_sessions WHERE id = ?",
        (review_id,),
    )
    if not session_rows:
        raise HTTPException(404, detail="Review session not found")

    session = row_to_dict(session_rows[0])
    if session.get("pipeline_stage") != "failed":
        raise HTTPException(
            400,
            detail=f"Pipeline is not in failed state (current stage: '{session.get('pipeline_stage')}')",
        )

    if _update_pipeline_stage is None:
        raise HTTPException(501, detail="Review orchestration module not available")

    # Check for a failed verify task first (higher priority to fix)
    failed_verify_rows = await db.execute_fetchall(
        """SELECT id FROM tasks
           WHERE review_session_id = ? AND task_type = 'verify'
             AND status IN ('timeout', 'cancelled')""",
        (review_id,),
    )
    if failed_verify_rows:
        verify_id = failed_verify_rows[0]["id"]
        await db.execute(
            "UPDATE tasks SET status = 'todo', started_at = NULL WHERE id = ?",
            (verify_id,),
        )
        await db.commit()
        await _update_pipeline_stage(db, review_id, "verifying")
        return {"retried": True, "stage": "verifying", "verify_task_id": verify_id, "reset_tasks": []}

    # Check for failed fix tasks
    failed_fix_rows = await db.execute_fetchall(
        """SELECT id FROM tasks
           WHERE review_session_id = ? AND task_type = 'fix'
             AND status IN ('timeout', 'cancelled')""",
        (review_id,),
    )
    if failed_fix_rows:
        task_ids = [r["id"] for r in failed_fix_rows]
        for tid in task_ids:
            await db.execute(
                "UPDATE tasks SET status = 'todo', started_at = NULL WHERE id = ?",
                (tid,),
            )
        await db.commit()
        await _update_pipeline_stage(db, review_id, "fixing")
        return {"retried": True, "stage": "fixing", "reset_tasks": task_ids, "verify_task_id": None}

    raise HTTPException(
        400,
        detail="No failed tasks found to retry. Pipeline stage is 'failed' but no timed-out or cancelled tasks were found.",
    )
