"""FastAPI routes for tool policy management."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException, Query

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.policies.resolver import resolve_policy
from peon_mcp.policies.schemas import (
    ResolvedPolicy,
    ToolPolicyCreate,
    ToolPolicyResponse,
    ToolPolicyUpdate,
)

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


@router.post("/api/policies", response_model=ToolPolicyResponse, status_code=201)
async def create_policy(body: ToolPolicyCreate, db=Depends(get_db)):
    """Create a tool policy for a project."""
    # Validate project exists
    project_rows = await db.execute_fetchall(
        "SELECT 1 FROM projects WHERE id = ?", (body.project_id,)
    )
    if not project_rows:
        raise HTTPException(404, detail=f"No project found with id '{body.project_id}'")

    # If is_default, clear existing defaults for project
    if body.is_default:
        await db.execute(
            "UPDATE tool_policies SET is_default = 0 WHERE project_id = ?",
            (body.project_id,),
        )

    cursor = await db.execute(
        """INSERT INTO tool_policies
           (project_id, name, profile, allow, also_allow, deny,
            fs_workspace_only, exec_security, exec_timeout_seconds, is_default)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
        (
            body.project_id,
            body.name,
            body.profile,
            body.allow,
            body.also_allow,
            body.deny,
            int(body.fs_workspace_only),
            body.exec_security,
            body.exec_timeout_seconds,
            int(body.is_default),
        ),
    )
    await db.commit()

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


@router.get("/api/policies", response_model=PaginatedResponse[ToolPolicyResponse])
async def list_policies(
    project_id: str = Query(...),
    limit: int = Query(default=50, ge=1, le=200),
    offset: int = Query(default=0, ge=0),
    db=Depends(get_db),
):
    """List tool policies for a project."""
    count_rows = await db.execute_fetchall(
        "SELECT COUNT(*) FROM tool_policies WHERE project_id = ?", (project_id,)
    )
    total = count_rows[0][0] if count_rows else 0

    rows = await db.execute_fetchall(
        "SELECT * FROM tool_policies WHERE project_id = ? ORDER BY is_default DESC, created_at ASC LIMIT ? OFFSET ?",
        (project_id, limit, offset),
    )
    items = [_normalize(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/policies/{policy_id}", response_model=ToolPolicyResponse)
async def get_policy(policy_id: int, db=Depends(get_db)):
    """Get a single tool policy by ID."""
    rows = await db.execute_fetchall(
        "SELECT * FROM tool_policies WHERE id = ?", (policy_id,)
    )
    if not rows:
        raise HTTPException(404, detail=f"No policy found with id {policy_id}")
    return _normalize(row_to_dict(rows[0]))


@router.put("/api/policies/{policy_id}", response_model=ToolPolicyResponse)
async def update_policy(policy_id: int, body: ToolPolicyUpdate, db=Depends(get_db)):
    """Update a tool policy."""
    rows = await db.execute_fetchall(
        "SELECT * FROM tool_policies WHERE id = ?", (policy_id,)
    )
    if not rows:
        raise HTTPException(404, detail=f"No policy found with id {policy_id}")

    existing = row_to_dict(rows[0])
    project_id = existing["project_id"]

    updates: dict = {}
    if body.name is not None:
        updates["name"] = body.name
    if body.profile is not None:
        updates["profile"] = body.profile
    if body.allow is not None:
        updates["allow"] = body.allow
    if body.also_allow is not None:
        updates["also_allow"] = body.also_allow
    if body.deny is not None:
        updates["deny"] = body.deny
    if body.fs_workspace_only is not None:
        updates["fs_workspace_only"] = int(body.fs_workspace_only)
    if body.exec_security is not None:
        updates["exec_security"] = body.exec_security
    if body.exec_timeout_seconds is not None:
        updates["exec_timeout_seconds"] = body.exec_timeout_seconds
    if body.is_default is not None:
        updates["is_default"] = int(body.is_default)
        # If setting as default, clear other defaults
        if body.is_default:
            await db.execute(
                "UPDATE tool_policies SET is_default = 0 WHERE project_id = ? AND id != ?",
                (project_id, policy_id),
            )

    if updates:
        updates["updated_at"] = "CURRENT_TIMESTAMP"
        set_clause = ", ".join(
            f"{k} = CURRENT_TIMESTAMP" if v == "CURRENT_TIMESTAMP" else f"{k} = ?"
            for k, v in updates.items()
        )
        values = [v for v in updates.values() if v != "CURRENT_TIMESTAMP"]
        values.append(policy_id)
        await db.execute(
            f"UPDATE tool_policies SET {set_clause} WHERE id = ?",
            values,
        )
        await db.commit()

    rows = await db.execute_fetchall(
        "SELECT * FROM tool_policies WHERE id = ?", (policy_id,)
    )
    return _normalize(row_to_dict(rows[0]))


@router.delete("/api/policies/{policy_id}", status_code=204)
async def delete_policy(policy_id: int, db=Depends(get_db)):
    """Delete a tool policy."""
    rows = await db.execute_fetchall(
        "SELECT 1 FROM tool_policies WHERE id = ?", (policy_id,)
    )
    if not rows:
        raise HTTPException(404, detail=f"No policy found with id {policy_id}")

    await db.execute("DELETE FROM tool_policies WHERE id = ?", (policy_id,))
    await db.commit()


@router.get("/api/policies/{policy_id}/resolve", response_model=ResolvedPolicy)
async def resolve_policy_endpoint(policy_id: int, db=Depends(get_db)):
    """Return the fully resolved tool list for a policy.

    Expands the profile preset, merges allow/also_allow, and subtracts deny
    to produce the final flat list of permitted tools.
    """
    rows = await db.execute_fetchall(
        "SELECT * FROM tool_policies WHERE id = ?", (policy_id,)
    )
    if not rows:
        raise HTTPException(404, detail=f"No policy found with id {policy_id}")

    policy = row_to_dict(rows[0])
    return resolve_policy(policy)


def _normalize(policy: dict) -> dict:
    """Convert SQLite integer booleans to Python bools."""
    policy["fs_workspace_only"] = bool(policy.get("fs_workspace_only", 1))
    policy["is_default"] = bool(policy.get("is_default", 0))
    return policy
