"""Output target storage using SQLite."""

import uuid
from datetime import datetime, timezone
from typing import Any, List, Optional

from ledgrab.storage.base_sqlite_store import BaseSqliteStore
from ledgrab.storage.bindable import BindableFloat
from ledgrab.storage.database import Database
from ledgrab.storage.output_target import OutputTarget
from ledgrab.storage.wled_output_target import WledOutputTarget
from ledgrab.storage.ha_light_output_target import (
    HALightMapping,
    HALightOutputTarget,
)
from ledgrab.storage.z2m_light_output_target import (
    DEFAULT_Z2M_BASE_TOPIC,
    Z2MLightMapping,
    Z2MLightOutputTarget,
)
from ledgrab.utils import get_logger

logger = get_logger(__name__)

DEFAULT_STATE_CHECK_INTERVAL = 30  # seconds

_VALID_HA_SOURCE_KINDS = ("css", "color_vs")
_VALID_HA_STOP_ACTIONS = ("none", "turn_off", "restore")
_VALID_Z2M_STOP_ACTIONS = ("none", "turn_off")


class OutputTargetStore(BaseSqliteStore[OutputTarget]):
    """Persistent storage for output targets."""

    _table_name = "output_targets"
    _entity_name = "Output target"

    def __init__(self, db: Database):
        super().__init__(db, OutputTarget.from_dict)

    # Backward-compatible aliases
    get_all_targets = BaseSqliteStore.get_all
    get_target = BaseSqliteStore.get
    delete_target = BaseSqliteStore.delete

    # ---- shared helpers -------------------------------------------------

    def _check_unique_name(self, name: str) -> None:
        for target in self._items.values():
            if target.name == name:
                raise ValueError(f"Output target with name '{name}' already exists")

    def _new_id_and_now(self) -> tuple[str, datetime]:
        return f"pt_{uuid.uuid4().hex[:8]}", datetime.now(timezone.utc)

    @staticmethod
    def _resolve_brightness(brightness: Any, brightness_value_source_id: str = "") -> BindableFloat:
        if isinstance(brightness, BindableFloat):
            return brightness
        if brightness is not None:
            return BindableFloat.from_raw(brightness, default=1.0)
        if brightness_value_source_id:
            return BindableFloat(1.0, source_id=brightness_value_source_id)
        return BindableFloat(1.0)

    @staticmethod
    def _resolve_transition(transition: Any, default: float) -> BindableFloat:
        if isinstance(transition, BindableFloat):
            return transition
        if transition is not None:
            return BindableFloat.from_raw(transition, default=default)
        return BindableFloat(default)

    def _finalize(
        self, target: OutputTarget, *, tags: Optional[List[str]], log_type: str
    ) -> OutputTarget:
        target.tags = tags or []
        self._items[target.id] = target
        self._save_item(target.id, target)
        logger.info(f"Created output target: {target.name} ({target.id}, type={log_type})")
        return target

    # ---- typed factory methods -----------------------------------------

    def create_wled_target(
        self,
        name: str,
        *,
        device_id: str = "",
        color_strip_source_id: str = "",
        brightness: Any = None,
        fps: Any = 30,
        keepalive_interval: float = 1.0,
        state_check_interval: int = DEFAULT_STATE_CHECK_INTERVAL,
        min_brightness_threshold: Any = 0,
        adaptive_fps: bool = False,
        protocol: str = "ddp",
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        # legacy compat
        brightness_value_source_id: str = "",
    ) -> WledOutputTarget:
        """Create a WLED/LED output target."""
        self._check_unique_name(name)
        target_id, now = self._new_id_and_now()
        target = WledOutputTarget(
            id=target_id,
            name=name,
            target_type="led",
            device_id=device_id,
            color_strip_source_id=color_strip_source_id,
            brightness=self._resolve_brightness(brightness, brightness_value_source_id),
            fps=BindableFloat.from_raw(fps, default=30.0),
            keepalive_interval=keepalive_interval,
            state_check_interval=state_check_interval,
            min_brightness_threshold=BindableFloat.from_raw(min_brightness_threshold, default=0.0),
            adaptive_fps=adaptive_fps,
            protocol=protocol,
            description=description,
            created_at=now,
            updated_at=now,
        )
        return self._finalize(target, tags=tags, log_type="led")

    def create_ha_light_target(
        self,
        name: str,
        *,
        ha_source_id: str = "",
        source_kind: str = "css",
        color_strip_source_id: str = "",
        color_value_source_id: str = "",
        brightness: Any = None,
        ha_light_mappings: Optional[List[HALightMapping]] = None,
        update_rate: Any = 2.0,
        transition: Any = None,
        min_brightness_threshold: Any = 0,
        color_tolerance: Any = 5,
        stop_action: str = "none",
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        # legacy compat
        brightness_value_source_id: str = "",
    ) -> HALightOutputTarget:
        """Create a Home Assistant light output target."""
        self._check_unique_name(name)
        target_id, now = self._new_id_and_now()
        target = HALightOutputTarget(
            id=target_id,
            name=name,
            target_type="ha_light",
            ha_source_id=ha_source_id,
            source_kind=source_kind if source_kind in _VALID_HA_SOURCE_KINDS else "css",
            color_strip_source_id=color_strip_source_id,
            color_value_source_id=color_value_source_id,
            brightness=self._resolve_brightness(brightness, brightness_value_source_id),
            light_mappings=ha_light_mappings or [],
            update_rate=BindableFloat.from_raw(update_rate, default=2.0),
            transition=self._resolve_transition(transition, default=0.5),
            min_brightness_threshold=BindableFloat.from_raw(min_brightness_threshold, default=0.0),
            color_tolerance=BindableFloat.from_raw(color_tolerance, default=5.0),
            stop_action=stop_action if stop_action in _VALID_HA_STOP_ACTIONS else "none",
            description=description,
            created_at=now,
            updated_at=now,
        )
        return self._finalize(target, tags=tags, log_type="ha_light")

    def create_z2m_light_target(
        self,
        name: str,
        *,
        mqtt_source_id: str = "",
        source_kind: str = "css",
        color_strip_source_id: str = "",
        color_value_source_id: str = "",
        brightness: Any = None,
        z2m_light_mappings: Optional[List[Z2MLightMapping]] = None,
        base_topic: str = DEFAULT_Z2M_BASE_TOPIC,
        update_rate: Any = 5.0,
        transition: Any = None,
        min_brightness_threshold: Any = 0,
        color_tolerance: Any = 5,
        stop_action: str = "none",
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        # legacy compat
        brightness_value_source_id: str = "",
    ) -> Z2MLightOutputTarget:
        """Create a Zigbee2MQTT light output target."""
        self._check_unique_name(name)
        target_id, now = self._new_id_and_now()
        target = Z2MLightOutputTarget(
            id=target_id,
            name=name,
            target_type="z2m_light",
            source_kind=source_kind if source_kind in _VALID_HA_SOURCE_KINDS else "css",
            color_strip_source_id=color_strip_source_id,
            color_value_source_id=color_value_source_id,
            brightness=self._resolve_brightness(brightness, brightness_value_source_id),
            light_mappings=z2m_light_mappings or [],
            mqtt_source_id=mqtt_source_id,
            base_topic=(base_topic or "").strip() or DEFAULT_Z2M_BASE_TOPIC,
            update_rate=BindableFloat.from_raw(update_rate, default=5.0),
            transition=self._resolve_transition(transition, default=0.3),
            min_brightness_threshold=BindableFloat.from_raw(min_brightness_threshold, default=0.0),
            color_tolerance=BindableFloat.from_raw(color_tolerance, default=5.0),
            stop_action=stop_action if stop_action in _VALID_Z2M_STOP_ACTIONS else "none",
            description=description,
            created_at=now,
            updated_at=now,
        )
        return self._finalize(target, tags=tags, log_type="z2m_light")

    # ---- legacy generic shim (deferred deletion in Phase 5) ------------

    def create_target(
        self,
        name: str,
        target_type: str,
        device_id: str = "",
        color_strip_source_id: str = "",
        brightness=None,
        fps: int = 30,
        keepalive_interval: float = 1.0,
        state_check_interval: int = DEFAULT_STATE_CHECK_INTERVAL,
        min_brightness_threshold: int = 0,
        adaptive_fps: bool = False,
        protocol: str = "ddp",
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        ha_source_id: str = "",
        source_kind: str = "css",
        color_value_source_id: str = "",
        ha_light_mappings: Optional[List[HALightMapping]] = None,
        update_rate: Optional[float] = None,
        transition=None,
        color_tolerance: int = 5,
        stop_action: str = "none",
        z2m_light_mappings: Optional[List[Z2MLightMapping]] = None,
        base_topic: str = DEFAULT_Z2M_BASE_TOPIC,
        mqtt_source_id: str = "",
        brightness_value_source_id: str = "",
    ) -> OutputTarget:
        """Generic create that dispatches to the typed factory by ``target_type``.

        Kept as a thin shim during the refactor — new code should call
        :meth:`create_wled_target`, :meth:`create_ha_light_target`, or
        :meth:`create_z2m_light_target` directly.
        """
        if target_type == "led":
            return self.create_wled_target(
                name=name,
                device_id=device_id,
                color_strip_source_id=color_strip_source_id,
                brightness=brightness,
                fps=fps,
                keepalive_interval=keepalive_interval,
                state_check_interval=state_check_interval,
                min_brightness_threshold=min_brightness_threshold,
                adaptive_fps=adaptive_fps,
                protocol=protocol,
                description=description,
                tags=tags,
                brightness_value_source_id=brightness_value_source_id,
            )
        if target_type == "ha_light":
            return self.create_ha_light_target(
                name=name,
                ha_source_id=ha_source_id,
                source_kind=source_kind,
                color_strip_source_id=color_strip_source_id,
                color_value_source_id=color_value_source_id,
                brightness=brightness,
                ha_light_mappings=ha_light_mappings,
                update_rate=update_rate if update_rate is not None else 2.0,
                transition=transition,
                min_brightness_threshold=min_brightness_threshold,
                color_tolerance=color_tolerance,
                stop_action=stop_action,
                description=description,
                tags=tags,
                brightness_value_source_id=brightness_value_source_id,
            )
        if target_type == "z2m_light":
            return self.create_z2m_light_target(
                name=name,
                mqtt_source_id=mqtt_source_id,
                source_kind=source_kind,
                color_strip_source_id=color_strip_source_id,
                color_value_source_id=color_value_source_id,
                brightness=brightness,
                z2m_light_mappings=z2m_light_mappings,
                base_topic=base_topic,
                update_rate=update_rate if update_rate is not None else 5.0,
                transition=transition,
                min_brightness_threshold=min_brightness_threshold,
                color_tolerance=color_tolerance,
                stop_action=stop_action,
                description=description,
                tags=tags,
                brightness_value_source_id=brightness_value_source_id,
            )
        raise ValueError(f"Invalid target type: {target_type}")

    # ---- typed update methods ------------------------------------------

    def _begin_update(self, target_id: str, new_name: Optional[str]) -> OutputTarget:
        if target_id not in self._items:
            raise ValueError(f"Output target not found: {target_id}")
        target = self._items[target_id]
        if new_name is not None:
            for other in self._items.values():
                if other.id != target_id and other.name == new_name:
                    raise ValueError(f"Output target with name '{new_name}' already exists")
        return target

    def _commit_update(self, target: OutputTarget) -> OutputTarget:
        target.updated_at = datetime.now(timezone.utc)
        self._save_item(target.id, target)
        logger.info(f"Updated output target: {target.id}")
        return target

    def update_wled_target(
        self,
        target_id: str,
        *,
        name: Optional[str] = None,
        device_id: Optional[str] = None,
        color_strip_source_id: Optional[str] = None,
        brightness: Any = None,
        fps: Any = None,
        keepalive_interval: Optional[float] = None,
        state_check_interval: Optional[int] = None,
        min_brightness_threshold: Any = None,
        adaptive_fps: Optional[bool] = None,
        protocol: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        icon: Optional[str] = None,
        icon_color: Optional[str] = None,
        brightness_value_source_id: Optional[str] = None,
    ) -> WledOutputTarget:
        target = self._begin_update(target_id, name)
        if not isinstance(target, WledOutputTarget):
            raise ValueError(f"Target {target_id} is not a WLED target")
        target.update_fields(
            name=name,
            device_id=device_id,
            color_strip_source_id=color_strip_source_id,
            brightness=brightness,
            brightness_value_source_id=brightness_value_source_id,
            fps=fps,
            keepalive_interval=keepalive_interval,
            state_check_interval=state_check_interval,
            min_brightness_threshold=min_brightness_threshold,
            adaptive_fps=adaptive_fps,
            protocol=protocol,
            description=description,
            tags=tags,
            icon=icon,
            icon_color=icon_color,
        )
        return self._commit_update(target)  # type: ignore[return-value]

    def update_ha_light_target(
        self,
        target_id: str,
        *,
        name: Optional[str] = None,
        ha_source_id: Optional[str] = None,
        source_kind: Optional[str] = None,
        color_strip_source_id: Optional[str] = None,
        color_value_source_id: Optional[str] = None,
        brightness: Any = None,
        ha_light_mappings: Optional[List[HALightMapping]] = None,
        update_rate: Any = None,
        transition: Any = None,
        min_brightness_threshold: Any = None,
        color_tolerance: Any = None,
        stop_action: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        icon: Optional[str] = None,
        icon_color: Optional[str] = None,
        brightness_value_source_id: Optional[str] = None,
    ) -> HALightOutputTarget:
        target = self._begin_update(target_id, name)
        if not isinstance(target, HALightOutputTarget):
            raise ValueError(f"Target {target_id} is not an HA-light target")
        target.update_fields(
            name=name,
            ha_source_id=ha_source_id,
            source_kind=source_kind,
            color_strip_source_id=color_strip_source_id,
            color_value_source_id=color_value_source_id,
            brightness=brightness,
            brightness_value_source_id=brightness_value_source_id,
            light_mappings=ha_light_mappings,
            update_rate=update_rate,
            transition=transition,
            min_brightness_threshold=min_brightness_threshold,
            color_tolerance=color_tolerance,
            stop_action=stop_action,
            description=description,
            tags=tags,
            icon=icon,
            icon_color=icon_color,
        )
        return self._commit_update(target)  # type: ignore[return-value]

    def update_z2m_light_target(
        self,
        target_id: str,
        *,
        name: Optional[str] = None,
        mqtt_source_id: Optional[str] = None,
        source_kind: Optional[str] = None,
        color_strip_source_id: Optional[str] = None,
        color_value_source_id: Optional[str] = None,
        brightness: Any = None,
        z2m_light_mappings: Optional[List[Z2MLightMapping]] = None,
        base_topic: Optional[str] = None,
        update_rate: Any = None,
        transition: Any = None,
        min_brightness_threshold: Any = None,
        color_tolerance: Any = None,
        stop_action: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        icon: Optional[str] = None,
        icon_color: Optional[str] = None,
        brightness_value_source_id: Optional[str] = None,
    ) -> Z2MLightOutputTarget:
        target = self._begin_update(target_id, name)
        if not isinstance(target, Z2MLightOutputTarget):
            raise ValueError(f"Target {target_id} is not a Z2M-light target")
        target.update_fields(
            name=name,
            source_kind=source_kind,
            color_strip_source_id=color_strip_source_id,
            color_value_source_id=color_value_source_id,
            brightness=brightness,
            brightness_value_source_id=brightness_value_source_id,
            light_mappings=z2m_light_mappings,
            mqtt_source_id=mqtt_source_id,
            base_topic=base_topic,
            update_rate=update_rate,
            transition=transition,
            min_brightness_threshold=min_brightness_threshold,
            color_tolerance=color_tolerance,
            stop_action=stop_action,
            description=description,
            tags=tags,
            icon=icon,
            icon_color=icon_color,
        )
        return self._commit_update(target)  # type: ignore[return-value]

    # ---- legacy generic shim (deferred deletion in Phase 5) ------------

    def update_target(
        self,
        target_id: str,
        name=None,
        device_id=None,
        color_strip_source_id=None,
        brightness=None,
        fps=None,
        keepalive_interval=None,
        state_check_interval=None,
        min_brightness_threshold=None,
        adaptive_fps=None,
        protocol=None,
        description=None,
        tags=None,
        icon=None,
        icon_color=None,
        ha_source_id=None,
        source_kind=None,
        color_value_source_id=None,
        ha_light_mappings=None,
        update_rate=None,
        transition=None,
        color_tolerance=None,
        stop_action=None,
        z2m_light_mappings=None,
        base_topic=None,
        mqtt_source_id=None,
        brightness_value_source_id=None,
    ) -> OutputTarget:
        """Generic update that dispatches by the target's existing type.

        Kept as a thin shim during the refactor — new code should call
        :meth:`update_wled_target`, :meth:`update_ha_light_target`, or
        :meth:`update_z2m_light_target` directly.
        """
        if target_id not in self._items:
            raise ValueError(f"Output target not found: {target_id}")
        target = self._items[target_id]
        if isinstance(target, WledOutputTarget):
            return self.update_wled_target(
                target_id,
                name=name,
                device_id=device_id,
                color_strip_source_id=color_strip_source_id,
                brightness=brightness,
                fps=fps,
                keepalive_interval=keepalive_interval,
                state_check_interval=state_check_interval,
                min_brightness_threshold=min_brightness_threshold,
                adaptive_fps=adaptive_fps,
                protocol=protocol,
                description=description,
                tags=tags,
                icon=icon,
                icon_color=icon_color,
                brightness_value_source_id=brightness_value_source_id,
            )
        if isinstance(target, HALightOutputTarget):
            return self.update_ha_light_target(
                target_id,
                name=name,
                ha_source_id=ha_source_id,
                source_kind=source_kind,
                color_strip_source_id=color_strip_source_id,
                color_value_source_id=color_value_source_id,
                brightness=brightness,
                ha_light_mappings=ha_light_mappings,
                update_rate=update_rate,
                transition=transition,
                min_brightness_threshold=min_brightness_threshold,
                color_tolerance=color_tolerance,
                stop_action=stop_action,
                description=description,
                tags=tags,
                icon=icon,
                icon_color=icon_color,
                brightness_value_source_id=brightness_value_source_id,
            )
        if isinstance(target, Z2MLightOutputTarget):
            return self.update_z2m_light_target(
                target_id,
                name=name,
                mqtt_source_id=mqtt_source_id,
                source_kind=source_kind,
                color_strip_source_id=color_strip_source_id,
                color_value_source_id=color_value_source_id,
                brightness=brightness,
                z2m_light_mappings=z2m_light_mappings,
                base_topic=base_topic,
                update_rate=update_rate,
                transition=transition,
                min_brightness_threshold=min_brightness_threshold,
                color_tolerance=color_tolerance,
                stop_action=stop_action,
                description=description,
                tags=tags,
                icon=icon,
                icon_color=icon_color,
                brightness_value_source_id=brightness_value_source_id,
            )
        raise ValueError(f"Unknown target class: {type(target).__name__}")

    def get_targets_for_device(self, device_id: str) -> List[OutputTarget]:
        """Get all targets that reference a specific device."""
        return [
            t
            for t in self._items.values()
            if isinstance(t, WledOutputTarget) and t.device_id == device_id
        ]

    def get_targets_referencing_css(self, css_id: str) -> List[str]:
        """Return names of targets that reference a color strip source."""
        result = []
        for target in self._items.values():
            if isinstance(target, WledOutputTarget) and target.color_strip_source_id == css_id:
                result.append(target.name)
            elif isinstance(target, HALightOutputTarget) and target.color_strip_source_id == css_id:
                result.append(target.name)
            elif (
                isinstance(target, Z2MLightOutputTarget) and target.color_strip_source_id == css_id
            ):
                result.append(target.name)
        return result

    def count(self) -> int:
        """Get number of targets."""
        return len(self._items)
