"""Color strip processing template routes."""

import asyncio
import json as _json
import uuid as _uuid

from fastapi import APIRouter, HTTPException, Depends, Query, WebSocket, WebSocketDisconnect

from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
    fire_entity_event,
    get_color_strip_store,
    get_cspt_store,
    get_device_store,
    get_processor_manager,
)
from wled_controller.api.schemas.filters import FilterInstanceSchema
from wled_controller.api.schemas.color_strip_processing import (
    ColorStripProcessingTemplateCreate,
    ColorStripProcessingTemplateListResponse,
    ColorStripProcessingTemplateResponse,
    ColorStripProcessingTemplateUpdate,
)
from wled_controller.core.filters import FilterInstance
from wled_controller.storage.color_strip_processing_template_store import ColorStripProcessingTemplateStore
from wled_controller.storage.color_strip_store import ColorStripStore
from wled_controller.storage import DeviceStore
from wled_controller.utils import get_logger
from wled_controller.storage.base_store import EntityNotFoundError

logger = get_logger(__name__)

router = APIRouter()


def _cspt_to_response(t) -> ColorStripProcessingTemplateResponse:
    """Convert a ColorStripProcessingTemplate to its API response."""
    return ColorStripProcessingTemplateResponse(
        id=t.id,
        name=t.name,
        filters=[FilterInstanceSchema(filter_id=f.filter_id, options=f.options) for f in t.filters],
        created_at=t.created_at,
        updated_at=t.updated_at,
        description=t.description,
        tags=t.tags,
    )


@router.get("/api/v1/color-strip-processing-templates", response_model=ColorStripProcessingTemplateListResponse, tags=["Color Strip Processing"])
async def list_cspt(
    _auth: AuthRequired,
    store: ColorStripProcessingTemplateStore = Depends(get_cspt_store),
):
    """List all color strip processing templates."""
    try:
        templates = store.get_all_templates()
        responses = [_cspt_to_response(t) for t in templates]
        return ColorStripProcessingTemplateListResponse(templates=responses, count=len(responses))
    except Exception as e:
        logger.error("Failed to list color strip processing templates: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.post("/api/v1/color-strip-processing-templates", response_model=ColorStripProcessingTemplateResponse, tags=["Color Strip Processing"], status_code=201)
async def create_cspt(
    data: ColorStripProcessingTemplateCreate,
    _auth: AuthRequired,
    store: ColorStripProcessingTemplateStore = Depends(get_cspt_store),
):
    """Create a new color strip processing template."""
    try:
        filters = [FilterInstance(f.filter_id, f.options) for f in data.filters]
        template = store.create_template(
            name=data.name,
            filters=filters,
            description=data.description,
            tags=data.tags,
        )
        fire_entity_event("cspt", "created", template.id)
        return _cspt_to_response(template)
    except EntityNotFoundError as e:
        raise HTTPException(status_code=404, detail=str(e))

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error("Failed to create color strip processing template: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.get("/api/v1/color-strip-processing-templates/{template_id}", response_model=ColorStripProcessingTemplateResponse, tags=["Color Strip Processing"])
async def get_cspt(
    template_id: str,
    _auth: AuthRequired,
    store: ColorStripProcessingTemplateStore = Depends(get_cspt_store),
):
    """Get color strip processing template by ID."""
    try:
        template = store.get_template(template_id)
        return _cspt_to_response(template)
    except ValueError:
        raise HTTPException(status_code=404, detail=f"Color strip processing template {template_id} not found")


@router.put("/api/v1/color-strip-processing-templates/{template_id}", response_model=ColorStripProcessingTemplateResponse, tags=["Color Strip Processing"])
async def update_cspt(
    template_id: str,
    data: ColorStripProcessingTemplateUpdate,
    _auth: AuthRequired,
    store: ColorStripProcessingTemplateStore = Depends(get_cspt_store),
):
    """Update a color strip processing template."""
    try:
        filters = [FilterInstance(f.filter_id, f.options) for f in data.filters] if data.filters is not None else None
        template = store.update_template(
            template_id=template_id,
            name=data.name,
            filters=filters,
            description=data.description,
            tags=data.tags,
        )
        fire_entity_event("cspt", "updated", template_id)
        return _cspt_to_response(template)
    except EntityNotFoundError as e:
        raise HTTPException(status_code=404, detail=str(e))

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error("Failed to update color strip processing template: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.delete("/api/v1/color-strip-processing-templates/{template_id}", status_code=204, tags=["Color Strip Processing"])
async def delete_cspt(
    template_id: str,
    _auth: AuthRequired,
    store: ColorStripProcessingTemplateStore = Depends(get_cspt_store),
    device_store: DeviceStore = Depends(get_device_store),
    css_store: ColorStripStore = Depends(get_color_strip_store),
):
    """Delete a color strip processing template."""
    try:
        refs = store.get_references(template_id, device_store=device_store, css_store=css_store)
        if refs:
            names = ", ".join(refs)
            raise HTTPException(
                status_code=409,
                detail=f"Cannot delete: template is referenced by: {names}. "
                       "Please reassign before deleting.",
            )
        store.delete_template(template_id)
        fire_entity_event("cspt", "deleted", template_id)
    except HTTPException:
        raise
    except EntityNotFoundError as e:
        raise HTTPException(status_code=404, detail=str(e))

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error("Failed to delete color strip processing template: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


# ── Test / Preview WebSocket ──────────────────────────────────────────

@router.websocket("/api/v1/color-strip-processing-templates/{template_id}/test/ws")
async def test_cspt_ws(
    websocket: WebSocket,
    template_id: str,
    token: str = Query(""),
    input_source_id: str = Query(""),
    led_count: int = Query(100),
    fps: int = Query(20),
):
    """WebSocket for real-time CSPT preview.

    Takes an input CSS source, applies the CSPT filter chain, and streams
    the processed RGB frames. Auth via ``?token=<api_key>``.
    """
    from wled_controller.api.auth import verify_ws_token
    from wled_controller.core.filters import FilterRegistry
    from wled_controller.core.processing.processor_manager import ProcessorManager

    if not verify_ws_token(token):
        await websocket.close(code=4001, reason="Unauthorized")
        return

    # Validate template exists
    cspt_store = get_cspt_store()
    try:
        template = cspt_store.get_template(template_id)
    except (ValueError, RuntimeError) as e:
        await websocket.close(code=4004, reason=str(e))
        return

    if not input_source_id:
        await websocket.close(code=4003, reason="input_source_id is required")
        return

    # Validate input source exists
    css_store = get_color_strip_store()
    try:
        input_source = css_store.get_source(input_source_id)
    except (ValueError, RuntimeError) as e:
        await websocket.close(code=4004, reason=str(e))
        return

    # Resolve filter chain
    try:
        resolved = cspt_store.resolve_filter_instances(template.filters)
        filters = [FilterRegistry.create_instance(fi.filter_id, fi.options) for fi in resolved]
    except Exception as e:
        logger.error(f"CSPT test: failed to resolve filters for {template_id}: {e}")
        await websocket.close(code=4003, reason=str(e))
        return

    # Acquire input stream
    manager: ProcessorManager = get_processor_manager()
    csm = manager.color_strip_stream_manager
    consumer_id = f"__cspt_test_{_uuid.uuid4().hex[:8]}__"
    try:
        stream = csm.acquire(input_source_id, consumer_id)
    except Exception as e:
        logger.error(f"CSPT test: failed to acquire input stream for {input_source_id}: {e}")
        await websocket.close(code=4003, reason=str(e))
        return

    # Configure LED count for auto-sizing streams
    if hasattr(stream, "configure"):
        stream.configure(max(1, led_count))

    fps = max(1, min(60, fps))
    frame_interval = 1.0 / fps

    await websocket.accept()
    logger.info(f"CSPT test WS connected: template={template_id}, input={input_source_id}")

    try:
        # Send metadata
        meta = {
            "type": "meta",
            "source_type": input_source.source_type,
            "source_name": input_source.name,
            "template_name": template.name,
            "led_count": stream.led_count,
            "filter_count": len(filters),
        }
        await websocket.send_text(_json.dumps(meta))

        # Stream processed frames
        while True:
            colors = stream.get_latest_colors()
            if colors is not None:
                # Apply CSPT filters
                for flt in filters:
                    try:
                        result = flt.process_strip(colors)
                        if result is not None:
                            colors = result
                    except Exception as e:
                        logger.debug("Strip filter processing error: %s", e)
                        pass
                await websocket.send_bytes(colors.tobytes())
            await asyncio.sleep(frame_interval)

    except WebSocketDisconnect:
        logger.debug("Color strip processing test WebSocket disconnected")
        pass
    except Exception as e:
        logger.error(f"CSPT test WS error: {e}")
    finally:
        csm.release(input_source_id, consumer_id)
        logger.info(f"CSPT test WS disconnected: template={template_id}")
