import uuid

from fastapi import APIRouter, HTTPException, Query, status

from tracecat import config
from tracecat.agent.folders.service import AgentFolderService
from tracecat.agent.preset.schemas import (
    AgentPresetCreate,
    AgentPresetMoveToFolder,
    AgentPresetRead,
    AgentPresetReadMinimal,
    AgentPresetUpdate,
    AgentPresetVersionDiff,
    AgentPresetVersionRead,
    AgentPresetVersionReadMinimal,
    build_agent_preset_read_minimal,
)
from tracecat.agent.preset.service import AgentPresetService
from tracecat.auth.dependencies import WorkspaceUserRouteRole
from tracecat.authz.controls import require_scope
from tracecat.db.dependencies import AsyncDBSession
from tracecat.exceptions import TracecatNotFoundError, TracecatValidationError
from tracecat.pagination import CursorPaginatedResponse, CursorPaginationParams

router = APIRouter(prefix="/agent/presets", tags=["agent-presets"])


@router.get("", response_model=list[AgentPresetReadMinimal])
@require_scope("agent:read")
async def list_agent_presets(
    *,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> list[AgentPresetReadMinimal]:
    """List all agent presets for the current workspace."""
    service = AgentPresetService(session, role=role)
    presets = await service.list_presets()
    return [build_agent_preset_read_minimal(preset) for preset in presets]


@router.post(
    "",
    response_model=AgentPresetRead,
    status_code=status.HTTP_201_CREATED,
)
@require_scope("agent:create")
async def create_agent_preset(
    *,
    params: AgentPresetCreate,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> AgentPresetRead:
    """Create a new agent preset."""
    service = AgentPresetService(session, role=role)
    try:
        preset = await service.create_preset(params)
        return await service.build_preset_read(preset)
    except TracecatValidationError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e),
        ) from e


@router.get("/{preset_id}", response_model=AgentPresetRead)
@require_scope("agent:read")
async def get_agent_preset(
    *,
    preset_id: uuid.UUID,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> AgentPresetRead:
    """Retrieve an agent preset by ID."""
    service = AgentPresetService(session, role=role)
    if not (preset := await service.get_preset(preset_id)):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset with ID '{preset_id}' not found",
        )
    return await service.build_preset_read(preset)


@router.get("/by-slug/{slug}", response_model=AgentPresetRead)
@require_scope("agent:read")
async def get_agent_preset_by_slug(
    *,
    slug: str,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> AgentPresetRead:
    """Retrieve an agent preset by slug."""
    service = AgentPresetService(session, role=role)
    if not (preset := await service.get_preset_by_slug(slug)):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset with slug '{slug}' not found",
        )
    return await service.build_preset_read(preset)


@router.patch("/{preset_id}", response_model=AgentPresetRead)
@require_scope("agent:update")
async def update_agent_preset(
    *,
    preset_id: uuid.UUID,
    params: AgentPresetUpdate,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> AgentPresetRead:
    """Update an existing agent preset."""
    service = AgentPresetService(session, role=role)
    if not (preset := await service.get_preset(preset_id)):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset {preset_id} not found",
        )
    preset = await service.update_preset(preset, params)
    return await service.build_preset_read(preset)


@router.delete("/{preset_id}", status_code=status.HTTP_204_NO_CONTENT)
@require_scope("agent:delete")
async def delete_agent_preset(
    *,
    preset_id: uuid.UUID,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> None:
    """Delete an agent preset."""
    service = AgentPresetService(session, role=role)
    if not (preset := await service.get_preset(preset_id)):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset {preset_id} not found",
        )
    await service.delete_preset(preset)


@router.get(
    "/{preset_id}/versions",
    response_model=CursorPaginatedResponse[AgentPresetVersionReadMinimal],
)
@require_scope("agent:read")
async def list_agent_preset_versions(
    *,
    preset_id: uuid.UUID,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
    limit: int = Query(
        default=config.TRACECAT__LIMIT_DEFAULT,
        ge=config.TRACECAT__LIMIT_MIN,
        le=config.TRACECAT__LIMIT_CURSOR_MAX,
    ),
    cursor: str | None = Query(default=None),
    reverse: bool = Query(default=False),
) -> CursorPaginatedResponse[AgentPresetVersionReadMinimal]:
    """List immutable versions for an agent preset."""
    service = AgentPresetService(session, role=role)
    if not await service.get_preset(preset_id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset {preset_id} not found",
        )
    versions = await service.list_versions(
        preset_id,
        CursorPaginationParams(limit=limit, cursor=cursor, reverse=reverse),
    )
    return versions


@router.get("/{preset_id}/versions/{version_id}", response_model=AgentPresetVersionRead)
@require_scope("agent:read")
async def get_agent_preset_version(
    *,
    preset_id: uuid.UUID,
    version_id: uuid.UUID,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> AgentPresetVersionRead:
    """Retrieve an immutable agent preset version."""
    service = AgentPresetService(session, role=role)
    version = await service.get_version(version_id)
    if version is None or version.preset_id != preset_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset version '{version_id}' not found",
        )
    return await service.build_version_read(version)


@router.get(
    "/{preset_id}/versions/{version_id}/compare",
    response_model=AgentPresetVersionDiff,
)
@require_scope("agent:read")
async def compare_agent_preset_versions(
    *,
    preset_id: uuid.UUID,
    version_id: uuid.UUID,
    compare_to: uuid.UUID = Query(..., description="Version ID to compare against"),
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> AgentPresetVersionDiff:
    """Compare two preset versions belonging to the same preset."""
    service = AgentPresetService(session, role=role)
    base_version = await service.get_version(version_id)
    compare_version = await service.get_version(compare_to)
    if base_version is None or base_version.preset_id != preset_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset version '{version_id}' not found",
        )
    if compare_version is None or compare_version.preset_id != preset_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset version '{compare_to}' not found",
        )
    return await service.compare_versions(base_version, compare_version)


@router.post(
    "/{preset_id}/versions/{version_id}/restore",
    response_model=AgentPresetRead,
)
@require_scope("agent:update")
async def restore_agent_preset_version(
    *,
    preset_id: uuid.UUID,
    version_id: uuid.UUID,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
) -> AgentPresetRead:
    """Restore a historical preset version as current."""
    service = AgentPresetService(session, role=role)
    preset = await service.get_preset(preset_id)
    if preset is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset {preset_id} not found",
        )
    version = await service.get_version(version_id)
    if version is None or version.preset_id != preset_id:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Agent preset version '{version_id}' not found",
        )
    try:
        restored = await service.restore_version(preset, version)
    except TracecatValidationError as exc:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(exc),
        ) from exc
    return await service.build_preset_read(restored)


@router.post(
    "/{preset_id}/move",
    status_code=status.HTTP_204_NO_CONTENT,
    tags=["agent-presets"],
)
@require_scope("agent:update")
async def move_agent_preset_to_folder(
    *,
    role: WorkspaceUserRouteRole,
    session: AsyncDBSession,
    preset_id: uuid.UUID,
    params: AgentPresetMoveToFolder,
) -> None:
    """Move an agent preset to a folder."""
    folder_service = AgentFolderService(session, role=role)
    if params.folder_path and params.folder_path != "/":
        folder = await folder_service.get_folder_by_path(params.folder_path)
        if not folder:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Folder with path '{params.folder_path}' not found",
            )
    else:
        folder = None

    try:
        await folder_service.move_preset(preset_id, folder)
    except TracecatNotFoundError as e:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
