"""Shared live stream management with reference counting.

LiveStreamManager creates LiveStream instances from PictureSource configs
and shares them across multiple consumers (devices). When multiple devices
reference the same PictureSource, they share a single LiveStream instance.

Reference counting ensures streams are cleaned up when the last consumer
releases them.
"""

from dataclasses import dataclass
from typing import Dict, Optional

import numpy as np

from wled_controller.core.capture_engines import EngineRegistry
from wled_controller.core.filters import FilterRegistry
from wled_controller.core.processing.live_stream import (
    LiveStream,
    ProcessedLiveStream,
    ScreenCaptureLiveStream,
    StaticImageLiveStream,
)
try:
    from wled_controller.core.processing.video_stream import VideoCaptureLiveStream
    _has_video = True
except ImportError:
    _has_video = False
from wled_controller.utils import get_logger

logger = get_logger(__name__)


@dataclass
class _LiveStreamEntry:
    """Internal tracking entry for a managed live stream."""

    live_stream: LiveStream
    ref_count: int
    # For ProcessedLiveStream: the source stream ID whose live stream we depend on.
    # Used to recursively release the source when this stream's ref count hits 0.
    source_stream_id: Optional[str] = None


class LiveStreamManager:
    """Manages shared LiveStream instances with reference counting.

    Multiple devices using the same PictureSource share a single LiveStream.
    Streams are created on first acquire and cleaned up when the last
    consumer releases.

    For ProcessedPictureSources, the source stream is recursively acquired,
    enabling sharing at every level of the stream chain.
    """

    def __init__(self, picture_source_store, capture_template_store=None, pp_template_store=None, asset_store=None):
        """Initialize the live stream manager.

        Args:
            picture_source_store: PictureSourceStore for resolving stream configs
            capture_template_store: TemplateStore for resolving capture engine settings
            pp_template_store: PostprocessingTemplateStore for resolving filter chains
            asset_store: AssetStore for resolving asset IDs to file paths
        """
        self._picture_source_store = picture_source_store
        self._capture_template_store = capture_template_store
        self._pp_template_store = pp_template_store
        self._asset_store = asset_store
        self._streams: Dict[str, _LiveStreamEntry] = {}

    def acquire(self, picture_source_id: str) -> LiveStream:
        """Get or create a LiveStream for the given PictureSource config.

        If a LiveStream already exists for this picture_source_id, increments
        the reference count and returns the existing instance.

        Otherwise, creates a new LiveStream from the PictureSource config,
        starts it, and stores it with ref_count=1.

        Args:
            picture_source_id: ID of the PictureSource config

        Returns:
            LiveStream instance (shared if already exists)

        Raises:
            ValueError: If PictureSource not found or config invalid
            RuntimeError: If stream creation/start fails
        """
        if picture_source_id in self._streams:
            entry = self._streams[picture_source_id]
            entry.ref_count += 1
            logger.info(
                f"Reusing live stream for picture source {picture_source_id} "
                f"(ref_count={entry.ref_count})"
            )
            return entry.live_stream

        # Create new live stream from config
        live_stream, source_stream_id = self._create_live_stream(picture_source_id)

        try:
            live_stream.start()
        except Exception as e:
            # If start fails, release any source dependency we acquired
            if source_stream_id:
                self.release(source_stream_id)
            raise RuntimeError(
                f"Failed to start live stream for picture source {picture_source_id}: {e}"
            )

        self._streams[picture_source_id] = _LiveStreamEntry(
            live_stream=live_stream,
            ref_count=1,
            source_stream_id=source_stream_id,
        )

        logger.info(f"Created live stream for picture source {picture_source_id}")
        return live_stream

    def release(self, picture_source_id: str) -> None:
        """Release a reference to a LiveStream.

        Decrements the reference count. When it reaches 0, stops the
        LiveStream and removes it from the registry.

        For ProcessedLiveStreams, recursively releases the source dependency.

        Args:
            picture_source_id: ID of the PictureSource to release
        """
        entry = self._streams.get(picture_source_id)
        if not entry:
            logger.warning(f"Attempted to release unknown live stream: {picture_source_id}")
            return

        entry.ref_count -= 1
        logger.debug(
            f"Released live stream {picture_source_id} (ref_count={entry.ref_count})"
        )

        if entry.ref_count <= 0:
            # Stop and remove
            try:
                entry.live_stream.stop()
            except Exception as e:
                logger.error(f"Error stopping live stream {picture_source_id}: {e}")

            source_stream_id = entry.source_stream_id
            del self._streams[picture_source_id]
            logger.info(f"Removed live stream for picture source {picture_source_id}")

            # Recursively release source dependency
            if source_stream_id:
                self.release(source_stream_id)

    def release_all(self) -> None:
        """Stop and remove all managed live streams. Safety net for shutdown."""
        stream_ids = list(self._streams.keys())
        for stream_id in stream_ids:
            entry = self._streams.get(stream_id)
            if entry:
                try:
                    entry.live_stream.stop()
                except Exception as e:
                    logger.error(f"Error stopping live stream {stream_id}: {e}")
        self._streams.clear()
        logger.info("Released all managed live streams")

    def get_active_stream_ids(self) -> list:
        """Get list of currently active stream IDs (for diagnostics)."""
        return [
            {"id": sid, "ref_count": entry.ref_count}
            for sid, entry in self._streams.items()
        ]

    def _create_live_stream(self, picture_source_id: str) -> tuple:
        """Create a LiveStream from a PictureSource config.

        Returns:
            Tuple of (LiveStream, source_stream_id or None)
        """
        from wled_controller.storage.picture_source import (
            ProcessedPictureSource,
            ScreenCapturePictureSource,
            StaticImagePictureSource,
            VideoCaptureSource,
        )

        stream_config = self._picture_source_store.get_stream(picture_source_id)

        if isinstance(stream_config, ScreenCapturePictureSource):
            return self._create_screen_capture_live_stream(stream_config), None

        elif isinstance(stream_config, ProcessedPictureSource):
            return self._create_processed_live_stream(stream_config)

        elif isinstance(stream_config, StaticImagePictureSource):
            return self._create_static_image_live_stream(stream_config), None

        elif isinstance(stream_config, VideoCaptureSource):
            return self._create_video_live_stream(stream_config), None

        else:
            raise ValueError(f"Unknown picture source type: {type(stream_config)}")

    def _create_screen_capture_live_stream(self, config) -> ScreenCaptureLiveStream:
        """Create a ScreenCaptureLiveStream from a ScreenCapturePictureSource config."""
        # Resolve capture engine from template
        engine_type = "mss"
        engine_config = {}

        if config.capture_template_id and self._capture_template_store:
            try:
                tpl = self._capture_template_store.get_template(config.capture_template_id)
                engine_type = tpl.engine_type
                engine_config = tpl.engine_config
            except ValueError:
                logger.warning(
                    f"Capture template {config.capture_template_id} not found, using MSS fallback"
                )

        capture_stream = EngineRegistry.create_stream(
            engine_type, config.display_index, engine_config
        )

        return ScreenCaptureLiveStream(capture_stream, config.target_fps)

    def _create_processed_live_stream(self, config) -> tuple:
        """Create a ProcessedLiveStream from a ProcessedPictureSource config.

        Returns:
            Tuple of (ProcessedLiveStream, source_stream_id)
        """
        # Recursively acquire source stream (with ref counting)
        source_stream_id = config.source_stream_id
        source_live = self.acquire(source_stream_id)

        # Resolve postprocessing filters (recursively expanding filter_template refs)
        filters = []
        if config.postprocessing_template_id and self._pp_template_store:
            try:
                pp = self._pp_template_store.get_template(config.postprocessing_template_id)
                filters = self._resolve_filters(pp.filters)
            except ValueError:
                logger.warning(
                    f"PP template {config.postprocessing_template_id} not found, no filters applied"
                )

        return ProcessedLiveStream(source_live, filters), source_stream_id

    def _resolve_filters(self, filter_instances):
        """Resolve filter instances into instantiated PostprocessingFilter objects.

        Expands filter_template references via the store, then creates instances.
        """
        if self._pp_template_store:
            flat = self._pp_template_store.resolve_filter_instances(filter_instances)
        else:
            flat = [fi for fi in filter_instances if fi.filter_id != "filter_template"]

        resolved = []
        for fi in flat:
            try:
                resolved.append(
                    FilterRegistry.create_instance(fi.filter_id, fi.options)
                )
            except ValueError as e:
                logger.warning(f"Skipping unknown filter '{fi.filter_id}': {e}")
        return resolved

    def _resolve_asset_path(self, asset_id: str | None, label: str) -> str:
        """Resolve an asset ID to its on-disk file path string.

        Raises:
            ValueError: If asset not found, deleted, or file missing.
        """
        if not asset_id:
            raise ValueError(f"{label} has no asset ID configured")
        if not self._asset_store:
            raise ValueError(f"AssetStore not available to resolve {label}")
        path = self._asset_store.get_file_path(asset_id)
        if not path:
            raise ValueError(f"Asset {asset_id} not found or file missing for {label}")
        return str(path)

    def _create_video_live_stream(self, config):
        """Create a VideoCaptureLiveStream from a VideoCaptureSource config."""
        if not _has_video:
            raise ImportError(
                "OpenCV is required for video stream support. "
                "Install it with: pip install opencv-python-headless"
            )
        video_path = self._resolve_asset_path(config.video_asset_id, "video source")
        stream = VideoCaptureLiveStream(
            url=video_path,
            loop=config.loop,
            playback_speed=config.playback_speed,
            start_time=config.start_time,
            end_time=config.end_time,
            resolution_limit=config.resolution_limit,
            target_fps=config.target_fps,
        )

        # Attach sync clock if configured
        if config.clock_id:
            try:
                from wled_controller.core.processing.processor_manager import ProcessorManager
                manager = ProcessorManager.instance()
                if manager and hasattr(manager, '_sync_clock_manager'):
                    clock = manager._sync_clock_manager.acquire(config.clock_id)
                    stream.set_clock(clock)
            except Exception as e:
                logger.warning(f"Could not attach clock {config.clock_id} to video stream: {e}")

        return stream

    def _create_static_image_live_stream(self, config) -> StaticImageLiveStream:
        """Create a StaticImageLiveStream from a StaticImagePictureSource config."""
        image_path = self._resolve_asset_path(config.image_asset_id, "static image source")
        image = self._load_static_image(image_path)
        return StaticImageLiveStream(image)

    @staticmethod
    def _load_static_image(file_path: str) -> np.ndarray:
        """Load a static image from a local file path, return as RGB numpy array."""
        from pathlib import Path

        from wled_controller.utils.image_codec import load_image_file

        path = Path(file_path)
        if not path.exists():
            raise FileNotFoundError(f"Image file not found: {file_path}")
        return load_image_file(path)
