"""Abstract base class for target processors.

A TargetProcessor encapsulates the processing loop and state for a single
output target. Concrete subclasses (WledTargetProcessor, KCTargetProcessor)
implement the target-specific capture→process→output pipeline.

ProcessorManager creates and owns TargetProcessor instances, delegating
all target-specific operations through the uniform interface defined here.
"""

from __future__ import annotations

import asyncio
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple

if TYPE_CHECKING:
    from wled_controller.core.processing.color_strip_stream_manager import ColorStripStreamManager
    from wled_controller.core.processing.live_stream_manager import LiveStreamManager
    from wled_controller.core.processing.value_stream import ValueStreamManager
    from wled_controller.core.capture.screen_overlay import OverlayManager
    from wled_controller.storage import DeviceStore
    from wled_controller.storage.picture_source_store import PictureSourceStore
    from wled_controller.storage.template_store import TemplateStore
    from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore
    from wled_controller.storage.pattern_template_store import PatternTemplateStore
    from wled_controller.storage.color_strip_processing_template_store import (
        ColorStripProcessingTemplateStore,
    )


# ---------------------------------------------------------------------------
# Shared dataclasses
# ---------------------------------------------------------------------------


@dataclass
class ProcessingMetrics:
    """Metrics for processing performance."""

    frames_processed: int = 0
    frames_skipped: int = 0
    frames_keepalive: int = 0
    errors_count: int = 0
    last_error: Optional[str] = None
    last_update: Optional[datetime] = None
    last_update_mono: float = (
        0.0  # monotonic timestamp for hot-path; lazily converted to last_update on read
    )
    start_time: Optional[datetime] = None
    fps_actual: float = 0.0
    fps_potential: float = 0.0
    fps_current: int = 0
    # Per-stage timing (ms), averaged over last 10 frames
    # LED targets
    timing_extract_ms: float = 0.0
    timing_map_leds_ms: float = 0.0
    timing_smooth_ms: float = 0.0
    timing_send_ms: float = 0.0
    timing_total_ms: float = 0.0
    # KC targets
    timing_calc_colors_ms: float = 0.0
    timing_broadcast_ms: float = 0.0
    # Streaming liveness (HTTP probe during DDP)
    device_streaming_reachable: Optional[bool] = None
    fps_effective: int = 0


@dataclass
class DeviceInfo:
    """Read-only snapshot of device state, passed to target processors."""

    device_id: str
    device_url: str
    led_count: int
    device_type: str = "wled"
    baud_rate: Optional[int] = None
    software_brightness: int = 255
    test_mode_active: bool = False
    send_latency_ms: int = 0
    rgbw: bool = False
    zone_mode: str = "combined"
    auto_shutdown: bool = False
    # DMX (Art-Net / sACN) fields
    dmx_protocol: str = "artnet"
    dmx_start_universe: int = 0
    dmx_start_channel: int = 1
    # ESP-NOW fields
    espnow_peer_mac: str = ""
    espnow_channel: int = 1
    # Philips Hue fields
    hue_username: str = ""
    hue_client_key: str = ""
    hue_entertainment_group_id: str = ""
    # SPI Direct fields
    spi_speed_hz: int = 800000
    spi_led_type: str = "WS2812B"
    # Razer Chroma fields
    chroma_device_type: str = "chromalink"
    # SteelSeries GameSense fields
    gamesense_device_type: str = "keyboard"


@dataclass
class TargetContext:
    """Shared infrastructure bag passed to every TargetProcessor.

    Avoids circular imports — processors never import ProcessorManager.
    """

    live_stream_manager: "LiveStreamManager"
    overlay_manager: "OverlayManager"
    picture_source_store: Optional["PictureSourceStore"] = None
    capture_template_store: Optional["TemplateStore"] = None
    pp_template_store: Optional["PostprocessingTemplateStore"] = None
    pattern_template_store: Optional["PatternTemplateStore"] = None
    device_store: Optional["DeviceStore"] = None
    color_strip_stream_manager: Optional["ColorStripStreamManager"] = None
    value_stream_manager: Optional["ValueStreamManager"] = None
    cspt_store: Optional["ColorStripProcessingTemplateStore"] = None
    fire_event: Callable[[dict], None] = lambda e: None
    get_device_info: Callable[[str], Optional[DeviceInfo]] = lambda _: None
    ha_manager: Optional[Any] = None  # HomeAssistantManager (avoid circular import)


# ---------------------------------------------------------------------------
# Abstract base class
# ---------------------------------------------------------------------------


class TargetProcessor(ABC):
    """Abstract base class for target processors.

    Lifecycle: register → start → (running loop) → stop → unregister
    """

    def __init__(self, target_id: str, ctx: TargetContext, picture_source_id: str = ""):
        self._target_id = target_id
        self._picture_source_id = picture_source_id
        self._ctx = ctx
        self._is_running = False
        self._task: Optional[asyncio.Task] = None
        self._metrics = ProcessingMetrics()

    # ----- Properties -----

    @property
    def target_id(self) -> str:
        return self._target_id

    @property
    def picture_source_id(self) -> str:
        return self._picture_source_id

    @property
    def is_running(self) -> bool:
        return self._is_running

    @property
    def metrics(self) -> ProcessingMetrics:
        return self._metrics

    # ----- Lifecycle (concrete helpers + abstract hook) -----

    @abstractmethod
    async def start(self) -> None:
        """Start the processing loop.

        Implementations should acquire resources, reset metrics,
        create the asyncio task, and set _is_running = True.
        """
        ...

    @abstractmethod
    async def stop(self) -> None:
        """Stop the processing loop and release resources.

        Implementations should set _is_running = False, cancel the task,
        release the live stream, and close any connections.
        """
        ...

    # ----- Settings -----

    @abstractmethod
    def update_settings(self, settings) -> None:
        """Update processing settings (type depends on subclass)."""
        ...

    def update_source(self, picture_source_id: str) -> None:
        """Update the picture source ID."""
        self._picture_source_id = picture_source_id

    def update_device(self, device_id: str) -> None:
        """Update device association. Raises for targets without devices."""
        raise ValueError(f"Target {self._target_id} does not support device assignment")

    def update_css_source(self, color_strip_source_id: str) -> None:
        """Update the color strip source. No-op for targets that don't use CSS."""
        pass

    # ----- Device / display info (overridden by device-aware subclasses) -----

    @property
    def device_id(self) -> Optional[str]:
        """Device ID this processor streams to, or None."""
        return None

    @property
    def led_client(self):
        """Active LED client, or None."""
        return None

    def get_display_index(self) -> Optional[int]:
        """Display index being captured, or None."""
        return None

    # ----- State / Metrics reporting -----

    @abstractmethod
    def get_state(self) -> dict:
        """Return current processing state as a JSON-serializable dict."""
        ...

    @abstractmethod
    def get_metrics(self) -> dict:
        """Return processing metrics as a JSON-serializable dict."""
        ...

    # ----- Optional capabilities (default no-ops) -----

    def supports_overlay(self) -> bool:
        """Whether this target supports screen overlay visualization."""
        return False

    async def start_overlay(self, target_name: Optional[str] = None) -> None:
        """Start overlay visualization (if supported)."""
        raise NotImplementedError(f"{type(self).__name__} does not support overlays")

    async def stop_overlay(self) -> None:
        """Stop overlay visualization (if supported)."""
        raise NotImplementedError(f"{type(self).__name__} does not support overlays")

    def is_overlay_active(self) -> bool:
        """Check if overlay is currently active."""
        return False

    def supports_websocket(self) -> bool:
        """Whether this target supports WebSocket color streaming."""
        return False

    def add_ws_client(self, ws) -> None:
        """Add a WebSocket client for live color updates."""
        raise NotImplementedError(f"{type(self).__name__} does not support WebSockets")

    def remove_ws_client(self, ws) -> None:
        """Remove a WebSocket client."""
        raise NotImplementedError(f"{type(self).__name__} does not support WebSockets")

    def add_led_preview_client(self, ws) -> None:
        """Add a WebSocket client for live LED strip preview."""
        raise NotImplementedError(f"{type(self).__name__} does not support LED preview")

    def remove_led_preview_client(self, ws) -> None:
        """Remove a LED preview WebSocket client."""
        raise NotImplementedError(f"{type(self).__name__} does not support LED preview")

    def get_latest_colors(self) -> Dict[str, Tuple[int, int, int]]:
        """Get latest extracted colors (KC targets only)."""
        return {}
