"""Color strip source schemas (CRUD)."""

from datetime import datetime
from typing import Dict, List, Literal, Optional

from pydantic import BaseModel, Field, model_validator

from wled_controller.api.schemas.devices import Calibration


class AppSoundOverride(BaseModel):
    """Per-application sound override for notification sources."""

    sound_asset_id: Optional[str] = Field(None, description="Asset ID for the sound (None = mute this app)")
    volume: Optional[float] = Field(None, ge=0.0, le=1.0, description="Volume override (None = use global)")


class AnimationConfig(BaseModel):
    """Procedural animation configuration for static/gradient color strip sources."""

    enabled: bool = True
    type: str = "breathing"  # breathing | color_cycle | gradient_shift | wave
    speed: float = Field(1.0, ge=0.1, le=10.0, description="Speed multiplier (0.1–10.0)")


class ColorStop(BaseModel):
    """A single color stop in a gradient."""

    position: float = Field(description="Relative position along the strip (0.0–1.0)", ge=0.0, le=1.0)
    color: List[int] = Field(description="Primary RGB color [R, G, B] (0–255 each)")
    color_right: Optional[List[int]] = Field(
        None,
        description="Optional right-side RGB color for a hard edge (bidirectional stop)",
    )


class CompositeLayer(BaseModel):
    """A single layer in a composite color strip source."""

    source_id: str = Field(description="ID of the layer's color strip source")
    blend_mode: str = Field(default="normal", description="Blend mode: normal|add|multiply|screen|override")
    opacity: float = Field(default=1.0, ge=0.0, le=1.0, description="Layer opacity 0.0-1.0")
    enabled: bool = Field(default=True, description="Whether this layer is active")
    brightness_source_id: Optional[str] = Field(None, description="Optional value source ID for dynamic brightness")
    processing_template_id: Optional[str] = Field(None, description="Optional color strip processing template ID")
    start: int = Field(default=0, ge=0, description="First LED index for range (0 = full strip)")
    end: int = Field(default=0, ge=0, description="Last LED index exclusive for range (0 = full strip)")
    reverse: bool = Field(default=False, description="Reverse layer output within its range")


class MappedZone(BaseModel):
    """A single zone in a mapped color strip source."""

    source_id: str = Field(description="ID of the zone's color strip source")
    start: int = Field(default=0, ge=0, description="First LED index (0-based)")
    end: int = Field(default=0, ge=0, description="Last LED index (exclusive); 0 = auto-fill")
    reverse: bool = Field(default=False, description="Reverse zone output")


class ColorStripSourceCreate(BaseModel):
    """Request to create a color strip source."""

    name: str = Field(description="Source name", min_length=1, max_length=100)
    source_type: Literal["picture", "picture_advanced", "static", "gradient", "color_cycle", "effect", "composite", "mapped", "audio", "api_input", "notification", "daylight", "candlelight", "processed", "weather"] = Field(default="picture", description="Source type")
    # picture-type fields
    picture_source_id: str = Field(default="", description="Picture source ID (for picture type)")
    smoothing: float = Field(default=0.3, description="Temporal smoothing (0.0=none, 1.0=full)", ge=0.0, le=1.0)
    interpolation_mode: str = Field(default="average", description="LED color interpolation mode (average, median, dominant)")
    calibration: Optional[Calibration] = Field(None, description="LED calibration (position and count per edge)")
    # static-type fields
    color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B] (0-255 each, for static type)")
    # gradient-type fields
    stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
    # color_cycle-type fields
    colors: Optional[List[List[int]]] = Field(None, description="List of [R,G,B] colors to cycle (color_cycle type)")
    # effect-type fields
    effect_type: Optional[str] = Field(None, description="Effect algorithm: fire|meteor|plasma|noise|aurora|rain|comet|bouncing_ball|fireworks|sparkle_rain|lava_lamp|wave_interference")
    palette: Optional[str] = Field(None, description="Named palette (fire/ocean/lava/forest/rainbow/aurora/sunset/ice) or 'custom'")
    intensity: Optional[float] = Field(None, description="Effect intensity 0.1-2.0", ge=0.1, le=2.0)
    scale: Optional[float] = Field(None, description="Spatial scale 0.5-5.0", ge=0.5, le=5.0)
    mirror: Optional[bool] = Field(None, description="Mirror/bounce mode (meteor/comet)")
    custom_palette: Optional[List[List[float]]] = Field(None, description="Custom palette stops [[pos,R,G,B],...]")
    # gradient entity reference (effect, gradient, audio types)
    gradient_id: Optional[str] = Field(None, description="Gradient entity ID (overrides palette/inline stops)")
    # gradient-type easing
    easing: Optional[str] = Field(None, description="Gradient interpolation easing: linear|ease_in_out|step|cubic")
    # composite-type fields
    layers: Optional[List[CompositeLayer]] = Field(None, description="Layers for composite type")
    # mapped-type fields
    zones: Optional[List[MappedZone]] = Field(None, description="Zones for mapped type")
    # audio-type fields
    visualization_mode: Optional[str] = Field(None, description="Audio visualization: spectrum|beat_pulse|vu_meter")
    audio_source_id: Optional[str] = Field(None, description="Mono audio source ID (for audio type)")
    sensitivity: Optional[float] = Field(None, description="Audio sensitivity/gain 0.1-5.0", ge=0.1, le=5.0)
    color_peak: Optional[List[int]] = Field(None, description="Peak/high RGB color for VU meter [R,G,B]")
    # shared
    led_count: int = Field(default=0, description="Total LED count (0 = auto from calibration / device)", ge=0)
    description: Optional[str] = Field(None, description="Optional description", max_length=500)
    animation: Optional[AnimationConfig] = Field(None, description="Procedural animation config (static/gradient only)")
    # api_input-type fields
    fallback_color: Optional[List[int]] = Field(None, description="Fallback RGB color [R,G,B] when no data received (api_input type)")
    timeout: Optional[float] = Field(None, description="Seconds before reverting to fallback (api_input type)", ge=0.0, le=300.0)
    interpolation: Optional[str] = Field(None, description="LED count interpolation mode: none|linear|nearest (api_input type)")
    # notification-type fields
    notification_effect: Optional[str] = Field(None, description="Notification effect: flash|pulse|sweep")
    duration_ms: Optional[int] = Field(None, ge=100, le=10000, description="Effect duration in milliseconds")
    default_color: Optional[str] = Field(None, description="Default hex color (#RRGGBB) for notifications")
    app_colors: Optional[Dict[str, str]] = Field(None, description="Map of app name to hex color (#RRGGBB)")
    app_filter_mode: Optional[str] = Field(None, description="App filter mode: off|whitelist|blacklist")
    app_filter_list: Optional[List[str]] = Field(None, description="App names for filter")
    os_listener: Optional[bool] = Field(None, description="Whether to listen for OS notifications")
    sound_asset_id: Optional[str] = Field(None, description="Global notification sound asset ID")
    sound_volume: Optional[float] = Field(None, ge=0.0, le=1.0, description="Global notification sound volume")
    app_sounds: Optional[Dict[str, AppSoundOverride]] = Field(None, description="Per-app sound overrides")
    # daylight-type fields
    speed: Optional[float] = Field(None, description="Cycle/flicker speed multiplier", ge=0.1, le=10.0)
    use_real_time: Optional[bool] = Field(None, description="Use wall-clock time for daylight cycle")
    latitude: Optional[float] = Field(None, description="Latitude for daylight timing (-90 to 90)", ge=-90.0, le=90.0)
    longitude: Optional[float] = Field(None, description="Longitude for daylight timing (-180 to 180)", ge=-180.0, le=180.0)
    # candlelight-type fields
    num_candles: Optional[int] = Field(None, description="Number of independent candle sources (1-20)", ge=1, le=20)
    wind_strength: Optional[float] = Field(None, description="Wind simulation strength (0.0-2.0)", ge=0.0, le=2.0)
    candle_type: Optional[str] = Field(None, description="Candle type preset: default|taper|votive|bonfire")
    # processed-type fields
    input_source_id: Optional[str] = Field(None, description="Input color strip source ID (for processed type)")
    processing_template_id: Optional[str] = Field(None, description="Color strip processing template ID (for processed type)")
    # weather-type fields
    weather_source_id: Optional[str] = Field(None, description="Weather source entity ID (for weather type)")
    temperature_influence: Optional[float] = Field(None, description="Temperature color shift strength (0.0-1.0)", ge=0.0, le=1.0)
    # sync clock
    clock_id: Optional[str] = Field(None, description="Optional sync clock ID for synchronized animation")
    tags: List[str] = Field(default_factory=list, description="User-defined tags")


class ColorStripSourceUpdate(BaseModel):
    """Request to update a color strip source."""

    name: Optional[str] = Field(None, description="Source name", min_length=1, max_length=100)
    # picture-type fields
    picture_source_id: Optional[str] = Field(None, description="Picture source ID")
    smoothing: Optional[float] = Field(None, description="Temporal smoothing (0.0-1.0)", ge=0.0, le=1.0)
    interpolation_mode: Optional[str] = Field(None, description="Interpolation mode (average, median, dominant)")
    calibration: Optional[Calibration] = Field(None, description="LED calibration")
    # static-type fields
    color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B] (0-255 each, for static type)")
    # gradient-type fields
    stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
    # color_cycle-type fields
    colors: Optional[List[List[int]]] = Field(None, description="List of [R,G,B] colors to cycle (color_cycle type)")
    # effect-type fields
    effect_type: Optional[str] = Field(None, description="Effect algorithm")
    palette: Optional[str] = Field(None, description="Named palette")
    intensity: Optional[float] = Field(None, description="Effect intensity 0.1-2.0", ge=0.1, le=2.0)
    scale: Optional[float] = Field(None, description="Spatial scale 0.5-5.0", ge=0.5, le=5.0)
    mirror: Optional[bool] = Field(None, description="Mirror/bounce mode")
    custom_palette: Optional[List[List[float]]] = Field(None, description="Custom palette stops [[pos,R,G,B],...]")
    # gradient entity reference (effect, gradient, audio types)
    gradient_id: Optional[str] = Field(None, description="Gradient entity ID (overrides palette/inline stops)")
    # gradient-type easing
    easing: Optional[str] = Field(None, description="Gradient interpolation easing: linear|ease_in_out|step|cubic")
    # composite-type fields
    layers: Optional[List[CompositeLayer]] = Field(None, description="Layers for composite type")
    # mapped-type fields
    zones: Optional[List[MappedZone]] = Field(None, description="Zones for mapped type")
    # audio-type fields
    visualization_mode: Optional[str] = Field(None, description="Audio visualization: spectrum|beat_pulse|vu_meter")
    audio_source_id: Optional[str] = Field(None, description="Mono audio source ID (for audio type)")
    sensitivity: Optional[float] = Field(None, description="Audio sensitivity/gain 0.1-5.0", ge=0.1, le=5.0)
    color_peak: Optional[List[int]] = Field(None, description="Peak/high RGB color for VU meter [R,G,B]")
    # shared
    led_count: Optional[int] = Field(None, description="Total LED count (0 = auto from calibration / device)", ge=0)
    description: Optional[str] = Field(None, description="Optional description", max_length=500)
    animation: Optional[AnimationConfig] = Field(None, description="Procedural animation config (static/gradient only)")
    # api_input-type fields
    fallback_color: Optional[List[int]] = Field(None, description="Fallback RGB color [R,G,B] (api_input type)")
    timeout: Optional[float] = Field(None, description="Timeout before fallback (api_input type)", ge=0.0, le=300.0)
    interpolation: Optional[str] = Field(None, description="LED count interpolation mode: none|linear|nearest (api_input type)")
    # notification-type fields
    notification_effect: Optional[str] = Field(None, description="Notification effect: flash|pulse|sweep")
    duration_ms: Optional[int] = Field(None, ge=100, le=10000, description="Effect duration in milliseconds")
    default_color: Optional[str] = Field(None, description="Default hex color (#RRGGBB)")
    app_colors: Optional[Dict[str, str]] = Field(None, description="Map of app name to hex color")
    app_filter_mode: Optional[str] = Field(None, description="App filter mode: off|whitelist|blacklist")
    app_filter_list: Optional[List[str]] = Field(None, description="App names for filter")
    os_listener: Optional[bool] = Field(None, description="Whether to listen for OS notifications")
    sound_asset_id: Optional[str] = Field(None, description="Global notification sound asset ID")
    sound_volume: Optional[float] = Field(None, ge=0.0, le=1.0, description="Global notification sound volume")
    app_sounds: Optional[Dict[str, AppSoundOverride]] = Field(None, description="Per-app sound overrides")
    # daylight-type fields
    speed: Optional[float] = Field(None, description="Cycle/flicker speed multiplier", ge=0.1, le=10.0)
    use_real_time: Optional[bool] = Field(None, description="Use wall-clock time for daylight cycle")
    latitude: Optional[float] = Field(None, description="Latitude for daylight timing (-90 to 90)", ge=-90.0, le=90.0)
    longitude: Optional[float] = Field(None, description="Longitude for daylight timing (-180 to 180)", ge=-180.0, le=180.0)
    # candlelight-type fields
    num_candles: Optional[int] = Field(None, description="Number of independent candle sources (1-20)", ge=1, le=20)
    wind_strength: Optional[float] = Field(None, description="Wind simulation strength (0.0-2.0)", ge=0.0, le=2.0)
    candle_type: Optional[str] = Field(None, description="Candle type preset: default|taper|votive|bonfire")
    # processed-type fields
    input_source_id: Optional[str] = Field(None, description="Input color strip source ID (for processed type)")
    processing_template_id: Optional[str] = Field(None, description="Color strip processing template ID (for processed type)")
    # weather-type fields
    weather_source_id: Optional[str] = Field(None, description="Weather source entity ID (for weather type)")
    temperature_influence: Optional[float] = Field(None, description="Temperature color shift strength (0.0-1.0)", ge=0.0, le=1.0)
    # sync clock
    clock_id: Optional[str] = Field(None, description="Optional sync clock ID for synchronized animation")
    tags: Optional[List[str]] = None


class ColorStripSourceResponse(BaseModel):
    """Color strip source response."""

    id: str = Field(description="Source ID")
    name: str = Field(description="Source name")
    source_type: str = Field(description="Source type")
    # picture-type fields
    picture_source_id: Optional[str] = Field(None, description="Picture source ID")
    smoothing: Optional[float] = Field(None, description="Temporal smoothing")
    interpolation_mode: Optional[str] = Field(None, description="Interpolation mode")
    calibration: Optional[Calibration] = Field(None, description="LED calibration")
    # static-type fields
    color: Optional[List[int]] = Field(None, description="Static RGB color [R, G, B]")
    # gradient-type fields
    stops: Optional[List[ColorStop]] = Field(None, description="Color stops for gradient type")
    # color_cycle-type fields
    colors: Optional[List[List[int]]] = Field(None, description="List of [R,G,B] colors to cycle (color_cycle type)")
    # effect-type fields
    effect_type: Optional[str] = Field(None, description="Effect algorithm")
    palette: Optional[str] = Field(None, description="Named palette")
    intensity: Optional[float] = Field(None, description="Effect intensity")
    scale: Optional[float] = Field(None, description="Spatial scale")
    mirror: Optional[bool] = Field(None, description="Mirror/bounce mode")
    custom_palette: Optional[List[List[float]]] = Field(None, description="Custom palette stops")
    gradient_id: Optional[str] = Field(None, description="Gradient entity ID")
    # gradient-type easing
    easing: Optional[str] = Field(None, description="Gradient interpolation easing")
    # composite-type fields
    layers: Optional[List[dict]] = Field(None, description="Layers for composite type")
    # mapped-type fields
    zones: Optional[List[dict]] = Field(None, description="Zones for mapped type")
    # audio-type fields
    visualization_mode: Optional[str] = Field(None, description="Audio visualization mode")
    audio_source_id: Optional[str] = Field(None, description="Mono audio source ID")
    sensitivity: Optional[float] = Field(None, description="Audio sensitivity")
    color_peak: Optional[List[int]] = Field(None, description="Peak color [R,G,B]")
    # shared
    led_count: int = Field(0, description="Total LED count (0 = auto from calibration / device)")
    description: Optional[str] = Field(None, description="Description")
    animation: Optional[AnimationConfig] = Field(None, description="Procedural animation config (static/gradient only)")
    # api_input-type fields
    fallback_color: Optional[List[int]] = Field(None, description="Fallback RGB color [R,G,B] (api_input type)")
    timeout: Optional[float] = Field(None, description="Timeout before fallback (api_input type)")
    interpolation: Optional[str] = Field(None, description="LED count interpolation mode: none|linear|nearest (api_input type)")
    # notification-type fields
    notification_effect: Optional[str] = Field(None, description="Notification effect: flash|pulse|sweep")
    duration_ms: Optional[int] = Field(None, description="Effect duration in milliseconds")
    default_color: Optional[str] = Field(None, description="Default hex color (#RRGGBB)")
    app_colors: Optional[Dict[str, str]] = Field(None, description="Map of app name to hex color")
    app_filter_mode: Optional[str] = Field(None, description="App filter mode: off|whitelist|blacklist")
    app_filter_list: Optional[List[str]] = Field(None, description="App names for filter")
    os_listener: Optional[bool] = Field(None, description="Whether to listen for OS notifications")
    sound_asset_id: Optional[str] = Field(None, description="Global notification sound asset ID")
    sound_volume: Optional[float] = Field(None, description="Global notification sound volume")
    app_sounds: Optional[Dict[str, dict]] = Field(None, description="Per-app sound overrides")
    # daylight-type fields
    speed: Optional[float] = Field(None, description="Cycle/flicker speed multiplier")
    use_real_time: Optional[bool] = Field(None, description="Use wall-clock time for daylight cycle")
    latitude: Optional[float] = Field(None, description="Latitude for daylight timing")
    longitude: Optional[float] = Field(None, description="Longitude for daylight timing")
    # candlelight-type fields
    num_candles: Optional[int] = Field(None, description="Number of independent candle sources")
    wind_strength: Optional[float] = Field(None, description="Wind simulation strength")
    candle_type: Optional[str] = Field(None, description="Candle type preset")
    # processed-type fields
    input_source_id: Optional[str] = Field(None, description="Input color strip source ID")
    processing_template_id: Optional[str] = Field(None, description="Color strip processing template ID")
    # weather-type fields
    weather_source_id: Optional[str] = Field(None, description="Weather source entity ID")
    temperature_influence: Optional[float] = Field(None, description="Temperature color shift strength")
    # sync clock
    clock_id: Optional[str] = Field(None, description="Optional sync clock ID for synchronized animation")
    tags: List[str] = Field(default_factory=list, description="User-defined tags")
    overlay_active: bool = Field(False, description="Whether the screen overlay is currently active")
    created_at: datetime = Field(description="Creation timestamp")
    updated_at: datetime = Field(description="Last update timestamp")


class ColorStripSourceListResponse(BaseModel):
    """List of color strip sources."""

    sources: List[ColorStripSourceResponse] = Field(description="List of color strip sources")
    count: int = Field(description="Number of sources")


class SegmentPayload(BaseModel):
    """A single segment for segment-based LED color updates."""

    start: int = Field(ge=0, description="Starting LED index")
    length: int = Field(ge=1, description="Number of LEDs in segment")
    mode: Literal["solid", "per_pixel", "gradient"] = Field(description="Fill mode")
    color: Optional[List[int]] = Field(None, description="RGB for solid mode [R,G,B]")
    colors: Optional[List[List[int]]] = Field(None, description="Colors for per_pixel/gradient [[R,G,B],...]")

    @model_validator(mode="after")
    def _validate_mode_fields(self) -> "SegmentPayload":
        if self.mode == "solid":
            if self.color is None or len(self.color) != 3:
                raise ValueError("solid mode requires 'color' as a list of 3 ints [R,G,B]")
            if not all(0 <= c <= 255 for c in self.color):
                raise ValueError("solid color values must be 0-255")
        elif self.mode == "per_pixel":
            if not self.colors:
                raise ValueError("per_pixel mode requires non-empty 'colors' list")
            for c in self.colors:
                if len(c) != 3:
                    raise ValueError("each color in per_pixel must be [R,G,B]")
        elif self.mode == "gradient":
            if not self.colors or len(self.colors) < 2:
                raise ValueError("gradient mode requires 'colors' with at least 2 stops")
            for c in self.colors:
                if len(c) != 3:
                    raise ValueError("each color stop in gradient must be [R,G,B]")
        return self


class ColorPushRequest(BaseModel):
    """Request to push raw LED colors to an api_input source.

    Accepts either 'colors' (legacy flat array) or 'segments' (new segment-based).
    At least one must be provided.
    """

    colors: Optional[List[List[int]]] = Field(None, description="LED color array [[R,G,B], ...] (0-255 each)")
    segments: Optional[List[SegmentPayload]] = Field(None, description="Segment-based color updates")

    @model_validator(mode="after")
    def _require_colors_or_segments(self) -> "ColorPushRequest":
        if self.colors is None and self.segments is None:
            raise ValueError("Either 'colors' or 'segments' must be provided")
        return self


class NotifyRequest(BaseModel):
    """Request to trigger a notification on a notification color strip source."""

    app: Optional[str] = Field(None, description="App name for color lookup")
    color: Optional[str] = Field(None, description="Hex color override (#RRGGBB)")


class CSSCalibrationTestRequest(BaseModel):
    """Request to run a calibration test for a color strip source on a specific device."""

    device_id: str = Field(description="Device ID to send test pixels to")
    edges: dict = Field(
        default_factory=lambda: {
            "top": [255, 0, 0],
            "right": [0, 255, 0],
            "bottom": [0, 100, 255],
            "left": [255, 255, 0],
        },
        description="Map of edge names to RGB colors. Empty dict = exit test mode.",
    )
