"""Terminal whitelist management API.

Endpoints:
  - GET    /api/v1/terminal/whitelist/user          — list current user's whitelist
  - POST   /api/v1/terminal/whitelist/user          — add a pattern to user whitelist
  - DELETE /api/v1/terminal/whitelist/user/{id}     — remove a pattern

  - GET    /api/v1/terminal/blocklist               — list blocklist (admin only)
  - POST   /api/v1/terminal/blocklist               — add a pattern (admin only)
  - DELETE /api/v1/terminal/blocklist/{id}          — remove a pattern (admin only)

  - GET    /api/v1/terminal/audit-logs              — query audit logs (admin only)
"""

from __future__ import annotations

import logging
import re
import uuid
from typing import Any

import aiosqlite
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import BaseModel, Field, field_validator

from agentkit.server.auth.dependencies import (
    _resolve_db_path,
    get_current_user,
    require_permission,
)
from agentkit.server.auth.permissions import Permission

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/terminal", tags=["terminal-whitelist"])


# Whitelist pattern validation: 1-256 chars, no newlines, no shell metachar
# chains. We allow spaces (for prefixes like "git push") but reject control
# chars and command separators.
_PATTERN_RE = re.compile(r"^[^\n\r;&|`$<>]{1,256}$")


# ── Schemas ───────────────────────────────────────────────────────────


class WhitelistEntry(BaseModel):
    id: str
    command_pattern: str
    scope: str = "user"
    created_at: str


class WhitelistListResponse(BaseModel):
    entries: list[WhitelistEntry]


class AddWhitelistRequest(BaseModel):
    command_pattern: str = Field(..., min_length=1, max_length=256)

    @field_validator("command_pattern")
    @classmethod
    def _validate_pattern(cls, v: str) -> str:
        v = v.strip()
        if not v:
            raise ValueError("command_pattern cannot be empty")
        if not _PATTERN_RE.match(v):
            raise ValueError(
                "command_pattern contains invalid characters "
                "(newlines, shell separators, or backticks are not allowed)"
            )
        return v


class BlocklistEntry(BaseModel):
    id: str
    command_pattern: str
    reason: str | None = None
    created_at: str


class BlocklistListResponse(BaseModel):
    entries: list[BlocklistEntry]


class AddBlocklistRequest(BaseModel):
    command_pattern: str = Field(..., min_length=1, max_length=256)
    reason: str | None = Field(None, max_length=512)

    @field_validator("command_pattern")
    @classmethod
    def _validate_pattern(cls, v: str) -> str:
        v = v.strip()
        if not v:
            raise ValueError("command_pattern cannot be empty")
        if not _PATTERN_RE.match(v):
            raise ValueError(
                "command_pattern contains invalid characters "
                "(newlines, shell separators, or backticks are not allowed)"
            )
        return v


class AuditLogEntry(BaseModel):
    id: str
    user_id: str | None = None
    username: str | None = None
    session_id: str
    command: str
    decision: str
    reason: str | None = None
    cwd: str | None = None
    exit_code: int | None = None
    terminal_mode: str = "local"
    created_at: str


class AuditLogListResponse(BaseModel):
    entries: list[AuditLogEntry]
    total: int


# ── User whitelist endpoints ──────────────────────────────────────────


@router.get("/whitelist/user", response_model=WhitelistListResponse)
async def list_user_whitelist(
    request: Request,
    user: dict[str, Any] = Depends(require_permission(Permission.TERMINAL_LOCAL_USE)),
):
    """List the current user's terminal whitelist."""
    db_path = await _resolve_db_path(request)

    if user.get("dev_mode"):
        # Dev mode has no DB-backed whitelist
        return WhitelistListResponse(entries=[])

    user_id = user["user_id"]
    async with aiosqlite.connect(str(db_path)) as db:
        db.row_factory = aiosqlite.Row
        cursor = await db.execute(
            "SELECT id, command_pattern, scope, created_at "
            "FROM terminal_whitelist_user WHERE user_id = ? AND scope = 'user' "
            "ORDER BY created_at DESC",
            (user_id,),
        )
        rows = await cursor.fetchall()

    return WhitelistListResponse(
        entries=[
            WhitelistEntry(
                id=row["id"],
                command_pattern=row["command_pattern"],
                scope=row["scope"],
                created_at=row["created_at"],
            )
            for row in rows
        ]
    )


@router.post("/whitelist/user", response_model=WhitelistEntry, status_code=201)
async def add_user_whitelist(
    payload: AddWhitelistRequest,
    request: Request,
    user: dict[str, Any] = Depends(require_permission(Permission.TERMINAL_LOCAL_USE)),
):
    """Add a command pattern to the current user's whitelist."""
    db_path = await _resolve_db_path(request)

    if user.get("dev_mode"):
        raise HTTPException(
            status_code=400,
            detail="User whitelist is not available in dev mode (no authenticated user)",
        )

    user_id = user["user_id"]
    entry_id = str(uuid.uuid4())

    async with aiosqlite.connect(str(db_path)) as db:
        # Check for duplicate
        cursor = await db.execute(
            "SELECT id FROM terminal_whitelist_user "
            "WHERE user_id = ? AND command_pattern = ? AND scope = 'user'",
            (user_id, payload.command_pattern),
        )
        existing = await cursor.fetchone()
        if existing:
            raise HTTPException(
                status_code=409,
                detail="Pattern already exists in user whitelist",
            )

        from agentkit.server.auth.models import _now_iso

        await db.execute(
            """INSERT INTO terminal_whitelist_user
               (id, user_id, command_pattern, scope, created_at, created_by)
               VALUES (?, ?, ?, 'user', ?, ?)""",
            (entry_id, user_id, payload.command_pattern, _now_iso(), user_id),
        )
        await db.commit()

        db.row_factory = aiosqlite.Row
        cursor = await db.execute(
            "SELECT id, command_pattern, scope, created_at "
            "FROM terminal_whitelist_user WHERE id = ?",
            (entry_id,),
        )
        row = await cursor.fetchone()

    return WhitelistEntry(
        id=row["id"],
        command_pattern=row["command_pattern"],
        scope=row["scope"],
        created_at=row["created_at"],
    )


@router.delete("/whitelist/user/{entry_id}", status_code=204)
async def delete_user_whitelist(
    entry_id: str,
    request: Request,
    user: dict[str, Any] = Depends(require_permission(Permission.TERMINAL_LOCAL_USE)),
):
    """Remove a pattern from the current user's whitelist."""
    db_path = await _resolve_db_path(request)

    if user.get("dev_mode"):
        raise HTTPException(
            status_code=400,
            detail="User whitelist is not available in dev mode",
        )

    user_id = user["user_id"]
    async with aiosqlite.connect(str(db_path)) as db:
        cursor = await db.execute(
            "DELETE FROM terminal_whitelist_user "
            "WHERE id = ? AND user_id = ? AND scope = 'user'",
            (entry_id, user_id),
        )
        await db.commit()

        if cursor.rowcount == 0:
            raise HTTPException(
                status_code=404,
                detail="Whitelist entry not found or not owned by current user",
            )


# ── Blocklist endpoints (admin only) ──────────────────────────────────


@router.get(
    "/blocklist",
    response_model=BlocklistListResponse,
    dependencies=[Depends(require_permission(Permission.TERMINAL_WHITELIST_MANAGE))],
)
async def list_blocklist(request: Request):
    """List the global terminal blocklist."""
    db_path = await _resolve_db_path(request)

    async with aiosqlite.connect(str(db_path)) as db:
        db.row_factory = aiosqlite.Row
        cursor = await db.execute(
            "SELECT id, command_pattern, reason, created_at "
            "FROM terminal_blocklist ORDER BY created_at DESC"
        )
        rows = await cursor.fetchall()

    return BlocklistListResponse(
        entries=[
            BlocklistEntry(
                id=row["id"],
                command_pattern=row["command_pattern"],
                reason=row["reason"],
                created_at=row["created_at"],
            )
            for row in rows
        ]
    )


@router.post(
    "/blocklist",
    response_model=BlocklistEntry,
    status_code=201,
    dependencies=[Depends(require_permission(Permission.TERMINAL_WHITELIST_MANAGE))],
)
async def add_blocklist(payload: AddBlocklistRequest, request: Request):
    """Add a command pattern to the global blocklist."""
    db_path = await _resolve_db_path(request)
    user = await get_current_user(request)
    entry_id = str(uuid.uuid4())

    async with aiosqlite.connect(str(db_path)) as db:
        cursor = await db.execute(
            "SELECT id FROM terminal_blocklist WHERE command_pattern = ?",
            (payload.command_pattern,),
        )
        existing = await cursor.fetchone()
        if existing:
            raise HTTPException(
                status_code=409,
                detail="Pattern already exists in blocklist",
            )

        from agentkit.server.auth.models import _now_iso

        await db.execute(
            """INSERT INTO terminal_blocklist
               (id, command_pattern, reason, created_at, created_by)
               VALUES (?, ?, ?, ?, ?)""",
            (
                entry_id,
                payload.command_pattern,
                payload.reason,
                _now_iso(),
                user.get("user_id") if user else None,
            ),
        )
        await db.commit()

        db.row_factory = aiosqlite.Row
        cursor = await db.execute(
            "SELECT id, command_pattern, reason, created_at "
            "FROM terminal_blocklist WHERE id = ?",
            (entry_id,),
        )
        row = await cursor.fetchone()

    return BlocklistEntry(
        id=row["id"],
        command_pattern=row["command_pattern"],
        reason=row["reason"],
        created_at=row["created_at"],
    )


@router.delete(
    "/blocklist/{entry_id}",
    status_code=204,
    dependencies=[Depends(require_permission(Permission.TERMINAL_WHITELIST_MANAGE))],
)
async def delete_blocklist(entry_id: str, request: Request):
    """Remove a pattern from the global blocklist."""
    db_path = await _resolve_db_path(request)

    async with aiosqlite.connect(str(db_path)) as db:
        cursor = await db.execute(
            "DELETE FROM terminal_blocklist WHERE id = ?",
            (entry_id,),
        )
        await db.commit()

        if cursor.rowcount == 0:
            raise HTTPException(status_code=404, detail="Blocklist entry not found")


# ── Audit log endpoints (admin only) ──────────────────────────────────


@router.get(
    "/audit-logs",
    response_model=AuditLogListResponse,
    dependencies=[Depends(require_permission(Permission.TERMINAL_WHITELIST_MANAGE))],
)
async def list_audit_logs(
    request: Request,
    user_id: str | None = Query(None, description="Filter by user ID"),
    session_id: str | None = Query(None, description="Filter by session ID"),
    decision: str | None = Query(None, description="Filter by decision"),
    terminal_mode: str | None = Query(
        None,
        description="Filter by terminal mode (local/server)",
    ),
    limit: int = Query(100, ge=1, le=1000),
    offset: int = Query(0, ge=0),
):
    """Query terminal audit logs (admin only)."""
    db_path = await _resolve_db_path(request)

    where_clauses: list[str] = []
    params: list[Any] = []
    if user_id is not None:
        where_clauses.append("user_id = ?")
        params.append(user_id)
    if session_id is not None:
        where_clauses.append("session_id = ?")
        params.append(session_id)
    if decision is not None:
        where_clauses.append("decision = ?")
        params.append(decision)
    if terminal_mode is not None:
        where_clauses.append("terminal_mode = ?")
        params.append(terminal_mode)

    where_sql = (" WHERE " + " AND ".join(where_clauses)) if where_clauses else ""

    async with aiosqlite.connect(str(db_path)) as db:
        db.row_factory = aiosqlite.Row

        count_cursor = await db.execute(
            f"SELECT COUNT(*) FROM terminal_audit_logs{where_sql}",
            params,
        )
        total = (await count_cursor.fetchone())[0]

        cursor = await db.execute(
            f"""SELECT id, user_id, username, session_id, command, decision,
                       reason, cwd, exit_code, terminal_mode, created_at
                FROM terminal_audit_logs{where_sql}
                ORDER BY created_at DESC
                LIMIT ? OFFSET ?""",
            [*params, limit, offset],
        )
        rows = await cursor.fetchall()

    return AuditLogListResponse(
        entries=[
            AuditLogEntry(
                id=row["id"],
                user_id=row["user_id"],
                username=row["username"],
                session_id=row["session_id"],
                command=row["command"],
                decision=row["decision"],
                reason=row["reason"],
                cwd=row["cwd"],
                exit_code=row["exit_code"],
                terminal_mode=row["terminal_mode"],
                created_at=row["created_at"],
            )
            for row in rows
        ],
        total=total,
    )
