"""Agent session REST API routes."""

from __future__ import annotations

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

from peon_mcp.common.auth import AuthInfo, get_auth
from peon_mcp.common.dependencies import get_db
from peon_mcp.common.schemas import PaginatedResponse
from peon_mcp.common.scoping import (
    authorize_project_access,
    authorize_entity_project_access,
    get_accessible_project_ids,
)
from peon_mcp.db import row_to_dict
from peon_mcp.sessions.utils import mark_stale_sessions_timeout
from peon_mcp.sessions.schemas import (
    SessionCreate,
    SessionDetailResponse,
    SessionEventCreate,
    SessionEventResponse,
    SessionResponse,
    SessionStatsResponse,
    SessionUpdate,
)

router = APIRouter(tags=["Agent Sessions"])


@router.post("/api/sessions/cleanup-stale")
async def cleanup_stale_sessions(
    body: dict = None,
    request: Request = None,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Mark all 'running' sessions for a project as 'failed'.

    Called on loop startup to clean up sessions left behind by a killed process.
    """
    project_id = (body or {}).get("project_id", "")
    if not project_id:
        raise HTTPException(400, detail="project_id is required")

    if auth.is_session:
        await authorize_project_access(db, auth, project_id, request)

    cursor = await db.execute(
        "UPDATE agent_sessions"
        " SET status = 'failed',"
        "     error_summary = 'Marked failed on loop startup (prior process was killed)',"
        "     ended_at = COALESCE(ended_at, CURRENT_TIMESTAMP),"
        "     duration_seconds = ROUND(EXTRACT(EPOCH FROM (COALESCE(ended_at, CURRENT_TIMESTAMP) - started_at))::numeric, 2),"
        "     updated_at = CURRENT_TIMESTAMP"
        " WHERE project_id = ? AND status = 'running'",
        (project_id,),
    )
    await db.commit()
    return {"updated": cursor.rowcount}


@router.post("/api/sessions/cleanup-timeout")
async def cleanup_timeout_sessions(
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Mark all 'running' sessions older than 1 hour as 'timeout'.

    Intended for periodic or manual cleanup of sessions that timed out without
    updating their own status (e.g. the agent was killed mid-run).
    """
    updated = await mark_stale_sessions_timeout(db)
    return {"updated": updated}


@router.post("/api/sessions", response_model=SessionResponse, status_code=201)
async def create_session(
    body: SessionCreate,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Create a new agent session (called when an agent starts)."""
    if auth.is_session:
        await authorize_project_access(db, auth, body.project_id, request)

    rows = await db.execute_fetchall(
        """INSERT INTO agent_sessions
           (project_id, task_id, loop_id, model, hostname)
           VALUES (?, ?, ?, ?, ?)
           RETURNING *""",
        (body.project_id, body.task_id, body.loop_id, body.model, body.hostname),
    )
    await db.commit()
    row = row_to_dict(rows[0])
    row["duration_seconds"] = None
    return row


@router.get("/api/sessions", response_model=PaginatedResponse[SessionResponse])
async def list_sessions(
    request: Request,
    project_id: str | None = Query(default=None),
    task_id: int | None = Query(default=None),
    status: str | None = Query(default=None),
    limit: int = Query(default=50, ge=1, le=200),
    offset: int = Query(default=0, ge=0),
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """List agent sessions with optional filters."""
    conditions = []
    params: list = []

    # Scope to accessible projects for session auth
    if auth.is_session:
        if project_id:
            await authorize_project_access(db, auth, project_id, request)
            conditions.append("project_id = ?")
            params.append(project_id)
        else:
            accessible_ids = await get_accessible_project_ids(db, auth, request)
            if accessible_ids is not None:
                if not accessible_ids:
                    return {"items": [], "total": 0, "limit": limit, "offset": offset, "has_next": False}
                placeholders = ",".join("?" * len(accessible_ids))
                conditions.append(f"project_id IN ({placeholders})")
                params.extend(accessible_ids)
    else:
        if project_id is not None:
            conditions.append("project_id = ?")
            params.append(project_id)

    if task_id is not None:
        conditions.append("task_id = ?")
        params.append(task_id)
    if status is not None:
        conditions.append("status = ?")
        params.append(status)

    where = f"WHERE {' AND '.join(conditions)}" if conditions else ""

    count_rows = await db.execute_fetchall(
        f"SELECT COUNT(*) FROM agent_sessions {where}", params
    )
    total = count_rows[0]["count"] if count_rows else 0

    rows = await db.execute_fetchall(
        f"""SELECT *,
               CASE WHEN ended_at IS NOT NULL AND started_at IS NOT NULL
                    THEN ROUND(EXTRACT(EPOCH FROM (ended_at - started_at))::numeric, 2)
                    ELSE NULL END AS duration_seconds
            FROM agent_sessions {where}
            ORDER BY started_at DESC LIMIT ? OFFSET ?""",
        params + [limit, offset],
    )
    items = [row_to_dict(r) for r in rows]

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


@router.get("/api/sessions/stats", response_model=SessionStatsResponse)
async def get_session_stats(
    request: Request,
    project_id: str = Query(...),
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Get aggregate stats for a project's agent sessions."""
    if auth.is_session:
        await authorize_project_access(db, auth, project_id, request)

    rows = await db.execute_fetchall(
        """SELECT
               COUNT(*) AS total_sessions,
               SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_sessions,
               SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed_sessions,
               AVG(CASE WHEN ended_at IS NOT NULL AND started_at IS NOT NULL
                        THEN EXTRACT(EPOCH FROM (ended_at - started_at))
                        ELSE NULL END) AS avg_duration_seconds,
               SUM(tokens_input) AS total_tokens_input,
               SUM(tokens_output) AS total_tokens_output,
               SUM(cost_usd) AS total_cost_usd
           FROM agent_sessions
           WHERE project_id = ?""",
        (project_id,),
    )
    agg = row_to_dict(rows[0]) if rows else {}

    total = agg.get("total_sessions") or 0
    completed = agg.get("completed_sessions") or 0
    failed = agg.get("failed_sessions") or 0
    success_rate = (completed / total) if total > 0 else 0.0

    # Sessions grouped by model
    model_rows = await db.execute_fetchall(
        """SELECT model, COUNT(*) AS count
           FROM agent_sessions
           WHERE project_id = ?
           GROUP BY model""",
        (project_id,),
    )
    sessions_by_model = {r["model"] or "": r["count"] for r in model_rows}

    return {
        "project_id": project_id,
        "total_sessions": total,
        "completed_sessions": completed,
        "failed_sessions": failed,
        "success_rate": round(success_rate, 4),
        "avg_duration_seconds": agg.get("avg_duration_seconds"),
        "total_tokens_input": agg.get("total_tokens_input") or 0,
        "total_tokens_output": agg.get("total_tokens_output") or 0,
        "total_cost_usd": agg.get("total_cost_usd") or 0.0,
        "sessions_by_model": sessions_by_model,
    }


@router.get("/api/sessions/{session_id}", response_model=SessionDetailResponse)
async def get_session(
    session_id: int,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Get a session with all its events."""
    await authorize_entity_project_access(
        db, auth, request, "agent_sessions", session_id,
    )

    rows = await db.execute_fetchall(
        """SELECT s.*,
               CASE WHEN s.ended_at IS NOT NULL AND s.started_at IS NOT NULL
                    THEN ROUND(EXTRACT(EPOCH FROM (s.ended_at - s.started_at))::numeric, 2)
                    ELSE NULL END AS duration_seconds,
               t.title AS task_title
           FROM agent_sessions s
           LEFT JOIN tasks t ON t.id = s.task_id
           WHERE s.id = ?""",
        (session_id,),
    )
    if not rows:
        raise HTTPException(404, detail=f"Session {session_id} not found")

    session = row_to_dict(rows[0])

    event_rows = await db.execute_fetchall(
        "SELECT * FROM session_events WHERE session_id = ? ORDER BY timestamp ASC",
        (session_id,),
    )
    session["events"] = [row_to_dict(r) for r in event_rows]
    return session


@router.put("/api/sessions/{session_id}", response_model=SessionResponse)
async def update_session(
    session_id: int,
    body: SessionUpdate,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Update a session (end it, record tokens, etc.)."""
    await authorize_entity_project_access(
        db, auth, request, "agent_sessions", session_id,
    )

    rows = await db.execute_fetchall(
        "SELECT id FROM agent_sessions WHERE id = ?", (session_id,)
    )
    if not rows:
        raise HTTPException(404, detail=f"Session {session_id} not found")

    updates: list[str] = ["updated_at = CURRENT_TIMESTAMP"]
    params: list = []

    if body.task_id is not None:
        updates.append("task_id = ?")
        params.append(body.task_id)
    if body.status is not None:
        updates.append("status = ?")
        params.append(body.status)
    if body.exit_code is not None:
        updates.append("exit_code = ?")
        params.append(body.exit_code)
    if body.tokens_input is not None:
        updates.append("tokens_input = ?")
        params.append(body.tokens_input)
    if body.tokens_output is not None:
        updates.append("tokens_output = ?")
        params.append(body.tokens_output)
    if body.cost_usd is not None:
        updates.append("cost_usd = ?")
        params.append(body.cost_usd)
    if body.error_summary is not None:
        updates.append("error_summary = ?")
        params.append(body.error_summary)
    if body.ended_at is not None:
        updates.append("ended_at = ?")
        params.append(body.ended_at)
        # Compute and store duration to avoid the SELECT * shadowing issue where
        # the stored NULL duration_seconds column hides the computed alias.
        updates.append(
            "duration_seconds = ROUND(EXTRACT(EPOCH FROM (?::timestamptz - started_at))::numeric, 2)"
        )
        params.append(body.ended_at)

    if len(updates) > 1:
        await db.execute(
            f"UPDATE agent_sessions SET {', '.join(updates)} WHERE id = ?",
            params + [session_id],
        )
        await db.commit()

    rows = await db.execute_fetchall(
        """SELECT *,
               CASE WHEN ended_at IS NOT NULL AND started_at IS NOT NULL
                    THEN ROUND(EXTRACT(EPOCH FROM (ended_at - started_at))::numeric, 2)
                    ELSE NULL END AS duration_seconds
           FROM agent_sessions WHERE id = ?""",
        (session_id,),
    )
    return row_to_dict(rows[0])


@router.post(
    "/api/sessions/{session_id}/events",
    response_model=SessionEventResponse,
    status_code=201,
)
async def add_session_event(
    session_id: int,
    body: SessionEventCreate,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Append an event to a session."""
    await authorize_entity_project_access(
        db, auth, request, "agent_sessions", session_id,
    )

    rows = await db.execute_fetchall(
        "SELECT id FROM agent_sessions WHERE id = ?", (session_id,)
    )
    if not rows:
        raise HTTPException(404, detail=f"Session {session_id} not found")

    event_rows = await db.execute_fetchall(
        """INSERT INTO session_events (session_id, event_type, tool_name, detail, tool_args)
           VALUES (?, ?, ?, ?, ?)
           RETURNING *""",
        (session_id, body.event_type, body.tool_name, body.detail, body.tool_args),
    )
    await db.commit()
    return row_to_dict(event_rows[0])
