import os
import base64
from datetime import datetime
from typing import Optional, List, Dict
from uuid import UUID
from fastapi import APIRouter, HTTPException, File, UploadFile, Form, Depends
from pydantic import BaseModel
from openai import OpenAI
from openai import APIError
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, func
from utils.asset_directory_utils import get_images_directory
from services.database import get_async_session
from models.sql.presentation_layout_code import PresentationLayoutCodeModel
from services.llm_service import UnifiedLLMService
from .prompts import (
    GENERATE_HTML_SYSTEM_PROMPT,
    HTML_TO_REACT_SYSTEM_PROMPT,
    HTML_EDIT_SYSTEM_PROMPT,
)
from utils.oxml_geometry import extract_geometry_from_oxml, format_geometry_for_llm
from utils.tsx_validator import validate_tsx_syntax
from models.sql.template import TemplateModel


# Create separate routers for each functionality
SLIDE_TO_HTML_ROUTER = APIRouter(prefix="/slide-to-html", tags=["slide-to-html"])
HTML_TO_REACT_ROUTER = APIRouter(prefix="/html-to-react", tags=["html-to-react"])
HTML_EDIT_ROUTER = APIRouter(prefix="/html-edit", tags=["html-edit"])
LAYOUT_MANAGEMENT_ROUTER = APIRouter(
    prefix="/template-management", tags=["template-management"]
)


# Request/Response models for slide-to-html endpoint
class SlideToHtmlRequest(BaseModel):
    image: str  # Partial path to image file (e.g., "/app_data/images/uuid/slide_1.png")
    xml: str  # OXML content as text
    fonts: Optional[List[str]] = None  # Optional normalized root fonts for this slide


class SlideToHtmlResponse(BaseModel):
    success: bool
    html: str


# Request/Response models for html-edit endpoint
class HtmlEditResponse(BaseModel):
    success: bool
    edited_html: str
    message: Optional[str] = None


# Request/Response models for html-to-react endpoint
class HtmlToReactRequest(BaseModel):
    html: str  # HTML content to convert to React component
    image: Optional[str] = None  # Optional image path to provide visual context


class HtmlToReactResponse(BaseModel):
    success: bool
    react_component: str
    message: Optional[str] = None


# Request/Response models for layout management endpoints
class LayoutData(BaseModel):
    presentation: UUID  # UUID of the presentation
    layout_id: str  # Unique identifier for the layout
    layout_name: str  # Display name of the layout
    layout_code: str  # TSX/React component code for the layout
    fonts: Optional[List[str]] = None  # Optional list of font links
    db_id: Optional[int] = None          # new: DB primary key for toggle
    is_enabled: Optional[bool] = True    # new
    thumbnail_path: Optional[str] = None # new


class SaveLayoutsRequest(BaseModel):
    layouts: list[LayoutData]


class SaveLayoutsResponse(BaseModel):
    success: bool
    saved_count: int
    message: Optional[str] = None


class GetLayoutsResponse(BaseModel):
    success: bool
    layouts: list[LayoutData]
    message: Optional[str] = None
    template: Optional[dict] = None
    fonts: Optional[List[str]] = None


class PresentationSummary(BaseModel):
    presentation_id: UUID
    layout_count: int
    last_updated_at: Optional[datetime] = None
    template: Optional[dict] = None


class GetPresentationSummaryResponse(BaseModel):
    success: bool
    presentations: List[PresentationSummary]
    total_presentations: int
    total_layouts: int
    message: Optional[str] = None


class ErrorResponse(BaseModel):
    success: bool = False
    detail: str
    error_code: Optional[str] = None


class TemplateCreateRequest(BaseModel):
    id: UUID
    name: str
    description: Optional[str] = None


class TemplateCreateResponse(BaseModel):
    success: bool
    template: dict
    message: Optional[str] = None


class TemplateInfo(BaseModel):
    id: UUID
    name: Optional[str] = None
    description: Optional[str] = None
    created_at: Optional[datetime] = None


async def generate_html_from_slide(
    base64_image: str,
    media_type: str,
    xml_content: str,
    api_key: str,  # Kept for signature compatibility, though LLM service uses env by default
    fonts: Optional[List[str]] = None,
) -> str:
    """Generate HTML content from slide image and XML using active LLM Service."""
    print(f"Generating HTML from slide image and XML using Unified LLM Service...")
    try:
        # Extract geometric coordinates from XML to JSON
        geometric_elements = extract_geometry_from_oxml(xml_content)
        geometry_json = format_geometry_for_llm(geometric_elements)
        
        fonts_text = (
            f"\nFONTS (Normalized root families used in this slide, use where it is required): {', '.join(fonts)}"
            if fonts else ""
        )
        user_text = f"Slide Design Extracted Elements (JSON): \n{geometry_json}\n\n{fonts_text}"
        
        html_content = await UnifiedLLMService.generate_vision_completion(
            system_prompt=GENERATE_HTML_SYSTEM_PROMPT,
            user_text=user_text,
            image_base64=base64_image,
            media_type=media_type
        )
        
        return UnifiedLLMService.clean_llm_code_output(html_content, ["html"])

    except Exception as e:
        print(f"Error occurred during HTML generation: {str(e)}")
        raise HTTPException(
            status_code=500, detail=f"LLM API error during HTML generation: {str(e)}"
        )


async def generate_react_component_from_html(
    html_content: str,
    api_key: str, # Kept for signature compatibility
    image_base64: Optional[str] = None,
    media_type: Optional[str] = None,
) -> str:
    """Convert HTML content to TSX React component using Active LLM Service."""
    try:
        user_text = f"HTML INPUT:\n{html_content}"
        
        react_content = await UnifiedLLMService.generate_vision_completion(
            system_prompt=HTML_TO_REACT_SYSTEM_PROMPT,
            user_text=user_text,
            image_base64=image_base64 or "",
            media_type=media_type or "image/png"
        )
        
        react_content = UnifiedLLMService.clean_llm_code_output(react_content, ["tsx", "typescript", "javascript"])

        # Filter out lines that start with import or export
        filtered_lines = []
        for line in react_content.split("\n"):
            stripped_line = line.strip()
            if not (stripped_line.startswith("import ") or stripped_line.startswith("export ")):
                filtered_lines.append(line)

        return "\n".join(filtered_lines)
    except Exception as e:
        print(f"Error occurred: {str(e)}")
        raise HTTPException(
            status_code=500, detail=f"LLM API error during React generation: {str(e)}"
        )


async def edit_html_with_images(
    current_ui_base64: str,
    sketch_base64: Optional[str],
    media_type: str,
    html_content: str,
    prompt: str,
    api_key: str, # Kept for signature compatibility
) -> str:
    """Edit HTML content based on images and text prompt using Active LLM Service."""
    try:
        user_text = f"CURRENT HTML TO EDIT:\n{html_content}\n\nTEXT PROMPT FOR CHANGES:\n{prompt}"
        
        # If there's a sketch image, we'd ideally pass both, but UnifiedLLMService 
        # API currently takes one primary image. We'll pass the sketch if it exists, 
        # otherwise the current UI, to keep the abstraction clean. 
        # (Future improvement: update UnifiedLLMService to accept multiple images)
        primary_image = sketch_base64 if sketch_base64 else current_ui_base64
        
        edited_html = await UnifiedLLMService.generate_vision_completion(
            system_prompt=HTML_EDIT_SYSTEM_PROMPT,
            user_text=user_text,
            image_base64=primary_image,
            media_type=media_type
        )
        
        return UnifiedLLMService.clean_llm_code_output(edited_html, ["html"])
        
    except Exception as e:
        print(f"Error occurred during HTML editing: {str(e)}")
        raise HTTPException(
            status_code=500, detail=f"LLM API error during HTML editing: {str(e)}"
        )


# ENDPOINT 1: Slide to HTML conversion
@SLIDE_TO_HTML_ROUTER.post("/", response_model=SlideToHtmlResponse)
async def convert_slide_to_html(request: SlideToHtmlRequest):
    """
    Convert a slide image and its OXML data to HTML using Anthropic Claude API.

    Args:
        request: JSON request containing image path and XML content

    Returns:
        SlideToHtmlResponse with generated HTML
    """
    try:
        # Get OpenAI API key from environment
        api_key = os.getenv("OPENAI_API_KEY")
        if not api_key:
            raise HTTPException(
                status_code=500, detail="OPENAI_API_KEY environment variable not set"
            )

        # Resolve image path to actual file system path
        image_path = request.image

        # Handle different path formats
        if image_path.startswith("/app_data/images/"):
            # Remove the /app_data/images/ prefix and join with actual images directory
            relative_path = image_path[len("/app_data/images/") :]
            actual_image_path = os.path.join(get_images_directory(), relative_path)
        elif image_path.startswith("/static/"):
            # Handle static files
            relative_path = image_path[len("/static/") :]
            actual_image_path = os.path.join("static", relative_path)
        else:
            # Assume it's already a full path or relative to images directory
            if os.path.isabs(image_path):
                actual_image_path = image_path
            else:
                actual_image_path = os.path.join(get_images_directory(), image_path)

        # Check if image file exists
        if not os.path.exists(actual_image_path):
            raise HTTPException(
                status_code=404, detail=f"Image file not found: {image_path}"
            )

        # Read and encode image to base64
        with open(actual_image_path, "rb") as image_file:
            image_content = image_file.read()
        base64_image = base64.b64encode(image_content).decode("utf-8")

        # Determine media type from file extension
        file_extension = os.path.splitext(actual_image_path)[1].lower()
        media_type_map = {
            ".png": "image/png",
            ".jpg": "image/jpeg",
            ".jpeg": "image/jpeg",
            ".gif": "image/gif",
            ".webp": "image/webp",
        }
        media_type = media_type_map.get(file_extension, "image/png")

        # Generate HTML using the extracted function
        html_content = await generate_html_from_slide(
            base64_image=base64_image,
            media_type=media_type,
            xml_content=request.xml,
            api_key=api_key,
            fonts=request.fonts,
        )

        html_content = html_content.replace("```html", "").replace("```", "")

        return SlideToHtmlResponse(success=True, html=html_content)

    except HTTPException:
        # Re-raise HTTP exceptions as-is
        raise
    except Exception as e:
        # Log the full error for debugging
        print(f"Unexpected error during slide to HTML processing: {str(e)}")
        raise HTTPException(
            status_code=500, detail=f"Error processing slide to HTML: {str(e)}"
        )


# ENDPOINT 2: HTML to React component conversion
@HTML_TO_REACT_ROUTER.post("/", response_model=HtmlToReactResponse)
async def convert_html_to_react(request: HtmlToReactRequest):
    """
    Convert HTML content to TSX React component using Anthropic Claude API.

    Args:
        request: JSON request containing HTML content

    Returns:
        HtmlToReactResponse with generated React component
    """
    try:
        # Get OpenAI API key from environment
        api_key = os.getenv("OPENAI_API_KEY")
        if not api_key:
            raise HTTPException(
                status_code=500, detail="OPENAI_API_KEY environment variable not set"
            )

        # Validate HTML content
        if not request.html or not request.html.strip():
            raise HTTPException(status_code=400, detail="HTML content cannot be empty")

        # Optionally resolve image and encode to base64
        image_b64 = None
        media_type = None
        if request.image:
            image_path = request.image
            if image_path.startswith("/app_data/images/"):
                relative_path = image_path[len("/app_data/images/") :]
                actual_image_path = os.path.join(get_images_directory(), relative_path)
            elif image_path.startswith("/static/"):
                relative_path = image_path[len("/static/") :]
                actual_image_path = os.path.join("static", relative_path)
            else:
                actual_image_path = (
                    image_path
                    if os.path.isabs(image_path)
                    else os.path.join(get_images_directory(), image_path)
                )
            if os.path.exists(actual_image_path):
                with open(actual_image_path, "rb") as f:
                    image_b64 = base64.b64encode(f.read()).decode("utf-8")
                ext = os.path.splitext(actual_image_path)[1].lower()
                media_type = {
                    ".png": "image/png",
                    ".jpg": "image/jpeg",
                    ".jpeg": "image/jpeg",
                    ".gif": "image/gif",
                    ".webp": "image/webp",
                }.get(ext, "image/png")

        # Convert HTML to React component
        react_component = await generate_react_component_from_html(
            html_content=request.html,
            api_key=api_key,
            image_base64=image_b64,
            media_type=media_type,
        )

        react_component = react_component.replace("```tsx", "").replace("```", "")
        
        # Verify basic TSX syntax to catch massive hallucinations
        if not validate_tsx_syntax(react_component):
            raise HTTPException(
                status_code=500, detail="Generated React component failed syntax validation (possible hallucination or truncation)"
            )

        return HtmlToReactResponse(
            success=True,
            react_component=react_component,
            message="React component generated successfully",
        )

    except HTTPException:
        # Re-raise HTTP exceptions as-is
        raise
    except Exception as e:
        # Log the full error for debugging
        print(f"Unexpected error during HTML to React processing: {str(e)}")
        raise HTTPException(
            status_code=500, detail=f"Error processing HTML to React: {str(e)}"
        )


# ENDPOINT 3: HTML editing with images
@HTML_EDIT_ROUTER.post("/", response_model=HtmlEditResponse)
async def edit_html_with_images_endpoint(
    current_ui_image: UploadFile = File(..., description="Current UI image file"),
    sketch_image: Optional[UploadFile] = File(
        None, description="Sketch/indication image file (optional)"
    ),
    html: str = Form(..., description="Current HTML content to edit"),
    prompt: str = Form(..., description="Text prompt describing the changes"),
):
    """
    Edit HTML content based on one or two uploaded images and a text prompt using Anthropic Claude API.

    Args:
        current_ui_image: Uploaded current UI image file
        sketch_image: Uploaded sketch/indication image file (optional)
        html: Current HTML content to edit (form data)
        prompt: Text prompt describing the changes (form data)

    Returns:
        HtmlEditResponse with edited HTML
    """
    try:
        # Get OpenAI API key from environment
        api_key = os.getenv("OPENAI_API_KEY")
        if not api_key:
            raise HTTPException(
                status_code=500, detail="OPENAI_API_KEY environment variable not set"
            )

        # Validate inputs
        if not html or not html.strip():
            raise HTTPException(status_code=400, detail="HTML content cannot be empty")

        if not prompt or not prompt.strip():
            raise HTTPException(status_code=400, detail="Text prompt cannot be empty")

        # Validate current UI image file
        if (
            not current_ui_image.content_type
            or not current_ui_image.content_type.startswith("image/")
        ):
            raise HTTPException(
                status_code=400, detail="Current UI file must be an image"
            )

        # Validate sketch image file only if provided
        if sketch_image and (
            not sketch_image.content_type
            or not sketch_image.content_type.startswith("image/")
        ):
            raise HTTPException(status_code=400, detail="Sketch file must be an image")

        # Read and encode current UI image to base64
        current_ui_content = await current_ui_image.read()
        current_ui_base64 = base64.b64encode(current_ui_content).decode("utf-8")

        # Read and encode sketch image to base64 only if provided
        sketch_base64 = None
        if sketch_image:
            sketch_content = await sketch_image.read()
            sketch_base64 = base64.b64encode(sketch_content).decode("utf-8")

        # Use the content type from the uploaded files
        media_type = current_ui_image.content_type

        # Edit HTML using the function
        edited_html = await edit_html_with_images(
            current_ui_base64=current_ui_base64,
            sketch_base64=sketch_base64,
            media_type=media_type,
            html_content=html,
            prompt=prompt,
            api_key=api_key,
        )

        edited_html = edited_html.replace("```html", "").replace("```", "")

        return HtmlEditResponse(
            success=True, edited_html=edited_html, message="HTML edited successfully"
        )

    except HTTPException:
        # Re-raise HTTP exceptions as-is
        raise
    except Exception as e:
        # Log the full error for debugging
        print(f"Unexpected error during HTML editing: {str(e)}")
        raise HTTPException(
            status_code=500, detail=f"Error processing HTML editing: {str(e)}"
        )


# ENDPOINT 4: Save layouts for a presentation
@LAYOUT_MANAGEMENT_ROUTER.post(
    "/save-templates",
    response_model=SaveLayoutsResponse,
    responses={
        400: {"model": ErrorResponse, "description": "Validation error"},
        500: {"model": ErrorResponse, "description": "Internal server error"},
    },
)
async def save_layouts(
    request: SaveLayoutsRequest, session: AsyncSession = Depends(get_async_session)
):
    """
    Save multiple layouts for presentations.

    Args:
        request: JSON request containing array of layout data
        session: Database session

    Returns:
        SaveLayoutsResponse with success status and count of saved layouts

    Raises:
        HTTPException: 400 for validation errors, 500 for server errors
    """
    try:
        # Validate request data
        if not request.layouts:
            raise HTTPException(status_code=400, detail="Layouts array cannot be empty")

        if len(request.layouts) > 50:  # Reasonable limit
            raise HTTPException(
                status_code=400, detail="Cannot save more than 50 layouts at once"
            )

        saved_count = 0

        for i, layout_data in enumerate(request.layouts):
            # Validate individual layout data
            if (
                not layout_data.presentation
                or not str(layout_data.presentation).strip()
            ):
                raise HTTPException(
                    status_code=400,
                    detail=f"Layout {i+1}: presentation_id cannot be empty",
                )

            if not layout_data.layout_id or not layout_data.layout_id.strip():
                raise HTTPException(
                    status_code=400, detail=f"Layout {i+1}: layout_id cannot be empty"
                )

            if not layout_data.layout_name or not layout_data.layout_name.strip():
                raise HTTPException(
                    status_code=400, detail=f"Layout {i+1}: layout_name cannot be empty"
                )

            if not layout_data.layout_code or not layout_data.layout_code.strip():
                raise HTTPException(
                    status_code=400, detail=f"Layout {i+1}: layout_code cannot be empty"
                )

            # Check if layout already exists for this presentation and layout_id
            stmt = select(PresentationLayoutCodeModel).where(
                PresentationLayoutCodeModel.presentation == layout_data.presentation,
                PresentationLayoutCodeModel.layout_id == layout_data.layout_id,
            )
            result = await session.execute(stmt)
            existing_layout = result.scalar_one_or_none()

            if existing_layout:
                # Update existing layout
                existing_layout.layout_name = layout_data.layout_name
                existing_layout.layout_code = layout_data.layout_code
                existing_layout.fonts = layout_data.fonts
                existing_layout.updated_at = datetime.now()
            else:
                # Create new layout
                new_layout = PresentationLayoutCodeModel(
                    presentation=layout_data.presentation,
                    layout_id=layout_data.layout_id,
                    layout_name=layout_data.layout_name,
                    layout_code=layout_data.layout_code,
                    fonts=layout_data.fonts,
                )
                session.add(new_layout)

            saved_count += 1

        await session.commit()

        return SaveLayoutsResponse(
            success=True,
            saved_count=saved_count,
            message=f"Successfully saved {saved_count} layout(s)",
        )

    except HTTPException:
        # Re-raise HTTP exceptions as-is
        await session.rollback()
        raise
    except Exception as e:
        await session.rollback()
        print(f"Unexpected error saving layouts: {str(e)}")
        raise HTTPException(
            status_code=500,
            detail=f"Internal server error while saving layouts: {str(e)}",
        )


# ENDPOINT 5: Get layouts for a presentation
@LAYOUT_MANAGEMENT_ROUTER.get(
    "/get-templates/{presentation}",
    response_model=GetLayoutsResponse,
    responses={
        400: {"model": ErrorResponse, "description": "Invalid presentation ID"},
        404: {
            "model": ErrorResponse,
            "description": "No layouts found for presentation",
        },
        500: {"model": ErrorResponse, "description": "Internal server error"},
    },
)
async def get_layouts(
    presentation: UUID, session: AsyncSession = Depends(get_async_session)
):
    """
    Retrieve all layouts for a specific presentation.

    Args:
        presentation: UUID of the presentation
        session: Database session

    Returns:
        GetLayoutsResponse with layouts data

    Raises:
        HTTPException: 404 if no layouts found, 400 for invalid UUID, 500 for server errors
    """
    try:
        # Validate presentation_id format (basic UUID check)
        if not presentation or len(str(presentation).strip()) == 0:
            raise HTTPException(
                status_code=400, detail="Presentation ID cannot be empty"
            )

        # Query layouts for the given presentation_id
        stmt = select(PresentationLayoutCodeModel).where(
            PresentationLayoutCodeModel.presentation == presentation
        )
        result = await session.execute(stmt)
        layouts_db = result.scalars().all()

        # Check if any layouts were found
        if not layouts_db:
            raise HTTPException(
                status_code=404,
                detail=f"No layouts found for presentation ID: {presentation}",
            )

        # Convert to response format
        layouts = [
            LayoutData(
                presentation=layout.presentation,
                layout_id=layout.layout_id,
                layout_name=layout.layout_name,
                layout_code=layout.layout_code,
                fonts=layout.fonts,
                db_id=layout.id,
                is_enabled=layout.is_enabled if layout.is_enabled is not None else True,
                thumbnail_path=layout.thumbnail_path,
            )
            for layout in layouts_db
        ]

        # Aggregate unique fonts across all layouts
        aggregated_fonts: set[str] = set()
        for layout in layouts_db:
            if layout.fonts:
                aggregated_fonts.update([f for f in layout.fonts if isinstance(f, str)])
        fonts_list = sorted(list(aggregated_fonts)) if aggregated_fonts else None

        # Fetch template meta
        template_meta = await session.get(TemplateModel, presentation)
        template = None
        if template_meta:
            template = {
                "id": template_meta.id,
                "name": template_meta.name,
                "description": template_meta.description,
                "created_at": template_meta.created_at,
            }

        return GetLayoutsResponse(
            success=True,
            layouts=layouts,
            message=f"Retrieved {len(layouts)} layout(s) for presentation {presentation}",
            template=template,
            fonts=fonts_list,
        )

    except HTTPException:
        # Re-raise HTTP exceptions as-is
        raise
    except Exception as e:
        print(f"Error retrieving layouts for presentation {presentation}: {str(e)}")
        raise HTTPException(
            status_code=500,
            detail=f"Internal server error while retrieving layouts: {str(e)}",
        )


# ENDPOINT: Get all presentations with layout counts
@LAYOUT_MANAGEMENT_ROUTER.get(
    "/summary",
    response_model=GetPresentationSummaryResponse,
    summary="Get all presentations with layout counts",
    description="Retrieve a summary of all presentations and the number of layouts in each",
    responses={
        200: {
            "model": GetPresentationSummaryResponse,
            "description": "Presentations summary retrieved successfully",
        },
        500: {"model": ErrorResponse, "description": "Internal server error"},
    },
)
async def get_presentations_summary(
    session: AsyncSession = Depends(get_async_session),
):
    """
    Get summary of all presentations with their layout counts.
    """
    try:
        # Query to get presentation_id, count of layouts, and MAX(updated_at)
        stmt = select(
            PresentationLayoutCodeModel.presentation,
            func.count(PresentationLayoutCodeModel.id).label("layout_count"),
            func.max(PresentationLayoutCodeModel.updated_at).label("last_updated_at"),
        ).group_by(PresentationLayoutCodeModel.presentation)

        result = await session.execute(stmt)
        presentation_data = result.all()

        # Convert to response format with template info if available
        presentations = []
        for row in presentation_data:
            template_meta = await session.get(TemplateModel, row.presentation)
            template = None
            if template_meta:
                template = {
                    "id": template_meta.id,
                    "name": template_meta.name,
                    "description": template_meta.description,
                    "created_at": template_meta.created_at,
                }
            presentations.append(
                PresentationSummary(
                    presentation_id=row.presentation,
                    layout_count=row.layout_count,
                    last_updated_at=row.last_updated_at,
                    template=template,
                )
            )

        # Calculate totals
        total_presentations = len(presentations)
        total_layouts = sum(p.layout_count for p in presentations)

        return GetPresentationSummaryResponse(
            success=True,
            presentations=presentations,
            total_presentations=total_presentations,
            total_layouts=total_layouts,
            message=f"Retrieved {total_presentations} presentation(s) with {total_layouts} total layout(s)",
        )

    except Exception as e:
        print(f"Error retrieving presentations summary: {str(e)}")
        raise HTTPException(
            status_code=500,
            detail=f"Internal server error while retrieving presentations summary: {str(e)}",
        )


@LAYOUT_MANAGEMENT_ROUTER.post(
    "/templates",
    response_model=TemplateCreateResponse,
    responses={
        400: {"model": ErrorResponse, "description": "Validation error"},
        500: {"model": ErrorResponse, "description": "Internal server error"},
    },
)
async def create_template(
    request: TemplateCreateRequest,
    session: AsyncSession = Depends(get_async_session),
):
    try:
        if not request.id or not request.name:
            raise HTTPException(status_code=400, detail="id and name are required")

        # Upsert template by id
        existing = await session.get(TemplateModel, request.id)
        if existing:
            existing.name = request.name
            existing.description = request.description
        else:
            session.add(
                TemplateModel(
                    id=request.id, name=request.name, description=request.description
                )
            )
        await session.commit()

        # Read back
        template = await session.get(TemplateModel, request.id)
        return TemplateCreateResponse(
            success=True,
            template={
                "id": template.id,
                "name": template.name,
                "description": template.description,
                "created_at": template.created_at,
            },
            message="Template saved",
        )
    except HTTPException:
        await session.rollback()
        raise
    except Exception as e:
        await session.rollback()
        raise HTTPException(
            status_code=500, detail=f"Failed to save template: {str(e)}"
        )


@LAYOUT_MANAGEMENT_ROUTER.delete("/delete-templates/{template_id}", status_code=204)
async def delete_template(
    template_id: UUID,
    session: AsyncSession = Depends(get_async_session),
):
    try:
        await session.execute(
            delete(TemplateModel).where(TemplateModel.id == template_id)
        )
        await session.execute(
            delete(PresentationLayoutCodeModel).where(
                PresentationLayoutCodeModel.presentation == template_id,
            )
        )
        await session.commit()
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to delete template")


class ToggleLayoutEnabledResponse(BaseModel):
    ok: bool
    is_enabled: bool


@LAYOUT_MANAGEMENT_ROUTER.patch(
    "/layouts/{layout_db_id}/toggle-enabled",
    response_model=ToggleLayoutEnabledResponse,
)
async def toggle_layout_enabled(
    layout_db_id: int,
    session: AsyncSession = Depends(get_async_session),
):
    """Toggle the is_enabled flag for a specific layout."""
    layout = await session.get(PresentationLayoutCodeModel, layout_db_id)
    if not layout:
        raise HTTPException(status_code=404, detail="Layout not found")
    layout.is_enabled = not (layout.is_enabled if layout.is_enabled is not None else True)
    await session.commit()
    return ToggleLayoutEnabledResponse(ok=True, is_enabled=layout.is_enabled)
