"""CRUD endpoints for color strip sources, plus overlay/colors/notify control."""

from typing import Annotated

import numpy as np
from fastapi import APIRouter, Body, Depends, HTTPException

from ledgrab.api.auth import AuthRequired
from ledgrab.api.dependencies import (
    fire_entity_event,
    get_color_strip_store,
    get_picture_source_store,
    get_output_target_store,
    get_processor_manager,
)
from ledgrab.api.schemas.color_strip_sources import (
    ColorPushRequest,
    ColorStripSourceCreate,
    ColorStripSourceListResponse,
    ColorStripSourceResponse,
    ColorStripSourceUpdate,
    NotifyRequest,
)
from ledgrab.core.capture.screen_capture import get_available_displays
from ledgrab.core.processing.processor_manager import ProcessorManager
from ledgrab.storage.base_store import EntityNotFoundError
from ledgrab.storage.color_strip_source import (
    AdvancedPictureColorStripSource,
    ApiInputColorStripSource,
    NotificationColorStripSource,
    PictureColorStripSource,
)
from ledgrab.storage.color_strip_store import ColorStripStore
from ledgrab.storage.output_target_store import OutputTargetStore
from ledgrab.storage.picture_source_store import PictureSourceStore
from ledgrab.utils import get_logger

from ._helpers import _css_to_response, _extract_css_kwargs, _resolve_display_index

logger = get_logger(__name__)

router = APIRouter()


@router.get(
    "/api/v1/color-strip-sources",
    response_model=ColorStripSourceListResponse,
    tags=["Color Strip Sources"],
)
async def list_color_strip_sources(
    _auth: AuthRequired,
    store: ColorStripStore = Depends(get_color_strip_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """List all color strip sources."""
    sources = store.get_all_sources()
    responses = [_css_to_response(s, manager.is_css_overlay_active(s.id)) for s in sources]
    return ColorStripSourceListResponse(sources=responses, count=len(responses))


@router.post(
    "/api/v1/color-strip-sources",
    response_model=ColorStripSourceResponse,
    tags=["Color Strip Sources"],
    status_code=201,
)
async def create_color_strip_source(
    data: Annotated[ColorStripSourceCreate, Body(discriminator="source_type")],
    _auth: AuthRequired,
    store: ColorStripStore = Depends(get_color_strip_store),
):
    """Create a new color strip source."""
    try:
        kwargs = _extract_css_kwargs(data)

        # Validate nesting for composite/mapped sources before creating
        if data.source_type == "composite" and kwargs.get("layers"):
            child_ids = [ly.get("source_id", "") for ly in kwargs["layers"] if ly.get("source_id")]
            from ledgrab.storage.color_strip_store import MAX_COMPOSITE_DEPTH

            for cid in child_ids:
                depth = store.get_nesting_depth(cid)
                if 1 + depth > MAX_COMPOSITE_DEPTH:
                    raise ValueError(
                        f"Nesting depth {1 + depth} exceeds maximum of {MAX_COMPOSITE_DEPTH}"
                    )

        source = store.create_source(source_type=data.source_type, **kwargs)
        fire_entity_event("color_strip_source", "created", source.id)
        return _css_to_response(source)

    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 source: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.get(
    "/api/v1/color-strip-sources/{source_id}",
    response_model=ColorStripSourceResponse,
    tags=["Color Strip Sources"],
)
async def get_color_strip_source(
    source_id: str,
    _auth: AuthRequired,
    store: ColorStripStore = Depends(get_color_strip_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Get a color strip source by ID."""
    try:
        source = store.get_source(source_id)
        return _css_to_response(source, manager.is_css_overlay_active(source_id))
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))


@router.put(
    "/api/v1/color-strip-sources/{source_id}",
    response_model=ColorStripSourceResponse,
    tags=["Color Strip Sources"],
)
async def update_color_strip_source(
    source_id: str,
    data: Annotated[ColorStripSourceUpdate, Body(discriminator="source_type")],
    _auth: AuthRequired,
    store: ColorStripStore = Depends(get_color_strip_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Update a color strip source and hot-reload any running streams."""
    try:
        kwargs = _extract_css_kwargs(data)

        # Validate nesting for composite sources before updating
        if data.source_type == "composite" and kwargs.get("layers") is not None:
            child_ids = [ly.get("source_id", "") for ly in kwargs["layers"] if ly.get("source_id")]
            store.validate_nesting(source_id, child_ids)

        source = store.update_source(source_id=source_id, **kwargs)

        # Hot-reload running stream (no restart needed for in-place param changes)
        try:
            manager.color_strip_stream_manager.update_source(source_id, source)
        except Exception as e:
            logger.warning(f"Could not hot-reload CSS stream {source_id}: {e}")

        fire_entity_event("color_strip_source", "updated", source_id)
        return _css_to_response(source)

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


@router.delete(
    "/api/v1/color-strip-sources/{source_id}", status_code=204, tags=["Color Strip Sources"]
)
async def delete_color_strip_source(
    source_id: str,
    _auth: AuthRequired,
    store: ColorStripStore = Depends(get_color_strip_store),
    target_store: OutputTargetStore = Depends(get_output_target_store),
):
    """Delete a color strip source. Returns 409 if referenced by any LED target."""
    try:
        target_names = target_store.get_targets_referencing_css(source_id)
        if target_names:
            names = ", ".join(target_names)
            raise HTTPException(
                status_code=409,
                detail=f"Color strip source is referenced by target(s): {names}. "
                "Delete or reassign the target(s) first.",
            )
        composite_names = store.get_composites_referencing(source_id)
        if composite_names:
            names = ", ".join(composite_names)
            raise HTTPException(
                status_code=409,
                detail=f"Color strip source is used as a layer in composite source(s): {names}. "
                "Remove it from the composite(s) first.",
            )
        mapped_names = store.get_mapped_referencing(source_id)
        if mapped_names:
            names = ", ".join(mapped_names)
            raise HTTPException(
                status_code=409,
                detail=f"Color strip source is used as a zone in mapped source(s): {names}. "
                "Remove it from the mapped source(s) first.",
            )
        processed_names = store.get_processed_referencing(source_id)
        if processed_names:
            names = ", ".join(processed_names)
            raise HTTPException(
                status_code=409,
                detail=f"Color strip source is used as input in processed source(s): {names}. "
                "Delete or reassign the processed source(s) first.",
            )
        store.delete_source(source_id)
        fire_entity_event("color_strip_source", "deleted", source_id)
    except HTTPException:
        raise
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))
    except Exception as e:
        logger.error("Failed to delete color strip source: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


# ===== OVERLAY VISUALIZATION =====


@router.post("/api/v1/color-strip-sources/{source_id}/overlay/start", tags=["Color Strip Sources"])
async def start_css_overlay(
    source_id: str,
    _auth: AuthRequired,
    store: ColorStripStore = Depends(get_color_strip_store),
    picture_source_store: PictureSourceStore = Depends(get_picture_source_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Start screen overlay visualization for a color strip source."""
    try:
        source = store.get_source(source_id)
        if not isinstance(source, (PictureColorStripSource, AdvancedPictureColorStripSource)):
            raise HTTPException(
                status_code=400, detail="Overlay is only supported for picture color strip sources"
            )
        if not source.calibration:
            raise HTTPException(
                status_code=400, detail="Color strip source has no calibration configured"
            )

        ps_id = getattr(source, "picture_source_id", "") or ""
        display_index = _resolve_display_index(ps_id, picture_source_store)
        displays = get_available_displays()
        if not displays:
            raise HTTPException(status_code=409, detail="No displays available")
        display_index = min(display_index, len(displays) - 1)
        display_info = displays[display_index]

        await manager.start_css_overlay(source_id, display_info, source.calibration, source.name)
        return {"status": "started", "source_id": source_id}

    except HTTPException:
        raise
    except RuntimeError as e:
        raise HTTPException(status_code=409, detail=str(e))
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))
    except Exception as e:
        logger.error("Failed to start CSS overlay: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.post("/api/v1/color-strip-sources/{source_id}/overlay/stop", tags=["Color Strip Sources"])
async def stop_css_overlay(
    source_id: str,
    _auth: AuthRequired,
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Stop screen overlay visualization for a color strip source."""
    try:
        await manager.stop_css_overlay(source_id)
        return {"status": "stopped", "source_id": source_id}
    except Exception as e:
        logger.error("Failed to stop CSS overlay: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.get("/api/v1/color-strip-sources/{source_id}/overlay/status", tags=["Color Strip Sources"])
async def get_css_overlay_status(
    source_id: str,
    _auth: AuthRequired,
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Check if overlay is active for a color strip source."""
    return {"source_id": source_id, "active": manager.is_css_overlay_active(source_id)}


# ===== API INPUT: COLOR PUSH =====


@router.post("/api/v1/color-strip-sources/{source_id}/colors", tags=["Color Strip Sources"])
async def push_colors(
    source_id: str,
    body: ColorPushRequest,
    _auth: AuthRequired,
    store: ColorStripStore = Depends(get_color_strip_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Push raw LED colors to an api_input color strip source.

    Accepts either 'colors' (flat [[R,G,B], ...] array) or 'segments' (segment-based).
    The payload is forwarded to all running stream instances for this source.
    """
    try:
        source = store.get_source(source_id)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    if not isinstance(source, ApiInputColorStripSource):
        raise HTTPException(status_code=400, detail="Source is not an api_input type")

    streams = manager.color_strip_stream_manager.get_streams_by_source_id(source_id)

    if body.segments is not None:
        # Segment-based path
        seg_dicts = [s.model_dump() for s in body.segments]
        for stream in streams:
            if hasattr(stream, "push_segments"):
                stream.push_segments(seg_dicts)
        return {
            "status": "ok",
            "streams_updated": len(streams),
            "segments_applied": len(body.segments),
        }
    else:
        # Legacy flat colors path
        colors_array = np.array(body.colors, dtype=np.uint8)
        if colors_array.ndim != 2 or colors_array.shape[1] != 3:
            raise HTTPException(
                status_code=400, detail="Colors must be an array of [R,G,B] triplets"
            )
        for stream in streams:
            if hasattr(stream, "push_colors"):
                stream.push_colors(colors_array)
        return {
            "status": "ok",
            "streams_updated": len(streams),
            "leds_received": len(body.colors),
        }


@router.post("/api/v1/color-strip-sources/{source_id}/notify", tags=["Color Strip Sources"])
async def notify_source(
    source_id: str,
    _auth: AuthRequired,
    body: NotifyRequest = None,
    store: ColorStripStore = Depends(get_color_strip_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Trigger a notification on a notification color strip source.

    Fires a one-shot visual effect (flash, pulse, sweep) on all running
    stream instances for this source. Optionally specify an app name for
    color lookup or a hex color override.
    """
    try:
        source = store.get_source(source_id)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    if not isinstance(source, NotificationColorStripSource):
        raise HTTPException(status_code=400, detail="Source is not a notification type")

    app_name = body.app if body else None
    color_override = body.color if body else None

    streams = manager.color_strip_stream_manager.get_streams_by_source_id(source_id)
    accepted = 0
    for stream in streams:
        if hasattr(stream, "fire"):
            if stream.fire(app_name=app_name, color_override=color_override):
                accepted += 1

    return {
        "status": "ok",
        "streams_notified": accepted,
        "filtered": len(streams) - accepted,
    }


@router.get("/api/v1/color-strip-sources/os-notifications/history", tags=["Color Strip Sources"])
async def os_notification_history(_auth: AuthRequired):
    """Return recent OS notification capture history (newest first)."""
    from ledgrab.core.processing.os_notification_listener import (
        get_os_notification_listener,
    )

    listener = get_os_notification_listener()
    if listener is None:
        return {"available": False, "history": []}
    return {
        "available": listener.available,
        "history": listener.recent_history,
    }
