"""Output target schemas — discriminated unions per target type."""

from datetime import datetime
from typing import Annotated, Any, Dict, List, Literal

from pydantic import BaseModel, Discriminator, Field, Tag

DEFAULT_STATE_CHECK_INTERVAL = 30  # seconds between health checks

# ---------------------------------------------------------------------------
# BindableFloat — accepts plain number OR {value, source_id} dict
# ---------------------------------------------------------------------------

BindableFloatInput = float | int | Dict[str, Any]
"""API input type: a plain number (static) or {"value": float, "source_id": str}."""


class BindableFloatSchema(BaseModel):
    """Response schema for a bindable scalar property."""

    value: float = Field(description="Static value (used when source_id is empty)")
    source_id: str = Field(default="", description="Value source ID (empty = static)")


class KeyColorRectangleSchema(BaseModel):
    """A named rectangle for key color extraction (relative coords 0.0-1.0)."""

    name: str = Field(description="Rectangle name", min_length=1, max_length=50)
    x: float = Field(default=0.0, description="Left edge (0.0-1.0)", ge=0.0, le=1.0)
    y: float = Field(default=0.0, description="Top edge (0.0-1.0)", ge=0.0, le=1.0)
    width: float = Field(default=1.0, description="Width (0.0-1.0)", gt=0.0, le=1.0)
    height: float = Field(default=1.0, description="Height (0.0-1.0)", gt=0.0, le=1.0)


class HALightMappingSchema(BaseModel):
    """Maps an LED range to one HA light entity."""

    entity_id: str = Field(description="HA light entity ID (e.g. 'light.living_room')")
    led_start: int = Field(default=0, ge=0, description="Start LED index (0-based)")
    led_end: int = Field(default=-1, description="End LED index (-1 = last)")
    brightness_scale: BindableFloatInput | None = Field(
        default=1.0, description="Brightness multiplier (bindable)"
    )


class Z2MLightMappingSchema(BaseModel):
    """Maps an LED range to one Zigbee2MQTT bulb (by friendly name)."""

    friendly_name: str = Field(
        description="Z2M friendly_name (e.g. 'living_room_bulb_1')",
        min_length=1,
    )
    led_start: int = Field(default=0, ge=0, description="Start LED index (0-based)")
    led_end: int = Field(default=-1, description="End LED index (-1 = last)")
    brightness_scale: BindableFloatInput | None = Field(
        default=1.0, description="Brightness multiplier (bindable)"
    )


# =====================================================================
# Response schemas (per-type, discriminated union)
# =====================================================================


class _OutputTargetResponseBase(BaseModel):
    """Shared fields for all output target responses."""

    id: str = Field(description="Target ID")
    name: str = Field(description="Target name")
    description: str | None = Field(None, description="Description")
    tags: List[str] = Field(default_factory=list, description="User-defined tags")
    icon: str = Field(default="", description="Custom icon id from the curated icon library")
    icon_color: str = Field(default="", description="Optional CSS color override for the icon")
    created_at: datetime = Field(description="Creation timestamp")
    updated_at: datetime = Field(description="Last update timestamp")


class LedOutputTargetResponse(_OutputTargetResponseBase):
    target_type: Literal["led"] = "led"
    device_id: str = Field(default="", description="LED device ID")
    color_strip_source_id: str = Field(default="", description="Color strip source ID")
    brightness: BindableFloatInput | None = Field(None, description="Brightness (bindable)")
    fps: BindableFloatInput | None = Field(None, description="Target send FPS (bindable)")
    keepalive_interval: float = Field(default=1.0, description="Keepalive interval (s)")
    state_check_interval: int = Field(
        default=DEFAULT_STATE_CHECK_INTERVAL, description="Health check interval (s)"
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        default=0, description="Min brightness threshold (bindable, 0=disabled)"
    )
    adaptive_fps: bool = Field(
        default=False, description="Auto-reduce FPS when device is unresponsive"
    )
    protocol: str = Field(default="ddp", description="Send protocol (ddp or http)")


class HALightOutputTargetResponse(_OutputTargetResponseBase):
    target_type: Literal["ha_light"] = "ha_light"
    ha_source_id: str = Field(default="", description="Home Assistant source ID")
    source_kind: Literal["css", "color_vs"] = Field(
        default="css",
        description="Colour source kind: 'css' (per-mapping LED segments) or "
        "'color_vs' (single colour value source applied to all entities).",
    )
    color_strip_source_id: str = Field(
        default="", description="Color strip source ID (used when source_kind='css')"
    )
    color_value_source_id: str = Field(
        default="",
        description="Colour value source ID (used when source_kind='color_vs'); "
        "must reference a value source whose return_type='color'.",
    )
    brightness: BindableFloatInput | None = Field(None, description="Brightness (bindable)")
    ha_light_mappings: List[HALightMappingSchema] | None = Field(
        None, description="LED-to-light mappings"
    )
    update_rate: BindableFloatInput | None = Field(
        None, description="Service call rate Hz (bindable)"
    )
    transition: BindableFloatInput | None = Field(
        None, description="HA transition seconds (bindable)"
    )
    color_tolerance: BindableFloatInput | None = Field(
        None, description="RGB delta tolerance (bindable)"
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        default=0, description="Min brightness threshold (bindable, 0=disabled)"
    )
    stop_action: Literal["none", "turn_off", "restore"] = Field(
        default="none",
        description="What to do with mapped lights when the target stops: "
        "'none' (leave as-is), 'turn_off', or 'restore' (revert to state captured at start).",
    )


class Z2MLightOutputTargetResponse(_OutputTargetResponseBase):
    target_type: Literal["z2m_light"] = "z2m_light"
    mqtt_source_id: str = Field(
        default="",
        description="MQTT source (broker) the target publishes to. Empty = unconfigured.",
    )
    source_kind: Literal["css", "color_vs"] = Field(
        default="css",
        description="Colour source kind: 'css' (per-mapping LED segments) or "
        "'color_vs' (single colour value source applied to all bulbs).",
    )
    color_strip_source_id: str = Field(
        default="", description="Color strip source ID (used when source_kind='css')"
    )
    color_value_source_id: str = Field(
        default="",
        description="Colour value source ID (used when source_kind='color_vs').",
    )
    brightness: BindableFloatInput | None = Field(None, description="Brightness (bindable)")
    z2m_light_mappings: List[Z2MLightMappingSchema] | None = Field(
        None, description="LED-to-bulb mappings (by Z2M friendly_name)"
    )
    base_topic: str = Field(
        default="zigbee2mqtt",
        description="Z2M MQTT base topic prefix (override if your Z2M instance is non-default).",
    )
    update_rate: BindableFloatInput | None = Field(
        None, description="Publish rate Hz (bindable; 0.5-10)"
    )
    transition: BindableFloatInput | None = Field(
        None, description="Z2M transition seconds (bindable)"
    )
    color_tolerance: BindableFloatInput | None = Field(
        None, description="RGB delta tolerance (bindable)"
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        default=0, description="Min brightness threshold (bindable, 0=disabled)"
    )
    stop_action: Literal["none", "turn_off"] = Field(
        default="none",
        description="What to do with mapped bulbs when the target stops: "
        "'none' (leave as-is) or 'turn_off'.",
    )


OutputTargetResponse = Annotated[
    Annotated[LedOutputTargetResponse, Tag("led")]
    | Annotated[HALightOutputTargetResponse, Tag("ha_light")]
    | Annotated[Z2MLightOutputTargetResponse, Tag("z2m_light")],
    Discriminator("target_type"),
]

# =====================================================================
# Create schemas (per-type, discriminated union)
# =====================================================================


class _OutputTargetCreateBase(BaseModel):
    """Shared fields for all output target create requests."""

    name: str = Field(description="Target name", min_length=1, max_length=100)
    description: str | None = Field(None, description="Optional description", max_length=500)
    tags: List[str] = Field(default_factory=list, description="User-defined tags")
    icon: str | None = Field(
        None, max_length=64, description="Custom icon id from the curated icon library"
    )
    icon_color: str | None = Field(
        None, max_length=32, description="Optional CSS color override for the icon"
    )


class LedOutputTargetCreate(_OutputTargetCreateBase):
    target_type: Literal["led"] = "led"
    device_id: str = Field(default="", description="LED device ID")
    color_strip_source_id: str = Field(default="", description="Color strip source ID")
    brightness: BindableFloatInput | None = Field(default=1.0, description="Brightness (bindable)")
    fps: BindableFloatInput | None = Field(
        default=30, description="Target send FPS (bindable, 1-90)"
    )
    keepalive_interval: float = Field(
        default=1.0,
        description="Keepalive send interval when screen is static (0.5-5.0s)",
        ge=0.5,
        le=5.0,
    )
    state_check_interval: int = Field(
        default=DEFAULT_STATE_CHECK_INTERVAL,
        description="Device health check interval (5-600s)",
        ge=5,
        le=600,
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        default=0,
        description="Min brightness threshold (bindable, 0=disabled); below this -> off",
    )
    adaptive_fps: bool = Field(
        default=False, description="Auto-reduce FPS when device is unresponsive"
    )
    protocol: str = Field(
        default="ddp",
        pattern="^(ddp|http)$",
        description="Send protocol: ddp (UDP) or http (JSON API)",
    )


class HALightOutputTargetCreate(_OutputTargetCreateBase):
    target_type: Literal["ha_light"] = "ha_light"
    ha_source_id: str = Field(default="", description="Home Assistant source ID")
    source_kind: Literal["css", "color_vs"] = Field(
        default="css",
        description="Colour source kind: 'css' (per-mapping LED segments) or "
        "'color_vs' (single colour value source applied to all entities).",
    )
    color_strip_source_id: str = Field(
        default="", description="Color strip source ID (used when source_kind='css')"
    )
    color_value_source_id: str = Field(
        default="",
        description="Colour value source ID (used when source_kind='color_vs').",
    )
    brightness: BindableFloatInput | None = Field(default=1.0, description="Brightness (bindable)")
    ha_light_mappings: List[HALightMappingSchema] | None = Field(
        None, description="LED-to-light mappings"
    )
    update_rate: BindableFloatInput | None = Field(
        default=2.0, description="Service call rate in Hz (bindable)"
    )
    transition: BindableFloatInput | None = Field(
        default=0.5, description="HA transition seconds (bindable)"
    )
    color_tolerance: BindableFloatInput | None = Field(
        default=5, description="RGB delta tolerance (bindable)"
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        default=0,
        description="Min brightness threshold (bindable, 0=disabled); below this -> off",
    )
    stop_action: Literal["none", "turn_off", "restore"] = Field(
        default="none",
        description="Finalization on stop: 'none', 'turn_off', or 'restore'.",
    )


class Z2MLightOutputTargetCreate(_OutputTargetCreateBase):
    target_type: Literal["z2m_light"] = "z2m_light"
    mqtt_source_id: str = Field(
        default="",
        description="MQTT source (broker) the target publishes to. Required to start.",
    )
    source_kind: Literal["css", "color_vs"] = Field(
        default="css",
        description="Colour source kind: 'css' or 'color_vs'.",
    )
    color_strip_source_id: str = Field(
        default="", description="Color strip source ID (used when source_kind='css')"
    )
    color_value_source_id: str = Field(
        default="",
        description="Colour value source ID (used when source_kind='color_vs').",
    )
    brightness: BindableFloatInput | None = Field(default=1.0, description="Brightness (bindable)")
    z2m_light_mappings: List[Z2MLightMappingSchema] | None = Field(
        None, description="LED-to-bulb mappings (by Z2M friendly_name)"
    )
    base_topic: str = Field(
        default="zigbee2mqtt",
        max_length=128,
        description="Z2M MQTT base topic prefix.",
    )
    update_rate: BindableFloatInput | None = Field(
        default=5.0, description="Publish rate in Hz (bindable; 0.5-10)"
    )
    transition: BindableFloatInput | None = Field(
        default=0.3, description="Z2M transition seconds (bindable)"
    )
    color_tolerance: BindableFloatInput | None = Field(
        default=5, description="RGB delta tolerance (bindable)"
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        default=0,
        description="Min brightness threshold (bindable, 0=disabled); below this -> off",
    )
    stop_action: Literal["none", "turn_off"] = Field(
        default="none",
        description="Finalization on stop: 'none' or 'turn_off'.",
    )


OutputTargetCreate = Annotated[
    Annotated[LedOutputTargetCreate, Tag("led")]
    | Annotated[HALightOutputTargetCreate, Tag("ha_light")]
    | Annotated[Z2MLightOutputTargetCreate, Tag("z2m_light")],
    Discriminator("target_type"),
]

# =====================================================================
# Update schemas (per-type, discriminated union)
# =====================================================================


class _OutputTargetUpdateBase(BaseModel):
    """Shared fields for all output target update requests."""

    name: str | None = Field(None, description="Target name", min_length=1, max_length=100)
    description: str | None = Field(None, description="Optional description", max_length=500)
    tags: List[str] | None = None
    icon: str | None = Field(
        None,
        max_length=64,
        description="Custom icon id; pass empty string to clear and inherit from device.",
    )
    icon_color: str | None = Field(
        None,
        max_length=32,
        description="Optional CSS color override for the icon; empty string clears.",
    )


class LedOutputTargetUpdate(_OutputTargetUpdateBase):
    target_type: Literal["led"] = "led"
    device_id: str | None = Field(None, description="LED device ID")
    color_strip_source_id: str | None = Field(None, description="Color strip source ID")
    brightness: BindableFloatInput | None = Field(None, description="Brightness (bindable)")
    fps: BindableFloatInput | None = Field(None, description="Target send FPS (bindable, 1-90)")
    keepalive_interval: float | None = Field(
        None, description="Keepalive interval (0.5-5.0s)", ge=0.5, le=5.0
    )
    state_check_interval: int | None = Field(
        None, description="Health check interval (5-600s)", ge=5, le=600
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        None, description="Min brightness threshold (bindable, 0=disabled)"
    )
    adaptive_fps: bool | None = Field(
        None, description="Auto-reduce FPS when device is unresponsive"
    )
    protocol: str | None = Field(
        None, pattern="^(ddp|http)$", description="Send protocol: ddp (UDP) or http (JSON API)"
    )


class HALightOutputTargetUpdate(_OutputTargetUpdateBase):
    target_type: Literal["ha_light"] = "ha_light"
    ha_source_id: str | None = Field(None, description="Home Assistant source ID")
    source_kind: Literal["css", "color_vs"] | None = Field(
        None,
        description="Colour source kind: 'css' or 'color_vs'.",
    )
    color_strip_source_id: str | None = Field(None, description="Color strip source ID")
    color_value_source_id: str | None = Field(
        None,
        description="Colour value source ID (used when source_kind='color_vs').",
    )
    brightness: BindableFloatInput | None = Field(None, description="Brightness (bindable)")
    ha_light_mappings: List[HALightMappingSchema] | None = Field(
        None, description="LED-to-light mappings"
    )
    update_rate: BindableFloatInput | None = Field(
        None, description="Service call rate Hz (bindable)"
    )
    transition: BindableFloatInput | None = Field(
        None, description="HA transition seconds (bindable)"
    )
    color_tolerance: BindableFloatInput | None = Field(
        None, description="RGB delta tolerance (bindable)"
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        None, description="Min brightness threshold (bindable, 0=disabled)"
    )
    stop_action: Literal["none", "turn_off", "restore"] | None = Field(
        None, description="Finalization on stop: 'none', 'turn_off', or 'restore'."
    )


class Z2MLightOutputTargetUpdate(_OutputTargetUpdateBase):
    target_type: Literal["z2m_light"] = "z2m_light"
    mqtt_source_id: str | None = Field(
        None,
        description="MQTT source (broker) id. Empty string clears the binding.",
    )
    source_kind: Literal["css", "color_vs"] | None = Field(
        None, description="Colour source kind: 'css' or 'color_vs'."
    )
    color_strip_source_id: str | None = Field(None, description="Color strip source ID")
    color_value_source_id: str | None = Field(
        None, description="Colour value source ID (used when source_kind='color_vs')."
    )
    brightness: BindableFloatInput | None = Field(None, description="Brightness (bindable)")
    z2m_light_mappings: List[Z2MLightMappingSchema] | None = Field(
        None, description="LED-to-bulb mappings (by Z2M friendly_name)"
    )
    base_topic: str | None = Field(None, max_length=128, description="Z2M MQTT base topic prefix.")
    update_rate: BindableFloatInput | None = Field(
        None, description="Publish rate Hz (bindable; 0.5-10)"
    )
    transition: BindableFloatInput | None = Field(
        None, description="Z2M transition seconds (bindable)"
    )
    color_tolerance: BindableFloatInput | None = Field(
        None, description="RGB delta tolerance (bindable)"
    )
    min_brightness_threshold: BindableFloatInput | None = Field(
        None, description="Min brightness threshold (bindable, 0=disabled)"
    )
    stop_action: Literal["none", "turn_off"] | None = Field(
        None, description="Finalization on stop: 'none' or 'turn_off'."
    )


OutputTargetUpdate = Annotated[
    Annotated[LedOutputTargetUpdate, Tag("led")]
    | Annotated[HALightOutputTargetUpdate, Tag("ha_light")]
    | Annotated[Z2MLightOutputTargetUpdate, Tag("z2m_light")],
    Discriminator("target_type"),
]

# =====================================================================
# List response & utility schemas
# =====================================================================


class OutputTargetListResponse(BaseModel):
    """List of output targets response."""

    targets: List[OutputTargetResponse] = Field(description="List of output targets")
    count: int = Field(description="Number of targets")


class TargetProcessingState(BaseModel):
    """Processing state for an output target."""

    target_id: str = Field(description="Target ID")
    device_id: str | None = Field(None, description="Device ID")
    color_strip_source_id: str = Field(default="", description="Color strip source ID")
    processing: bool = Field(description="Whether processing is active")
    fps_actual: float | None = Field(None, description="Actual FPS achieved")
    fps_potential: float | None = Field(
        None, description="Potential FPS (processing speed without throttle)"
    )
    fps_target: int | None = Field(None, description="Target FPS")
    fps_capture: int | None = Field(
        None, description="Configured capture-side FPS for the underlying color strip stream"
    )
    frames_skipped: int | None = Field(None, description="Frames skipped (no screen change)")
    frames_keepalive: int | None = Field(None, description="Keepalive frames sent during standby")
    fps_current: int | None = Field(None, description="Frames sent in the last second")
    timing_send_ms: float | None = Field(None, description="DDP send time (ms)")
    timing_extract_ms: float | None = Field(None, description="Border pixel extraction time (ms)")
    timing_map_leds_ms: float | None = Field(None, description="LED color mapping time (ms)")
    timing_smooth_ms: float | None = Field(None, description="Temporal smoothing time (ms)")
    timing_total_ms: float | None = Field(None, description="Total processing time per frame (ms)")
    timing_audio_read_ms: float | None = Field(None, description="Audio device read time (ms)")
    timing_audio_fft_ms: float | None = Field(None, description="Audio FFT analysis time (ms)")
    timing_audio_render_ms: float | None = Field(
        None, description="Audio visualization render time (ms)"
    )
    display_index: int | None = Field(None, description="Current display index")
    overlay_active: bool = Field(
        default=False, description="Whether visualization overlay is active"
    )
    last_update: datetime | None = Field(None, description="Last successful update")
    errors: List[str] = Field(default_factory=list, description="Recent errors")
    device_online: bool = Field(default=False, description="Whether device is reachable")
    device_latency_ms: float | None = Field(None, description="Health check latency in ms")
    device_name: str | None = Field(None, description="Device name reported by firmware")
    device_version: str | None = Field(None, description="Firmware version")
    device_led_count: int | None = Field(None, description="LED count reported by device")
    device_rgbw: bool | None = Field(None, description="Whether device uses RGBW LEDs")
    device_led_type: str | None = Field(
        None, description="LED chip type (e.g. WS2812B, SK6812 RGBW)"
    )
    device_fps: int | None = Field(
        None, description="Device-reported FPS (WLED internal refresh rate)"
    )
    device_last_checked: datetime | None = Field(None, description="Last health check time")
    device_error: str | None = Field(None, description="Last health check error")
    device_streaming_reachable: bool | None = Field(
        None, description="Device reachable during streaming (HTTP probe)"
    )
    fps_effective: int | None = Field(None, description="Effective FPS after adaptive reduction")


class TargetMetricsResponse(BaseModel):
    """Target metrics response."""

    target_id: str = Field(description="Target ID")
    device_id: str | None = Field(None, description="Device ID")
    processing: bool = Field(description="Whether processing is active")
    fps_actual: float | None = Field(None, description="Actual FPS")
    fps_target: int | None = Field(None, description="Target FPS")
    uptime_seconds: float = Field(description="Processing uptime in seconds")
    frames_processed: int = Field(description="Total frames processed")
    errors_count: int = Field(description="Total error count")
    last_error: str | None = Field(None, description="Last error message")
    last_update: datetime | None = Field(None, description="Last update timestamp")


class BulkTargetRequest(BaseModel):
    """Request body for bulk start/stop operations."""

    ids: List[str] = Field(description="List of target IDs to operate on")


class BulkTargetResponse(BaseModel):
    """Response for bulk start/stop operations."""

    started: List[str] = Field(
        default_factory=list, description="IDs that were successfully started"
    )
    stopped: List[str] = Field(
        default_factory=list, description="IDs that were successfully stopped"
    )
    errors: Dict[str, str] = Field(
        default_factory=dict, description="Map of target ID to error message for failures"
    )
