"""Output target routes: CRUD endpoints and batch state/metrics queries."""

import asyncio

from fastapi import APIRouter, HTTPException, Depends

from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
    fire_entity_event,
    get_device_store,
    get_output_target_store,
    get_processor_manager,
)
from wled_controller.api.schemas.output_targets import (
    KeyColorsSettingsSchema,
    OutputTargetCreate,
    OutputTargetListResponse,
    OutputTargetResponse,
    OutputTargetUpdate,
)
from wled_controller.core.processing.processor_manager import ProcessorManager
from wled_controller.storage import DeviceStore
from wled_controller.storage.wled_output_target import WledOutputTarget
from wled_controller.storage.key_colors_output_target import (
    KeyColorsSettings,
    KeyColorsOutputTarget,
)
from wled_controller.storage.ha_light_output_target import (
    HALightMapping,
    HALightOutputTarget,
)
from wled_controller.api.schemas.output_targets import HALightMappingSchema
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.utils import get_logger
from wled_controller.storage.base_store import EntityNotFoundError

logger = get_logger(__name__)

router = APIRouter()


def _kc_settings_to_schema(settings: KeyColorsSettings) -> KeyColorsSettingsSchema:
    """Convert core KeyColorsSettings to schema."""
    return KeyColorsSettingsSchema(
        fps=settings.fps,
        interpolation_mode=settings.interpolation_mode,
        smoothing=settings.smoothing,
        pattern_template_id=settings.pattern_template_id,
        brightness=settings.brightness,
        brightness_value_source_id=settings.brightness_value_source_id,
    )


def _kc_schema_to_settings(schema: KeyColorsSettingsSchema) -> KeyColorsSettings:
    """Convert schema KeyColorsSettings to core."""
    return KeyColorsSettings(
        fps=schema.fps,
        interpolation_mode=schema.interpolation_mode,
        smoothing=schema.smoothing,
        pattern_template_id=schema.pattern_template_id,
        brightness=schema.brightness,
        brightness_value_source_id=schema.brightness_value_source_id,
    )


def _target_to_response(target) -> OutputTargetResponse:
    """Convert an OutputTarget to OutputTargetResponse."""
    if isinstance(target, WledOutputTarget):
        return OutputTargetResponse(
            id=target.id,
            name=target.name,
            target_type=target.target_type,
            device_id=target.device_id,
            color_strip_source_id=target.color_strip_source_id,
            brightness_value_source_id=target.brightness_value_source_id or "",
            fps=target.fps,
            keepalive_interval=target.keepalive_interval,
            state_check_interval=target.state_check_interval,
            min_brightness_threshold=target.min_brightness_threshold,
            adaptive_fps=target.adaptive_fps,
            protocol=target.protocol,
            description=target.description,
            tags=target.tags,
            created_at=target.created_at,
            updated_at=target.updated_at,
        )
    elif isinstance(target, KeyColorsOutputTarget):
        return OutputTargetResponse(
            id=target.id,
            name=target.name,
            target_type=target.target_type,
            picture_source_id=target.picture_source_id,
            key_colors_settings=_kc_settings_to_schema(target.settings),
            description=target.description,
            tags=target.tags,
            created_at=target.created_at,
            updated_at=target.updated_at,
        )
    elif isinstance(target, HALightOutputTarget):
        return OutputTargetResponse(
            id=target.id,
            name=target.name,
            target_type=target.target_type,
            ha_source_id=target.ha_source_id,
            color_strip_source_id=target.color_strip_source_id,
            ha_light_mappings=[
                HALightMappingSchema(
                    entity_id=m.entity_id,
                    led_start=m.led_start,
                    led_end=m.led_end,
                    brightness_scale=m.brightness_scale,
                )
                for m in target.light_mappings
            ],
            update_rate=target.update_rate,
            ha_transition=target.transition,
            color_tolerance=target.color_tolerance,
            min_brightness_threshold=target.min_brightness_threshold,
            description=target.description,
            tags=target.tags,
            created_at=target.created_at,
            updated_at=target.updated_at,
        )
    else:
        return OutputTargetResponse(
            id=target.id,
            name=target.name,
            target_type=target.target_type,
            description=target.description,
            tags=target.tags,
            created_at=target.created_at,
            updated_at=target.updated_at,
        )


# ===== CRUD ENDPOINTS =====


@router.post(
    "/api/v1/output-targets", response_model=OutputTargetResponse, tags=["Targets"], status_code=201
)
async def create_target(
    data: OutputTargetCreate,
    _auth: AuthRequired,
    target_store: OutputTargetStore = Depends(get_output_target_store),
    device_store: DeviceStore = Depends(get_device_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Create a new output target."""
    try:
        # Validate device exists if provided
        if data.device_id:
            try:
                device_store.get_device(data.device_id)
            except ValueError:
                raise HTTPException(status_code=422, detail=f"Device {data.device_id} not found")

        kc_settings = (
            _kc_schema_to_settings(data.key_colors_settings) if data.key_colors_settings else None
        )
        ha_mappings = (
            [
                HALightMapping(
                    entity_id=m.entity_id,
                    led_start=m.led_start,
                    led_end=m.led_end,
                    brightness_scale=m.brightness_scale,
                )
                for m in data.ha_light_mappings
            ]
            if data.ha_light_mappings
            else None
        )

        # Create in store
        target = target_store.create_target(
            name=data.name,
            target_type=data.target_type,
            device_id=data.device_id,
            color_strip_source_id=data.color_strip_source_id,
            brightness_value_source_id=data.brightness_value_source_id,
            fps=data.fps,
            keepalive_interval=data.keepalive_interval,
            state_check_interval=data.state_check_interval,
            min_brightness_threshold=data.min_brightness_threshold,
            adaptive_fps=data.adaptive_fps,
            protocol=data.protocol,
            picture_source_id=data.picture_source_id,
            key_colors_settings=kc_settings,
            description=data.description,
            tags=data.tags,
            ha_source_id=data.ha_source_id,
            ha_light_mappings=ha_mappings,
            update_rate=data.update_rate,
            transition=data.transition,
            color_tolerance=data.color_tolerance,
        )

        # Register in processor manager
        try:
            target.register_with_manager(manager)
        except ValueError as e:
            logger.warning(f"Could not register target {target.id} in processor manager: {e}")

        fire_entity_event("output_target", "created", target.id)
        return _target_to_response(target)

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


@router.get("/api/v1/output-targets", response_model=OutputTargetListResponse, tags=["Targets"])
async def list_targets(
    _auth: AuthRequired,
    target_store: OutputTargetStore = Depends(get_output_target_store),
):
    """List all output targets."""
    targets = target_store.get_all_targets()
    responses = [_target_to_response(t) for t in targets]
    return OutputTargetListResponse(targets=responses, count=len(responses))


@router.get("/api/v1/output-targets/batch/states", tags=["Processing"])
async def batch_target_states(
    _auth: AuthRequired,
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Get processing state for all targets in a single request."""
    return {"states": manager.get_all_target_states()}


@router.get("/api/v1/output-targets/batch/metrics", tags=["Metrics"])
async def batch_target_metrics(
    _auth: AuthRequired,
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Get metrics for all targets in a single request."""
    return {"metrics": manager.get_all_target_metrics()}


@router.get(
    "/api/v1/output-targets/{target_id}", response_model=OutputTargetResponse, tags=["Targets"]
)
async def get_target(
    target_id: str,
    _auth: AuthRequired,
    target_store: OutputTargetStore = Depends(get_output_target_store),
):
    """Get a output target by ID."""
    try:
        target = target_store.get_target(target_id)
        return _target_to_response(target)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))


@router.put(
    "/api/v1/output-targets/{target_id}", response_model=OutputTargetResponse, tags=["Targets"]
)
async def update_target(
    target_id: str,
    data: OutputTargetUpdate,
    _auth: AuthRequired,
    target_store: OutputTargetStore = Depends(get_output_target_store),
    device_store: DeviceStore = Depends(get_device_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Update a output target."""
    try:
        # Validate device exists if changing
        if data.device_id is not None and data.device_id:
            try:
                device_store.get_device(data.device_id)
            except ValueError:
                raise HTTPException(status_code=422, detail=f"Device {data.device_id} not found")

        # Build KC settings with partial-update support: only apply fields that were
        # explicitly provided in the request body, merging with the existing settings.
        kc_settings = None
        if data.key_colors_settings is not None:
            incoming = data.key_colors_settings.model_dump(exclude_unset=True)
            try:
                existing_target = target_store.get_target(target_id)
            except ValueError:
                existing_target = None

            if isinstance(existing_target, KeyColorsOutputTarget):
                ex = existing_target.settings
                merged = KeyColorsSettingsSchema(
                    fps=incoming.get("fps", ex.fps),
                    interpolation_mode=incoming.get("interpolation_mode", ex.interpolation_mode),
                    smoothing=incoming.get("smoothing", ex.smoothing),
                    pattern_template_id=incoming.get("pattern_template_id", ex.pattern_template_id),
                    brightness=incoming.get("brightness", ex.brightness),
                    brightness_value_source_id=incoming.get(
                        "brightness_value_source_id", ex.brightness_value_source_id
                    ),
                )
                kc_settings = _kc_schema_to_settings(merged)
            else:
                kc_settings = _kc_schema_to_settings(data.key_colors_settings)

        # Update in store
        target = target_store.update_target(
            target_id=target_id,
            name=data.name,
            device_id=data.device_id,
            color_strip_source_id=data.color_strip_source_id,
            brightness_value_source_id=data.brightness_value_source_id,
            fps=data.fps,
            keepalive_interval=data.keepalive_interval,
            state_check_interval=data.state_check_interval,
            min_brightness_threshold=data.min_brightness_threshold,
            adaptive_fps=data.adaptive_fps,
            protocol=data.protocol,
            key_colors_settings=kc_settings,
            description=data.description,
            tags=data.tags,
        )

        # Detect KC brightness VS change (inside key_colors_settings)
        kc_brightness_vs_changed = False
        if data.key_colors_settings is not None:
            kc_incoming = data.key_colors_settings.model_dump(exclude_unset=True)
            if "brightness_value_source_id" in kc_incoming:
                kc_brightness_vs_changed = True

        # Sync processor manager (run in thread — css release/acquire can block)
        try:
            await asyncio.to_thread(
                target.sync_with_manager,
                manager,
                settings_changed=(
                    data.fps is not None
                    or data.keepalive_interval is not None
                    or data.state_check_interval is not None
                    or data.min_brightness_threshold is not None
                    or data.adaptive_fps is not None
                    or data.key_colors_settings is not None
                ),
                css_changed=data.color_strip_source_id is not None,
                brightness_vs_changed=(
                    data.brightness_value_source_id is not None or kc_brightness_vs_changed
                ),
            )
        except ValueError as e:
            logger.debug("Processor config update skipped for target %s: %s", target_id, e)
            pass

        # Device change requires async stop -> swap -> start cycle
        if data.device_id is not None:
            try:
                await manager.update_target_device(target_id, target.device_id)
            except ValueError as e:
                logger.debug("Device update skipped for target %s: %s", target_id, e)
                pass

        fire_entity_event("output_target", "updated", target_id)
        return _target_to_response(target)

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


@router.delete("/api/v1/output-targets/{target_id}", status_code=204, tags=["Targets"])
async def delete_target(
    target_id: str,
    _auth: AuthRequired,
    target_store: OutputTargetStore = Depends(get_output_target_store),
    manager: ProcessorManager = Depends(get_processor_manager),
):
    """Delete a output target. Stops processing first if active."""
    try:
        # Stop processing if running
        try:
            await manager.stop_processing(target_id)
        except ValueError as e:
            logger.debug("Stop processing skipped for target %s (not running): %s", target_id, e)
            pass

        # Remove from manager
        try:
            manager.remove_target(target_id)
        except (ValueError, RuntimeError) as e:
            logger.debug("Remove target from manager skipped for %s: %s", target_id, e)
            pass

        # Delete from store
        target_store.delete_target(target_id)

        fire_entity_event("output_target", "deleted", target_id)
        logger.info(f"Deleted target {target_id}")

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