"""Tool definition API endpoints."""

import logging
import uuid

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

from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import ToolDefinitionManifest
from src.models import ToolType
from src.services.build.manifest_compiler import (
    compile_compose,
    compile_dockerfile,
    compile_entrypoint,
    compute_image_tag,
    deep_merge,
    resolve_base,
)

logger = logging.getLogger(__name__)

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


class CreateToolDefinitionRequest(BaseModel):
    """Request body for creating a tool definition manifest."""

    model_config = {"extra": "ignore"}

    name: str = Field(description="Unique identifier (kebab-case)")
    display_name: str = Field(description="Human-readable name")
    description: str | None = Field(default=None)
    category: str = Field(default="development")
    interface_type: str = Field(default="terminal", description="web or terminal")
    base_image: str | None = Field(default=None, description="Direct base image")
    base_definition_id: str | None = Field(
        default=None, description="Reference to a base definition"
    )
    base_version: str = Field(default="latest")
    manifest: dict = Field(description="The full manifest JSON")


class UpdateToolDefinitionRequest(BaseModel):
    """Request body for updating a tool definition manifest."""

    model_config = {"extra": "ignore"}

    display_name: str | None = Field(default=None)
    description: str | None = Field(default=None)
    category: str | None = Field(default=None)
    manifest: dict | None = Field(default=None)
    base_version: str | None = Field(default=None)


@router.post(
    "",
    summary="Create tool definition",
    description="Create a new tool definition manifest.",
)
async def create_tool_definition(
    data: CreateToolDefinitionRequest,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Create a new tool definition manifest.

    Args:
        data: Manifest data.
        user_id: Authenticated user ID.
        session: Database session.

    Returns:
        Dictionary with created definition details.
    """
    # Validate base reference
    if not data.base_image and not data.base_definition_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Either base_image or base_definition_id is required",
        )

    base_def_id = None
    if data.base_definition_id:
        try:
            base_def_id = uuid.UUID(data.base_definition_id)
        except ValueError:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Invalid base_definition_id: {data.base_definition_id}",
            )

        base_def = await session.get(ToolDefinitionManifest, base_def_id)
        if not base_def:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Base definition not found: {data.base_definition_id}",
            )
        if not base_def.is_base:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Referenced definition is not a base definition",
            )

    # Check name uniqueness
    existing = await session.execute(
        select(ToolDefinitionManifest).where(ToolDefinitionManifest.name == data.name)
    )
    if existing.scalar_one_or_none():
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Tool definition '{data.name}' already exists",
        )

    definition = ToolDefinitionManifest(
        name=data.name,
        display_name=data.display_name,
        description=data.description,
        category=data.category,
        interface_type=data.interface_type,
        base_image=data.base_image,
        base_definition_id=base_def_id,
        base_version=data.base_version,
        manifest=data.manifest,
        created_by_id=user_id,
    )

    session.add(definition)
    await session.commit()
    await session.refresh(definition)

    logger.info("Created tool definition %s (%s)", definition.id, definition.name)

    return {
        "id": str(definition.id),
        "name": definition.name,
        "display_name": definition.display_name,
        "description": definition.description,
        "category": definition.category,
        "interface_type": definition.interface_type,
        "base_image": definition.base_image,
        "base_definition_id": str(definition.base_definition_id)
        if definition.base_definition_id
        else None,
        "base_version": definition.base_version,
        "manifest": definition.manifest,
        "is_base": definition.is_base,
        "created_at": definition.created_at.isoformat(),
    }


@router.get(
    "",
    summary="List tool definitions",
    description="List all tool definition manifests.",
)
async def list_tool_definitions(
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
    include_bases: bool = True,
) -> dict:
    """List all tool definition manifests.

    Args:
        user_id: Authenticated user ID.
        session: Database session.
        include_bases: Whether to include base definitions.

    Returns:
        Dictionary containing list of definitions.
    """
    query = select(ToolDefinitionManifest)
    if not include_bases:
        query = query.where(ToolDefinitionManifest.is_base.is_(False))

    result = await session.execute(
        query.order_by(ToolDefinitionManifest.created_at.desc())
    )
    definitions = result.scalars().all()

    return {
        "definitions": [
            {
                "id": str(d.id),
                "name": d.name,
                "display_name": d.display_name,
                "description": d.description,
                "category": d.category,
                "interface_type": d.interface_type,
                "is_base": d.is_base,
                "base_image": d.base_image,
                "base_definition_id": str(d.base_definition_id)
                if d.base_definition_id
                else None,
                "base_version": d.base_version,
                "version": d.version,
                "created_at": d.created_at.isoformat(),
            }
            for d in definitions
        ]
    }


@router.get(
    "/{definition_id}",
    summary="Get tool definition",
    description="Get a specific tool definition manifest.",
)
async def get_tool_definition(
    definition_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Get a specific tool definition manifest.

    Args:
        definition_id: UUID of the definition.
        user_id: Authenticated user ID.
        session: Database session.

    Returns:
        Dictionary with definition details.
    """
    definition = await session.get(ToolDefinitionManifest, definition_id)
    if not definition:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tool definition not found: {definition_id}",
        )

    return {
        "id": str(definition.id),
        "name": definition.name,
        "display_name": definition.display_name,
        "description": definition.description,
        "category": definition.category,
        "interface_type": definition.interface_type,
        "base_image": definition.base_image,
        "base_definition_id": str(definition.base_definition_id)
        if definition.base_definition_id
        else None,
        "base_version": definition.base_version,
        "manifest": definition.manifest,
        "dockerfile_cache": definition.dockerfile_cache,
        "compose_cache": definition.compose_cache,
        "version": definition.version,
        "is_base": definition.is_base,
        "created_at": definition.created_at.isoformat(),
        "updated_at": definition.updated_at.isoformat(),
    }


@router.put(
    "/{definition_id}",
    summary="Update tool definition",
    description="Update a tool definition manifest.",
)
async def update_tool_definition(
    definition_id: uuid.UUID,
    data: UpdateToolDefinitionRequest,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Update a tool definition manifest.

    Args:
        definition_id: UUID of the definition.
        data: Update data.
        user_id: Authenticated user ID.
        session: Database session.

    Returns:
        Dictionary with updated definition details.
    """
    definition = await session.get(ToolDefinitionManifest, definition_id)
    if not definition:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tool definition not found: {definition_id}",
        )

    if data.display_name is not None:
        definition.display_name = data.display_name
    if data.description is not None:
        definition.description = data.description
    if data.category is not None:
        definition.category = data.category
    if data.manifest is not None:
        definition.manifest = data.manifest
    if data.base_version is not None:
        definition.base_version = data.base_version

    await session.commit()
    await session.refresh(definition)

    logger.info("Updated tool definition %s (%s)", definition.id, definition.name)

    return {
        "id": str(definition.id),
        "name": definition.name,
        "display_name": definition.display_name,
        "manifest": definition.manifest,
        "updated_at": definition.updated_at.isoformat(),
    }


@router.delete(
    "/{definition_id}",
    summary="Delete tool definition",
    description="Delete a tool definition manifest.",
)
async def delete_tool_definition(
    definition_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Delete a tool definition manifest.

    Args:
        definition_id: UUID of the definition.
        user_id: Authenticated user ID.
        session: Database session.

    Returns:
        Dictionary with deletion status.
    """
    definition = await session.get(ToolDefinitionManifest, definition_id)
    if not definition:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tool definition not found: {definition_id}",
        )

    # Check if any tool types reference this manifest
    result = await session.execute(
        select(ToolType).where(ToolType.manifest_id == definition_id)
    )
    referencing = result.scalars().all()
    if referencing:
        tool_names = ", ".join(t.name for t in referencing)
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Cannot delete: referenced by tool types: {tool_names}",
        )

    await session.delete(definition)
    await session.commit()

    logger.info("Deleted tool definition %s (%s)", definition.id, definition.name)

    return {"status": "deleted", "id": str(definition_id)}


@router.post(
    "/{definition_id}/compile",
    summary="Compile tool definition",
    description="Compile a manifest to Dockerfile + Compose preview without building.",
)
async def compile_tool_definition(
    definition_id: uuid.UUID,
    user_id: uuid.UUID = Depends(get_current_user_id),
    session: AsyncSession = Depends(get_db_session),
) -> dict:
    """Compile a manifest to Dockerfile + Compose preview.

    Args:
        definition_id: UUID of the definition.
        user_id: Authenticated user ID.
        session: Database session.

    Returns:
        Dictionary with compiled Dockerfile, Compose, and image tag.
    """
    definition = await session.get(ToolDefinitionManifest, definition_id)
    if not definition:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tool definition not found: {definition_id}",
        )

    manifest = dict(definition.manifest)

    # Resolve base if referenced
    if definition.base_definition_id:
        base_def = await session.get(
            ToolDefinitionManifest, definition.base_definition_id
        )
        if base_def:
            base_manifest = dict(base_def.manifest)
            manifest = resolve_base(deep_merge(base_manifest, manifest))

    # Compile
    dockerfile = compile_dockerfile(manifest)
    entrypoint = compile_entrypoint(manifest)
    image_tag = compute_image_tag(definition.name, manifest)

    # Dummy compose with placeholder variables
    dummy_vars = {
        "IMAGE_TAG": image_tag,
        "INSTANCE_NAME": f"{definition.name}-preview",
        "INSTANCE_DIR": "/data/instances/preview",
        "REPO_PATH": "/data/repos/preview",
        "SSH_PATH": "/data/instances/preview/.ssh",
        "TOOL_PORT": "8080",
        "EXTRA_ENV": {},
        "EXTRA_VOLUMES": [],
    }
    compose = compile_compose(manifest, dummy_vars)

    # Update cache
    definition.dockerfile_cache = dockerfile
    definition.compose_cache = compose
    await session.commit()

    return {
        "id": str(definition.id),
        "name": definition.name,
        "dockerfile": dockerfile,
        "entrypoint": entrypoint,
        "compose": compose,
        "image_tag": image_tag,
    }
