from typing import Optional, List
from common.database import DBConfig, Db
from common.logging import get_logger, span

from common.models.custom_wake_word import CustomWakeWord

from common.config.config import ProModelSettings

logger = get_logger(__name__)


class WakeWordError(Exception):
    """Base exception for wake word operations."""


class WakeWordValidationError(WakeWordError):
    """Invalid payload or wake word parameters."""


class WakeWordForbiddenError(WakeWordError):
    """Wake word operation is forbidden for current user/tier."""


class WakeWordConflictError(WakeWordError):
    """Wake word operation conflicts with existing state/limits."""


SELECT_BASE = """
    SELECT id, user_id, word, language
    FROM custom_wake_words
"""

COUNT_BY_USER_QUERY = """
    SELECT COUNT(*) 
    FROM custom_wake_words
    WHERE user_id = %s
"""

SELECT_BY_USER_AND_LANGUAGE_QUERY = (
    SELECT_BASE
    + """
    WHERE user_id = %s
      AND language = %s
    ORDER BY word ASC, id ASC
"""
)

SELECT_BY_USER_QUERY = (
    SELECT_BASE
    + """
    WHERE user_id = %s
    ORDER BY word ASC, id ASC
"""
)

DELETE_BY_ID_QUERY = """
    DELETE FROM custom_wake_words
    WHERE id = %s
"""

ALLOWED_LANGUAGES = {
    "english",
    "spanish",
    "russian",
    "french",
    "german",
    "portuguese",
    "japanese",
    "chinese",
    "italian",
    "polish",
}


class CustomWakeWordsDAO:
    """DAO for custom_wake_words."""

    def __init__(self, db: Optional[Db] = None):
        self.db = db if db else Db(DBConfig(pg_dsn=None))
        self.proSettings = ProModelSettings()
        logger.debug(
            "CustomWakeWordsDAO initialized", extra={"component": "CustomWakeWordsDAO"}
        )

    async def count_by_user(self, user_id: int) -> int:
        """Return how many wake words a user has."""
        with span(logger, "cww_count_by_user", {"user_id": user_id}):
            row = await self.db.fetch_one(COUNT_BY_USER_QUERY, (user_id,))
            try:
                return int(row[0])
            except (TypeError, KeyError):
                raise

    async def get_by_user_and_language(
        self, user_id: int, language: str
    ) -> List[CustomWakeWord]:
        """Return all wake words for a user in a given language."""
        with span(
            logger,
            "cww_get_by_user_and_lang",
            {"user_id": user_id, "language": language},
        ):
            rows = await self.db.fetch_all(
                SELECT_BY_USER_AND_LANGUAGE_QUERY, (user_id, language)
            )
            return [self._row_to_model(r) for r in rows]

    async def get_by_user(self, user_id: int) -> List[CustomWakeWord]:
        """Return all wake words for a user"""
        with span(logger, "cww_get_by_user", {"user_id": user_id}):
            rows = await self.db.fetch_all(SELECT_BY_USER_QUERY, (user_id))
            return [self._row_to_model(r) for r in rows]

    async def delete(self, wake_word_id: int) -> None:
        """Delete a wake word by id."""
        with span(logger, "cww_delete", {"id": wake_word_id}):
            await self.db.execute_commit(DELETE_BY_ID_QUERY, (wake_word_id,))
            logger.info("Deleted custom wake word", extra={"id": wake_word_id})

    async def create(
        self, user_id: int, word: str, language: str, user_tier: str
    ) -> CustomWakeWord:
        """
        create a new wake word on the database
        """
        with span(logger, "cww_create", {"user_id": user_id, "tier": user_tier}):
            if user_tier == self.proSettings.free_tier:
                raise WakeWordForbiddenError(
                    "Free tier users cannot create custom wake words"
                )

            current = await self.count_by_user(user_id)
            if current >= self.proSettings.max_custom_wake_words:
                raise WakeWordConflictError(
                    f"User {user_id} exceeded max_custom_wake_words "
                    f"({self.proSettings.max_custom_wake_words})"
                )

            w = (word or "").strip()
            if not w:
                raise WakeWordValidationError("word cannot be empty")
            if len(w) > 50:
                raise WakeWordValidationError("word must be <= 50 characters")

            lang = (language or "").strip().lower()
            if lang not in ALLOWED_LANGUAGES:
                raise WakeWordValidationError(f"Unsupported language: {lang}")

            columns = ["user_id", "word", "language"]
            values = (user_id, w, lang)
            new_id = await self.db.insert("custom_wake_words", columns, values)

            if not new_id:
                raise RuntimeError("Insert returned no id for custom_wake_words")

            logger.info(
                "Created custom wake word",
                extra={"id": new_id, "user_id": user_id, "language": lang},
            )
            return CustomWakeWord(id=new_id, user_id=user_id, word=w, language=lang)

    def _row_to_model(self, row) -> CustomWakeWord:
        try:
            return CustomWakeWord(
                id=row[0],
                user_id=row[1],
                word=row[2],
                language=row[3],
            )
        except (TypeError, KeyError):
            raise
