"""Approval REST API routes."""

from __future__ import annotations

import json
import logging
from datetime import datetime, timedelta, timezone
from uuid import UUID

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

from peon_mcp.approvals.schemas import (
    ApprovalResponse,
    CreateApprovalRequest,
    RespondApprovalRequest,
)
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.db import row_to_dict

logger = logging.getLogger(__name__)

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

# Expiry durations per priority (None = never expires)
_EXPIRY: dict[str, timedelta | None] = {
    "critical": None,
    "medium": timedelta(minutes=30),
    "low": timedelta(hours=2),
}


def _expires_at(priority: str) -> datetime | None:
    delta = _EXPIRY.get(priority)
    if delta is None:
        return None
    return datetime.now(tz=timezone.utc) + delta


@router.post("/api/approvals", response_model=ApprovalResponse, status_code=201)
async def create_approval(
    body: CreateApprovalRequest,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Agent creates an approval request, optionally pausing the associated task."""
    if not body.title.strip():
        raise HTTPException(400, detail="title is required")
    if not body.options:
        raise HTTPException(400, detail="options must be a non-empty list")

    expires_at = _expires_at(body.priority)
    options_json = json.dumps(body.options)

    async with db.transaction() as tx:
        # Validate task exists if provided
        if body.task_id is not None:
            task_rows = await tx.execute_fetchall(
                "SELECT id, status FROM tasks WHERE id = ?", (body.task_id,)
            )
            if not task_rows:
                raise HTTPException(404, detail=f"Task {body.task_id} not found")

        row = await tx.execute_fetchone(
            """
            INSERT INTO approvals
                (agent_session_id, task_id, title, context, options, priority, expires_at)
            VALUES (?, ?, ?, ?, ?::jsonb, ?, ?)
            RETURNING *
            """,
            (
                body.agent_session_id,
                body.task_id,
                body.title.strip(),
                body.context,
                options_json,
                body.priority,
                expires_at,
            ),
        )

        # Move task to waiting_for_input if it's currently in_progress
        if body.task_id is not None and task_rows:
            task_status = task_rows[0]["status"]
            if task_status == "in_progress":
                await tx.execute(
                    "UPDATE tasks SET status = 'waiting_for_input', updated_at = CURRENT_TIMESTAMP"
                    " WHERE id = ? AND status = 'in_progress'",
                    (body.task_id,),
                )

    return row_to_dict(row)


@router.get("/api/approvals", response_model=PaginatedResponse[ApprovalResponse])
async def list_approvals(
    request: Request,
    agent_session_id: int | 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 approvals with optional filters. Used by agents to poll for responses."""
    # Build dynamic WHERE clause
    conditions = []
    params: list = []

    if agent_session_id is not None:
        conditions.append("agent_session_id = ?")
        params.append(agent_session_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 approvals {where}", params
    )
    total = count_rows[0]["count"] if count_rows else 0

    rows = await db.execute_fetchall(
        f"SELECT * FROM approvals {where} ORDER BY created_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/approvals/{approval_id}", response_model=ApprovalResponse)
async def get_approval(
    approval_id: UUID,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Get a single approval by ID."""
    row = await db.execute_fetchone(
        "SELECT * FROM approvals WHERE id = ?", (approval_id,)
    )
    if not row:
        raise HTTPException(404, detail=f"Approval {approval_id} not found")
    return row_to_dict(row)


@router.patch("/api/approvals/{approval_id}", response_model=ApprovalResponse)
async def respond_approval(
    approval_id: UUID,
    body: RespondApprovalRequest,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Human responds to an approval request, optionally resuming the associated task.

    The associated task is always resumed to in_progress regardless of the approval
    outcome (approved/rejected/deferred). Agents should call get_approval after the
    task resumes to retrieve the response and decide how to proceed.
    """
    is_expired = False
    updated_row = None

    async with db.transaction() as tx:
        rows = await tx.execute_fetchall(
            "SELECT * FROM approvals WHERE id = ?", (approval_id,)
        )
        if not rows:
            raise HTTPException(404, detail=f"Approval {approval_id} not found")

        approval = row_to_dict(rows[0])
        if approval["status"] != "pending":
            raise HTTPException(
                400, detail=f"Approval is already {approval['status']}"
            )

        # Check whether the approval has passed its expiry deadline
        expires_at = approval.get("expires_at")
        if expires_at is not None:
            now = datetime.now(tz=timezone.utc)
            if isinstance(expires_at, str):
                expires_at = datetime.fromisoformat(expires_at)
            if now > expires_at:
                is_expired = True
                await tx.execute(
                    "UPDATE approvals SET status = 'expired', updated_at = CURRENT_TIMESTAMP"
                    " WHERE id = ?",
                    (approval_id,),
                )

        if not is_expired:
            # Validate the response is one of the allowed options
            options = approval.get("options") or []
            if body.response not in options:
                raise HTTPException(
                    400,
                    detail=f"Response must be one of: {options}",
                )

            # Update approval
            updated_row = await tx.execute_fetchone(
                """
                UPDATE approvals
                SET status = ?, response = ?, updated_at = CURRENT_TIMESTAMP
                WHERE id = ?
                RETURNING *
                """,
                (body.status, body.response, approval_id),
            )

            # Move task back to in_progress if it was waiting
            task_id = approval.get("task_id")
            if task_id is not None:
                await tx.execute(
                    "UPDATE tasks SET status = 'in_progress', updated_at = CURRENT_TIMESTAMP"
                    " WHERE id = ? AND status = 'waiting_for_input'",
                    (task_id,),
                )

    # Raise after the transaction commits so the expired status is persisted
    if is_expired:
        raise HTTPException(409, detail="Approval has expired and can no longer be responded to")

    return row_to_dict(updated_row)
