"""Audio source storage using JSON files."""

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

from wled_controller.storage.audio_source import (
    AudioSource,
    MonoAudioSource,
    MultichannelAudioSource,
)
from wled_controller.storage.base_store import BaseJsonStore
from wled_controller.storage.utils import resolve_ref
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class AudioSourceStore(BaseJsonStore[AudioSource]):
    """Persistent storage for audio sources."""

    _json_key = "audio_sources"
    _entity_name = "Audio source"

    def __init__(self, file_path: str):
        super().__init__(file_path, AudioSource.from_dict)

    # Backward-compatible aliases
    get_all_sources = BaseJsonStore.get_all
    get_source = BaseJsonStore.get

    def get_mono_sources(self) -> List[MonoAudioSource]:
        """Return only mono audio sources (for CSS dropdown)."""
        return [s for s in self._items.values() if isinstance(s, MonoAudioSource)]

    def create_source(
        self,
        name: str,
        source_type: str,
        device_index: Optional[int] = None,
        is_loopback: Optional[bool] = None,
        audio_source_id: Optional[str] = None,
        channel: Optional[str] = None,
        description: Optional[str] = None,
        audio_template_id: Optional[str] = None,
        tags: Optional[List[str]] = None,
    ) -> AudioSource:
        self._check_name_unique(name)

        if source_type not in ("multichannel", "mono"):
            raise ValueError(f"Invalid source type: {source_type}")

        sid = f"as_{uuid.uuid4().hex[:8]}"
        now = datetime.now(timezone.utc)

        if source_type == "mono":
            if not audio_source_id:
                raise ValueError("Mono sources require audio_source_id")
            # Validate parent exists and is multichannel
            parent = self._items.get(audio_source_id)
            if not parent:
                raise ValueError(f"Parent audio source not found: {audio_source_id}")
            if not isinstance(parent, MultichannelAudioSource):
                raise ValueError("Mono sources must reference a multichannel source")

            source = MonoAudioSource(
                id=sid, name=name, source_type="mono",
                created_at=now, updated_at=now, description=description, tags=tags or [],
                audio_source_id=audio_source_id,
                channel=channel or "mono",
            )
        else:
            source = MultichannelAudioSource(
                id=sid, name=name, source_type="multichannel",
                created_at=now, updated_at=now, description=description, tags=tags or [],
                device_index=device_index if device_index is not None else -1,
                is_loopback=bool(is_loopback) if is_loopback is not None else True,
                audio_template_id=audio_template_id,
            )

        self._items[sid] = source
        self._save()

        logger.info(f"Created audio source: {name} ({sid}, type={source_type})")
        return source

    def update_source(
        self,
        source_id: str,
        name: Optional[str] = None,
        device_index: Optional[int] = None,
        is_loopback: Optional[bool] = None,
        audio_source_id: Optional[str] = None,
        channel: Optional[str] = None,
        description: Optional[str] = None,
        audio_template_id: Optional[str] = None,
        tags: Optional[List[str]] = None,
    ) -> AudioSource:
        source = self.get(source_id)

        if name is not None:
            self._check_name_unique(name, exclude_id=source_id)
            source.name = name

        if description is not None:
            source.description = description
        if tags is not None:
            source.tags = tags

        if isinstance(source, MultichannelAudioSource):
            if device_index is not None:
                source.device_index = device_index
            if is_loopback is not None:
                source.is_loopback = bool(is_loopback)
            if audio_template_id is not None:
                source.audio_template_id = resolve_ref(audio_template_id, source.audio_template_id)
        elif isinstance(source, MonoAudioSource):
            if audio_source_id is not None:
                resolved = resolve_ref(audio_source_id, source.audio_source_id)
                if resolved is not None:
                    # Validate parent exists and is multichannel
                    parent = self._items.get(resolved)
                    if not parent:
                        raise ValueError(f"Parent audio source not found: {resolved}")
                    if not isinstance(parent, MultichannelAudioSource):
                        raise ValueError("Mono sources must reference a multichannel source")
                source.audio_source_id = resolved
            if channel is not None:
                source.channel = channel

        source.updated_at = datetime.now(timezone.utc)
        self._save()

        logger.info(f"Updated audio source: {source_id}")
        return source

    def delete_source(self, source_id: str) -> None:
        if source_id not in self._items:
            raise ValueError(f"{self._entity_name} not found: {source_id}")

        source = self._items[source_id]

        # Prevent deleting multichannel sources referenced by mono sources
        if isinstance(source, MultichannelAudioSource):
            for other in self._items.values():
                if isinstance(other, MonoAudioSource) and other.audio_source_id == source_id:
                    raise ValueError(
                        f"Cannot delete '{source.name}': referenced by mono source '{other.name}'"
                    )

        del self._items[source_id]
        self._save()

        logger.info(f"Deleted audio source: {source_id}")

    # ── Resolution ───────────────────────────────────────────────────

    def resolve_audio_source(self, source_id: str) -> Tuple[int, bool, str, Optional[str]]:
        """Resolve any audio source to (device_index, is_loopback, channel, audio_template_id).

        Accepts both MultichannelAudioSource (defaults to "mono" channel)
        and MonoAudioSource (follows reference chain to parent multichannel).

        Raises:
            ValueError: If source not found or chain is broken
        """
        source = self.get_source(source_id)

        if isinstance(source, MultichannelAudioSource):
            return source.device_index, source.is_loopback, "mono", source.audio_template_id

        if isinstance(source, MonoAudioSource):
            parent = self.get_source(source.audio_source_id)
            if not isinstance(parent, MultichannelAudioSource):
                raise ValueError(
                    f"Mono source {source_id} references non-multichannel source {source.audio_source_id}"
                )
            return parent.device_index, parent.is_loopback, source.channel, parent.audio_template_id

        raise ValueError(f"Audio source {source_id} is not a valid audio source")
