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

import asyncio
from typing import Annotated

from fastapi import APIRouter, Body, 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 (
    HALightMappingSchema,
    HALightOutputTargetResponse,
    LedOutputTargetResponse,
    OutputTargetCreate,
    OutputTargetListResponse,
    OutputTargetResponse,
    OutputTargetUpdate,
)
from wled_controller.core.processing.processor_manager import ProcessorManager
from wled_controller.storage import DeviceStore
from wled_controller.storage.bindable import BindableFloat
from wled_controller.storage.wled_output_target import WledOutputTarget
from wled_controller.storage.ha_light_output_target import (
    HALightMapping,
    HALightOutputTarget,
)
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 _led_target_to_response(target: WledOutputTarget) -> LedOutputTargetResponse:
    """Convert a WledOutputTarget to LedOutputTargetResponse."""
    return LedOutputTargetResponse(
        id=target.id,
        name=target.name,
        device_id=target.device_id,
        color_strip_source_id=target.color_strip_source_id,
        brightness=target.brightness.to_dict(),
        fps=target.fps.to_dict(),
        keepalive_interval=target.keepalive_interval,
        state_check_interval=target.state_check_interval,
        min_brightness_threshold=target.min_brightness_threshold.to_dict(),
        adaptive_fps=target.adaptive_fps,
        protocol=target.protocol,
        description=target.description,
        tags=target.tags,
        created_at=target.created_at,
        updated_at=target.updated_at,
    )


def _ha_light_target_to_response(
    target: HALightOutputTarget,
) -> HALightOutputTargetResponse:
    """Convert an HALightOutputTarget to HALightOutputTargetResponse."""
    return HALightOutputTargetResponse(
        id=target.id,
        name=target.name,
        ha_source_id=target.ha_source_id,
        color_strip_source_id=target.color_strip_source_id,
        brightness=target.brightness.to_dict(),
        ha_light_mappings=[
            HALightMappingSchema(
                entity_id=m.entity_id,
                led_start=m.led_start,
                led_end=m.led_end,
                brightness_scale=m.brightness_scale.to_dict(),
            )
            for m in target.light_mappings
        ],
        update_rate=target.update_rate.to_dict(),
        transition=target.transition.to_dict(),
        color_tolerance=target.color_tolerance.to_dict(),
        min_brightness_threshold=target.min_brightness_threshold.to_dict(),
        description=target.description,
        tags=target.tags,
        created_at=target.created_at,
        updated_at=target.updated_at,
    )


def _target_to_response(target) -> OutputTargetResponse:
    """Convert any OutputTarget to the appropriate typed response."""
    if isinstance(target, WledOutputTarget):
        return _led_target_to_response(target)
    elif isinstance(target, HALightOutputTarget):
        return _ha_light_target_to_response(target)
    else:
        # Fallback for unknown types — use LED response with defaults
        return LedOutputTargetResponse(
            id=target.id,
            name=target.name,
            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: Annotated[OutputTargetCreate, Body(discriminator="target_type")],
    _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
        device_id = getattr(data, "device_id", "")
        if device_id:
            try:
                device_store.get_device(device_id)
            except ValueError:
                raise HTTPException(status_code=422, detail=f"Device {device_id} not found")

        ha_light_mappings_raw = getattr(data, "ha_light_mappings", None)
        ha_mappings = (
            [
                HALightMapping(
                    entity_id=m.entity_id,
                    led_start=m.led_start,
                    led_end=m.led_end,
                    brightness_scale=BindableFloat.from_raw(m.brightness_scale, default=1.0),
                )
                for m in ha_light_mappings_raw
            ]
            if ha_light_mappings_raw
            else None
        )

        # Create in store
        target = target_store.create_target(
            name=data.name,
            target_type=data.target_type,
            device_id=device_id,
            color_strip_source_id=getattr(data, "color_strip_source_id", ""),
            brightness=getattr(data, "brightness", 1.0),
            fps=getattr(data, "fps", 30),
            keepalive_interval=getattr(data, "keepalive_interval", 1.0),
            state_check_interval=getattr(data, "state_check_interval", 30),
            min_brightness_threshold=getattr(data, "min_brightness_threshold", 0),
            adaptive_fps=getattr(data, "adaptive_fps", False),
            protocol=getattr(data, "protocol", "ddp"),
            description=data.description,
            tags=data.tags,
            ha_source_id=getattr(data, "ha_source_id", ""),
            ha_light_mappings=ha_mappings,
            update_rate=getattr(data, "update_rate", 2.0),
            transition=getattr(data, "transition", 0.5),
            color_tolerance=getattr(data, "color_tolerance", 5),
        )

        # 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: Annotated[OutputTargetUpdate, Body(discriminator="target_type")],
    _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
        device_id = getattr(data, "device_id", None)
        if device_id is not None and device_id:
            try:
                device_store.get_device(device_id)
            except ValueError:
                raise HTTPException(status_code=422, detail=f"Device {device_id} not found")

        # Build HA light mappings if provided
        ha_light_mappings_raw = getattr(data, "ha_light_mappings", None)
        ha_mappings = None
        if ha_light_mappings_raw is not None:
            ha_mappings = [
                HALightMapping(
                    entity_id=m.entity_id,
                    led_start=m.led_start,
                    led_end=m.led_end,
                    brightness_scale=BindableFloat.from_raw(m.brightness_scale, default=1.0),
                )
                for m in ha_light_mappings_raw
            ]

        # Update in store
        target = target_store.update_target(
            target_id=target_id,
            name=data.name,
            device_id=device_id,
            color_strip_source_id=getattr(data, "color_strip_source_id", None),
            brightness=getattr(data, "brightness", None),
            fps=getattr(data, "fps", None),
            keepalive_interval=getattr(data, "keepalive_interval", None),
            state_check_interval=getattr(data, "state_check_interval", None),
            min_brightness_threshold=getattr(data, "min_brightness_threshold", None),
            adaptive_fps=getattr(data, "adaptive_fps", None),
            protocol=getattr(data, "protocol", None),
            description=data.description,
            tags=data.tags,
            ha_source_id=getattr(data, "ha_source_id", None),
            ha_light_mappings=ha_mappings,
            update_rate=getattr(data, "update_rate", None),
            transition=getattr(data, "transition", None),
            color_tolerance=getattr(data, "color_tolerance", None),
        )

        # Sync processor manager (run in thread — css release/acquire can block)
        color_strip_source_id = getattr(data, "color_strip_source_id", None)
        fps = getattr(data, "fps", None)
        keepalive_interval = getattr(data, "keepalive_interval", None)
        state_check_interval = getattr(data, "state_check_interval", None)
        min_brightness_threshold = getattr(data, "min_brightness_threshold", None)
        adaptive_fps = getattr(data, "adaptive_fps", None)
        update_rate = getattr(data, "update_rate", None)
        transition = getattr(data, "transition", None)
        color_tolerance = getattr(data, "color_tolerance", None)
        brightness = getattr(data, "brightness", None)

        try:
            await asyncio.to_thread(
                target.sync_with_manager,
                manager,
                settings_changed=(
                    fps is not None
                    or keepalive_interval is not None
                    or state_check_interval is not None
                    or min_brightness_threshold is not None
                    or adaptive_fps is not None
                    or update_rate is not None
                    or transition is not None
                    or color_tolerance is not None
                    or ha_light_mappings_raw is not None
                    or brightness is not None
                ),
                css_changed=color_strip_source_id is not None,
                brightness_changed=brightness is not None,
            )
        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 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")
