"""Device storage using SQLite."""

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

from wled_controller.storage.base_sqlite_store import BaseSqliteStore
from wled_controller.storage.database import Database
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class Device:
    """Represents a WLED device configuration.

    A device holds connection state and output settings.
    Calibration, processing settings, and picture source assignments
    now live on ColorStripSource and WledOutputTarget respectively.
    """

    def __init__(
        self,
        device_id: str,
        name: str,
        url: str,
        led_count: int,
        enabled: bool = True,
        device_type: str = "wled",
        baud_rate: Optional[int] = None,
        software_brightness: int = 255,
        auto_shutdown: bool = False,
        send_latency_ms: int = 0,
        rgbw: bool = False,
        zone_mode: str = "combined",
        tags: List[str] = None,
        # 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",
        # Default color strip processing template
        default_css_processing_template_id: str = "",
        created_at: Optional[datetime] = None,
        updated_at: Optional[datetime] = None,
    ):
        self.id = device_id
        self.name = name
        self.url = url
        self.led_count = led_count
        self.enabled = enabled
        self.device_type = device_type
        self.baud_rate = baud_rate
        self.software_brightness = software_brightness
        self.auto_shutdown = auto_shutdown
        self.send_latency_ms = send_latency_ms
        self.rgbw = rgbw
        self.zone_mode = zone_mode
        self.tags = tags or []
        self.dmx_protocol = dmx_protocol
        self.dmx_start_universe = dmx_start_universe
        self.dmx_start_channel = dmx_start_channel
        self.espnow_peer_mac = espnow_peer_mac
        self.espnow_channel = espnow_channel
        self.hue_username = hue_username
        self.hue_client_key = hue_client_key
        self.hue_entertainment_group_id = hue_entertainment_group_id
        self.spi_speed_hz = spi_speed_hz
        self.spi_led_type = spi_led_type
        self.chroma_device_type = chroma_device_type
        self.gamesense_device_type = gamesense_device_type
        self.default_css_processing_template_id = default_css_processing_template_id
        self.created_at = created_at or datetime.now(timezone.utc)
        self.updated_at = updated_at or datetime.now(timezone.utc)

    def to_dict(self) -> dict:
        """Convert device to dictionary."""
        d = {
            "id": self.id,
            "name": self.name,
            "url": self.url,
            "led_count": self.led_count,
            "enabled": self.enabled,
            "device_type": self.device_type,
            "created_at": self.created_at.isoformat(),
            "updated_at": self.updated_at.isoformat(),
        }
        if self.baud_rate is not None:
            d["baud_rate"] = self.baud_rate
        if self.software_brightness != 255:
            d["software_brightness"] = self.software_brightness
        if self.auto_shutdown:
            d["auto_shutdown"] = True
        if self.send_latency_ms:
            d["send_latency_ms"] = self.send_latency_ms
        if self.rgbw:
            d["rgbw"] = True
        if self.zone_mode != "combined":
            d["zone_mode"] = self.zone_mode
        if self.tags:
            d["tags"] = self.tags
        if self.dmx_protocol != "artnet":
            d["dmx_protocol"] = self.dmx_protocol
        if self.dmx_start_universe != 0:
            d["dmx_start_universe"] = self.dmx_start_universe
        if self.dmx_start_channel != 1:
            d["dmx_start_channel"] = self.dmx_start_channel
        if self.espnow_peer_mac:
            d["espnow_peer_mac"] = self.espnow_peer_mac
        if self.espnow_channel != 1:
            d["espnow_channel"] = self.espnow_channel
        if self.hue_username:
            d["hue_username"] = self.hue_username
        if self.hue_client_key:
            d["hue_client_key"] = self.hue_client_key
        if self.hue_entertainment_group_id:
            d["hue_entertainment_group_id"] = self.hue_entertainment_group_id
        if self.spi_speed_hz != 800000:
            d["spi_speed_hz"] = self.spi_speed_hz
        if self.spi_led_type != "WS2812B":
            d["spi_led_type"] = self.spi_led_type
        if self.chroma_device_type != "chromalink":
            d["chroma_device_type"] = self.chroma_device_type
        if self.gamesense_device_type != "keyboard":
            d["gamesense_device_type"] = self.gamesense_device_type
        if self.default_css_processing_template_id:
            d["default_css_processing_template_id"] = self.default_css_processing_template_id
        return d

    @classmethod
    def from_dict(cls, data: dict) -> "Device":
        """Create device from dictionary."""
        return cls(
            device_id=data["id"],
            name=data["name"],
            url=data["url"],
            led_count=data["led_count"],
            enabled=data.get("enabled", True),
            device_type=data.get("device_type", "wled"),
            baud_rate=data.get("baud_rate"),
            software_brightness=data.get("software_brightness", 255),
            auto_shutdown=data.get("auto_shutdown", False),
            send_latency_ms=data.get("send_latency_ms", 0),
            rgbw=data.get("rgbw", False),
            zone_mode=data.get("zone_mode", "combined"),
            tags=data.get("tags", []),
            dmx_protocol=data.get("dmx_protocol", "artnet"),
            dmx_start_universe=data.get("dmx_start_universe", 0),
            dmx_start_channel=data.get("dmx_start_channel", 1),
            espnow_peer_mac=data.get("espnow_peer_mac", ""),
            espnow_channel=data.get("espnow_channel", 1),
            hue_username=data.get("hue_username", ""),
            hue_client_key=data.get("hue_client_key", ""),
            hue_entertainment_group_id=data.get("hue_entertainment_group_id", ""),
            spi_speed_hz=data.get("spi_speed_hz", 800000),
            spi_led_type=data.get("spi_led_type", "WS2812B"),
            chroma_device_type=data.get("chroma_device_type", "chromalink"),
            gamesense_device_type=data.get("gamesense_device_type", "keyboard"),
            default_css_processing_template_id=data.get("default_css_processing_template_id", ""),
            created_at=datetime.fromisoformat(data.get("created_at", datetime.now(timezone.utc).isoformat())),
            updated_at=datetime.fromisoformat(data.get("updated_at", datetime.now(timezone.utc).isoformat())),
        )


# Fields that can be updated (all Device.__init__ params except identity/timestamps)
_UPDATABLE_FIELDS: frozenset[str] = frozenset({
    "name", "url", "led_count", "enabled", "device_type",
    "baud_rate", "software_brightness", "auto_shutdown",
    "send_latency_ms", "rgbw", "zone_mode", "tags",
    "dmx_protocol", "dmx_start_universe", "dmx_start_channel",
    "espnow_peer_mac", "espnow_channel",
    "hue_username", "hue_client_key", "hue_entertainment_group_id",
    "spi_speed_hz", "spi_led_type",
    "chroma_device_type", "gamesense_device_type",
    "default_css_processing_template_id",
})


class DeviceStore(BaseSqliteStore[Device]):
    """Persistent storage for WLED devices."""

    _table_name = "devices"
    _entity_name = "Device"

    def __init__(self, db: Database):
        super().__init__(db, Device.from_dict)
        logger.info(f"Device store initialized with {len(self._items)} devices")

    # ── Backward-compat aliases ──────────────────────────────────

    def get_device(self, device_id: str) -> Device:
        """Get device by ID. Raises ValueError if not found."""
        return self.get(device_id)

    def get_all_devices(self) -> List[Device]:
        """Get all devices."""
        return self.get_all()

    def delete_device(self, device_id: str) -> None:
        """Delete device. Raises ValueError if not found."""
        self.delete(device_id)

    # ── Create / Update ──────────────────────────────────────────

    def create_device(
        self,
        name: str,
        url: str,
        led_count: int,
        device_type: str = "wled",
        baud_rate: Optional[int] = None,
        auto_shutdown: bool = False,
        send_latency_ms: int = 0,
        rgbw: bool = False,
        zone_mode: str = "combined",
        tags: Optional[List[str]] = None,
        dmx_protocol: str = "artnet",
        dmx_start_universe: int = 0,
        dmx_start_channel: int = 1,
        espnow_peer_mac: str = "",
        espnow_channel: int = 1,
        hue_username: str = "",
        hue_client_key: str = "",
        hue_entertainment_group_id: str = "",
        spi_speed_hz: int = 800000,
        spi_led_type: str = "WS2812B",
        chroma_device_type: str = "chromalink",
        gamesense_device_type: str = "keyboard",
    ) -> Device:
        """Create a new device."""
        with self._lock:
            self._check_name_unique(name)

            device_id = f"device_{uuid.uuid4().hex[:8]}"

            # Mock devices use their device ID as the URL authority
            if device_type == "mock":
                url = f"mock://{device_id}"

            device = Device(
                device_id=device_id,
                name=name,
                url=url,
                led_count=led_count,
                device_type=device_type,
                baud_rate=baud_rate,
                auto_shutdown=auto_shutdown,
                send_latency_ms=send_latency_ms,
                rgbw=rgbw,
                zone_mode=zone_mode,
                tags=tags or [],
                dmx_protocol=dmx_protocol,
                dmx_start_universe=dmx_start_universe,
                dmx_start_channel=dmx_start_channel,
                espnow_peer_mac=espnow_peer_mac,
                espnow_channel=espnow_channel,
                hue_username=hue_username,
                hue_client_key=hue_client_key,
                hue_entertainment_group_id=hue_entertainment_group_id,
                spi_speed_hz=spi_speed_hz,
                spi_led_type=spi_led_type,
                chroma_device_type=chroma_device_type,
                gamesense_device_type=gamesense_device_type,
            )

            self._items[device_id] = device
            self._save_item(device_id, device)

            logger.info(f"Created device {device_id}: {name}")
            return device

    def update_device(self, device_id: str, **kwargs) -> Device:
        """Update device fields.

        Pass any updatable Device field as a keyword argument.
        ``None`` values are ignored (no change).
        """
        with self._lock:
            device = self.get(device_id)  # raises ValueError if not found

            # Collect updates (ignore None values and unknown fields)
            updates = {
                key: value
                for key, value in kwargs.items()
                if value is not None and key in _UPDATABLE_FIELDS
            }

            # Check name uniqueness if name is being changed
            new_name = updates.get("name")
            if new_name is not None and new_name != device.name:
                self._check_name_unique(new_name, exclude_id=device_id)

            # Build new Device from existing fields + updates (immutable pattern)
            device_fields = device.to_dict()
            # Map 'id' back to 'device_id' for the constructor
            device_fields["device_id"] = device_fields.pop("id")
            # Restore datetime objects (to_dict serializes them as ISO strings)
            device_fields["created_at"] = device.created_at
            device_fields["updated_at"] = datetime.now(timezone.utc)
            # Apply updates
            device_fields.update(updates)

            new_device = Device(**device_fields)
            self._items[device_id] = new_device
            self._save_item(device_id, new_device)

            logger.info(f"Updated device {device_id}")
            return new_device

    # ── Unique helpers ───────────────────────────────────────────

    def device_exists(self, device_id: str) -> bool:
        """Check if device exists."""
        return device_id in self._items

    def clear(self):
        """Clear all devices (for testing)."""
        self._items.clear()
        self._db.delete_all(self._table_name)
        logger.warning("Cleared all devices from storage")
