import uuid

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.api.tool.tool_types_validation import (
    check_port_exposed,
    validate_compose_yaml,
    validate_required_variables,
)
from src.auth.dependencies import _get_user, get_current_user_id, get_db_session
from src.models import ToolType
from src.models.user import User
from src.schemas.tool import (
    ToolTypeCreate,
    ToolTypeResponse,
    ToolTypeUpdate,
    ToolTypeValidateRequest,
)

router = APIRouter(prefix="/tool-types", tags=["tool-types"])


async def _require_admin(user: User) -> None:
    """Check if user has admin privileges.

    For now, all authenticated users can manage tool types.
    In production, this should check user.role or similar.
    """
    # For now, all authenticated users can manage tool types
    # In production, check user.role or similar
    pass


@router.post(
    "",
    response_model=ToolTypeResponse,
    status_code=status.HTTP_201_CREATED,
    summary="Create tool type",
    description="Create a new custom tool type with a Docker Compose template.",
)
async def create_tool_type(
    data: ToolTypeCreate,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> ToolType:
    """Create a new tool type.

    Args:
        data: Tool type creation data including name, display name, and compose template.
        user_id: ID of the authenticated user.
        session: Database session.

    Returns:
        The newly created tool type.
    """
    user = await _get_user(session, user_id)
    await _require_admin(user)

    # Check for duplicate name
    existing = await session.scalar(select(ToolType).where(ToolType.name == data.name))
    if existing:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="tool type with this name already exists",
        )

    tool_type = ToolType(
        name=data.name,
        display_name=data.display_name,
        description=data.description,
        default_port=data.default_port,
        definition_type=data.definition_type,
        manifest_id=data.manifest_id,
        compose_template=data.compose_template,
        dockerfile_template=data.dockerfile_template,
        build_context=data.build_context,
        readiness_probe=data.readiness_probe,
        startup_command=data.startup_command,
        required_variables=data.required_variables,
        category=data.category,
        interface_type=data.interface_type,
        requires_port=data.requires_port,
        created_by_id=user.id,
    )
    session.add(tool_type)
    await session.commit()
    await session.refresh(tool_type)
    return tool_type


@router.get(
    "",
    response_model=list[ToolTypeResponse],
    summary="List tool types",
    description="List all available tool types including built-in and custom ones.",
)
async def list_tool_types(
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> list[ToolType]:
    """List all tool types.

    Args:
        user_id: ID of the authenticated user.
        session: Database session.

    Returns:
        List of all tool types ordered by name.
    """
    await _get_user(session, user_id)
    result = await session.execute(select(ToolType).order_by(ToolType.name))
    return list(result.scalars().all())


@router.get(
    "/{tool_type_id}",
    response_model=ToolTypeResponse,
    summary="Get tool type",
    description="Get a specific tool type by ID.",
)
async def get_tool_type(
    tool_type_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> ToolType:
    """Get a specific tool type by ID.

    Args:
        tool_type_id: UUID of the tool type to retrieve.
        user_id: ID of the authenticated user.
        session: Database session.

    Returns:
        The requested tool type.
    """
    await _get_user(session, user_id)
    tool_type = await session.get(ToolType, tool_type_id)
    if tool_type is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="tool type not found"
        )
    return tool_type


@router.put(
    "/{tool_type_id}",
    response_model=ToolTypeResponse,
    summary="Update tool type",
    description="Update a custom tool type. Built-in tool types cannot be modified.",
)
async def update_tool_type(
    tool_type_id: uuid.UUID,
    data: ToolTypeUpdate,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> ToolType:
    """Update a tool type.

    Args:
        tool_type_id: UUID of the tool type to update.
        data: Tool type update data with optional fields.
        user_id: ID of the authenticated user.
        session: Database session.

    Returns:
        The updated tool type.
    """
    user = await _get_user(session, user_id)
    await _require_admin(user)

    tool_type = await session.get(ToolType, tool_type_id)
    if tool_type is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="tool type not found"
        )

    # Built-in tool types can now be modified

    update_data = data.model_dump(exclude_unset=True)

    # Validate port if being updated
    requires_port = update_data.get("requires_port", tool_type.requires_port)
    if "default_port" in update_data and requires_port:
        new_port = update_data["default_port"]
        if new_port <= 0 or new_port > 65535:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Port must be between 1 and 65535",
            )

        # Only validate port exposure for compose definitions
        definition_type = update_data.get("definition_type", tool_type.definition_type)
        if definition_type == "compose":
            template = update_data.get("compose_template", tool_type.compose_template)
            if template:
                try:
                    parsed = validate_compose_yaml(template)
                    if not check_port_exposed(parsed, new_port):
                        raise HTTPException(
                            status_code=status.HTTP_400_BAD_REQUEST,
                            detail=f"Port {new_port} is not exposed in the compose template",
                        )
                except ValueError as e:
                    raise HTTPException(
                        status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)
                    )

    # Validate required variables for compose definitions
    definition_type = update_data.get("definition_type", tool_type.definition_type)
    if definition_type == "compose":
        if "required_variables" in update_data and "compose_template" in update_data:
            validate_required_variables(
                update_data["compose_template"], update_data["required_variables"]
            )
        elif "required_variables" in update_data:
            template = tool_type.compose_template
            if template:
                validate_required_variables(template, update_data["required_variables"])

    # When switching to manifest, clear legacy templates
    if definition_type == "manifest":
        if "manifest_id" in update_data:
            tool_type.manifest_id = update_data["manifest_id"]
        tool_type.compose_template = None
        tool_type.dockerfile_template = None

    for field, value in update_data.items():
        setattr(tool_type, field, value)

    await session.commit()
    await session.refresh(tool_type)
    return tool_type


@router.post(
    "/validate",
    summary="Validate tool type template",
    description="Validate a compose template or dockerfile syntax before creating a tool type.",
)
async def validate_tool_type_template(
    data: ToolTypeValidateRequest,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Validate a tool type template syntax.

    Args:
        data: Validation request with definition type and template.
        user_id: ID of the authenticated user.
        session: Database session.

    Returns:
        Validation result with success status and any errors.
    """
    await _get_user(session, user_id)

    errors = []

    if data.definition_type == "compose":
        if not data.compose_template:
            errors.append("Compose template is required")
        else:
            try:
                validate_compose_yaml(data.compose_template)
            except ValueError as e:
                errors.append(str(e))

    elif data.definition_type == "dockerfile":
        if not data.dockerfile_template:
            errors.append("Dockerfile template is required")
        elif not data.dockerfile_template.strip().startswith("FROM"):
            errors.append("Dockerfile must start with a FROM instruction")

    elif data.definition_type == "manifest":
        pass  # Manifest validation is handled separately

    else:
        errors.append("definition_type must be 'compose', 'dockerfile', or 'manifest'")

    return {
        "valid": len(errors) == 0,
        "errors": errors,
    }


@router.get(
    "/{tool_type_id}/validate",
    summary="Validate tool type",
    description="Validate the compose template or dockerfile syntax of a tool type.",
)
async def validate_tool_type(
    tool_type_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Validate a tool type's template syntax.

    Args:
        tool_type_id: UUID of the tool type to validate.
        user_id: ID of the authenticated user.
        session: Database session.

    Returns:
        Validation result with success status and any errors.
    """
    await _get_user(session, user_id)
    tool_type = await session.get(ToolType, tool_type_id)
    if tool_type is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="tool type not found"
        )

    errors = []

    if tool_type.definition_type == "compose":
        if not tool_type.compose_template:
            errors.append("Compose template is empty")
        else:
            try:
                validate_compose_yaml(tool_type.compose_template)
            except ValueError as e:
                errors.append(str(e))

    elif tool_type.definition_type == "dockerfile":
        if not tool_type.dockerfile_template:
            errors.append("Dockerfile template is empty")
        elif not tool_type.dockerfile_template.strip().startswith("FROM"):
            errors.append("Dockerfile must start with a FROM instruction")

    elif tool_type.definition_type == "manifest":
        if not tool_type.manifest_id:
            errors.append("Manifest reference is missing")

    return {
        "valid": len(errors) == 0,
        "errors": errors,
    }


@router.delete(
    "/{tool_type_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Delete tool type",
    description="Delete a custom tool type. Built-in tool types cannot be deleted.",
)
async def delete_tool_type(
    tool_type_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> None:
    """Delete a tool type.

    Args:
        tool_type_id: UUID of the tool type to delete.
        user_id: ID of the authenticated user.
        session: Database session.

    Returns:
        None with 204 status code.
    """
    user = await _get_user(session, user_id)
    await _require_admin(user)

    tool_type = await session.get(ToolType, tool_type_id)
    if tool_type is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="tool type not found"
        )

    # Built-in tool types can now be deleted

    await session.delete(tool_type)
    await session.commit()
