"""Processed color strip stream — wraps another CSS and applies a CSPT filter chain."""

import threading
import time
from typing import Optional

import numpy as np

from wled_controller.core.processing.color_strip_stream import ColorStripStream
from wled_controller.core.filters import FilterRegistry
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class ProcessedColorStripStream(ColorStripStream):
    """Color strip stream that wraps an input CSS and applies CSPT filters.

    Acquires the input stream from the manager, reads its colors, applies
    the filter chain from the referenced processing template, and caches
    the result.
    """

    def __init__(self, source, css_manager, cspt_store=None):
        self._source = source
        self._css_manager = css_manager
        self._cspt_store = cspt_store
        self._input_stream: Optional[ColorStripStream] = None
        self._consumer_id = f"__processed_{source.id}__"
        self._filters = []
        self._cached_template_id = None
        self._running = False
        self._thread: Optional[threading.Thread] = None
        self._colors: Optional[np.ndarray] = None
        self._colors_lock = threading.Lock()
        self._led_count = 0
        self._auto_size = True
        self._fps = 30
        self._frame_time = 1.0 / 30
        self._resolve_count = 0

    @property
    def target_fps(self) -> int:
        return self._fps

    @property
    def led_count(self) -> int:
        if self._input_stream:
            return self._input_stream.led_count
        return self._led_count or 1

    @property
    def is_animated(self) -> bool:
        if self._input_stream:
            return self._input_stream.is_animated
        return True

    def configure(self, device_led_count: int) -> None:
        self._led_count = device_led_count
        self._auto_size = True
        if self._input_stream and hasattr(self._input_stream, 'configure'):
            self._input_stream.configure(device_led_count)

    def start(self) -> None:
        if self._running:
            return
        # Acquire input stream
        input_id = self._source.input_source_id
        if not input_id:
            raise ValueError(f"Processed source {self._source.id} has no input_source_id")
        self._input_stream = self._css_manager.acquire(input_id, self._consumer_id)
        # Resolve initial filter chain
        self._resolve_filters()
        self._running = True
        self._thread = threading.Thread(
            target=self._processing_loop,
            name=f"css-processed-{self._source.id[:8]}",
            daemon=True,
        )
        self._thread.start()
        logger.info(f"ProcessedColorStripStream started for {self._source.id}")

    def stop(self) -> None:
        self._running = False
        if self._thread:
            self._thread.join(timeout=5.0)
            self._thread = None
        # Release input stream
        if self._input_stream:
            input_id = self._source.input_source_id
            self._css_manager.release(input_id, self._consumer_id)
            self._input_stream = None
        logger.info(f"ProcessedColorStripStream stopped for {self._source.id}")

    def get_latest_colors(self) -> Optional[np.ndarray]:
        with self._colors_lock:
            return self._colors

    def update_source(self, source) -> None:
        self._source = source
        # Force re-resolve filters on next iteration
        self._cached_template_id = None

    def set_clock(self, clock_runtime) -> None:
        if self._input_stream and hasattr(self._input_stream, 'set_clock'):
            self._input_stream.set_clock(clock_runtime)

    def _resolve_filters(self) -> None:
        """Resolve the CSPT filter chain from the template."""
        template_id = self._source.processing_template_id
        if template_id == self._cached_template_id:
            return
        self._cached_template_id = template_id
        self._filters = []
        if not template_id or not self._cspt_store:
            return
        try:
            template = self._cspt_store.get_template(template_id)
            resolved = self._cspt_store.resolve_filter_instances(template.filters)
            self._filters = [
                FilterRegistry.create_instance(fi.filter_id, fi.options)
                for fi in resolved
            ]
        except Exception as e:
            logger.warning(f"Failed to resolve CSPT {template_id}: {e}")
            self._filters = []

    def _processing_loop(self) -> None:
        """Main loop: read input, apply filters, cache result."""
        while self._running:
            t0 = time.monotonic()

            # Periodically re-resolve filters (every 30 iterations)
            self._resolve_count += 1
            if self._resolve_count >= 30:
                self._resolve_count = 0
                self._resolve_filters()

            colors = None
            if self._input_stream:
                colors = self._input_stream.get_latest_colors()

            if colors is not None and self._filters:
                for flt in self._filters:
                    try:
                        result = flt.process_strip(colors)
                        if result is not None:
                            colors = result
                    except Exception as e:
                        logger.warning(f"Filter error in processed stream: {e}")

            if colors is not None:
                with self._colors_lock:
                    self._colors = colors

            elapsed = time.monotonic() - t0
            sleep_time = self._frame_time - elapsed
            if sleep_time > 0:
                time.sleep(sleep_time)
