"""
Forbidden word service for AI Service.
[AC-AISVC-78~AC-AISVC-81] Forbidden word CRUD and hit statistics management.
"""

import logging
import time
import uuid
from collections.abc import Sequence
from datetime import datetime
from typing import Any

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import col

from app.models.entities import (
    ForbiddenWord,
    ForbiddenWordCategory,
    ForbiddenWordCreate,
    ForbiddenWordStrategy,
    ForbiddenWordUpdate,
)

logger = logging.getLogger(__name__)

WORD_CACHE_TTL_SECONDS = 60


class WordCache:
    """
    [AC-AISVC-82] In-memory cache for forbidden words.
    Key: tenant_id
    Value: (words_list, cached_at)
    TTL: 60 seconds
    """

    def __init__(self, ttl_seconds: int = WORD_CACHE_TTL_SECONDS):
        self._cache: dict[str, tuple[list[ForbiddenWord], float]] = {}
        self._ttl = ttl_seconds

    def get(self, tenant_id: str) -> list[ForbiddenWord] | None:
        """Get cached words if not expired."""
        if tenant_id in self._cache:
            words, cached_at = self._cache[tenant_id]
            if time.time() - cached_at < self._ttl:
                return words
            else:
                del self._cache[tenant_id]
        return None

    def set(self, tenant_id: str, words: list[ForbiddenWord]) -> None:
        """Cache words for a tenant."""
        self._cache[tenant_id] = (words, time.time())

    def invalidate(self, tenant_id: str) -> None:
        """Invalidate cache for a tenant."""
        if tenant_id in self._cache:
            del self._cache[tenant_id]
            logger.debug(f"Invalidated word cache for tenant={tenant_id}")


_word_cache = WordCache()


class ForbiddenWordService:
    """
    [AC-AISVC-78~AC-AISVC-81] Service for managing forbidden words.

    Features:
    - Word CRUD with tenant isolation
    - Hit count statistics
    - In-memory caching with TTL
    - Cache invalidation on CRUD operations
    - Support for mask/replace/block strategies
    """

    VALID_CATEGORIES = [c.value for c in ForbiddenWordCategory]
    VALID_STRATEGIES = [s.value for s in ForbiddenWordStrategy]

    def __init__(self, session: AsyncSession):
        self._session = session
        self._cache = _word_cache

    async def create_word(
        self,
        tenant_id: str,
        create_data: ForbiddenWordCreate,
    ) -> ForbiddenWord:
        """
        [AC-AISVC-78] Create a new forbidden word.
        """
        if create_data.category not in self.VALID_CATEGORIES:
            raise ValueError(f"Invalid category. Must be one of: {self.VALID_CATEGORIES}")

        if create_data.strategy not in self.VALID_STRATEGIES:
            raise ValueError(f"Invalid strategy. Must be one of: {self.VALID_STRATEGIES}")

        if create_data.strategy == ForbiddenWordStrategy.REPLACE.value and not create_data.replacement:
            raise ValueError("replacement is required when strategy is 'replace'")

        if create_data.strategy == ForbiddenWordStrategy.BLOCK.value and not create_data.fallback_reply:
            logger.warning(
                f"[AC-AISVC-78] Creating block word without fallback_reply: tenant={tenant_id}, word={create_data.word}"
            )

        word = ForbiddenWord(
            tenant_id=tenant_id,
            word=create_data.word,
            category=create_data.category,
            strategy=create_data.strategy,
            replacement=create_data.replacement,
            fallback_reply=create_data.fallback_reply,
            is_enabled=True,
            hit_count=0,
        )
        self._session.add(word)
        await self._session.flush()

        self._cache.invalidate(tenant_id)

        logger.info(
            f"[AC-AISVC-78] Created forbidden word: tenant={tenant_id}, "
            f"id={word.id}, word={word.word}, strategy={word.strategy}"
        )
        return word

    async def list_words(
        self,
        tenant_id: str,
        category: str | None = None,
        is_enabled: bool | None = None,
    ) -> Sequence[ForbiddenWord]:
        """
        [AC-AISVC-79] List words for a tenant with optional filters.
        """
        stmt = select(ForbiddenWord).where(
            ForbiddenWord.tenant_id == tenant_id  # type: ignore
        )

        if category is not None:
            stmt = stmt.where(ForbiddenWord.category == category)  # type: ignore

        if is_enabled is not None:
            stmt = stmt.where(ForbiddenWord.is_enabled == is_enabled)  # type: ignore

        stmt = stmt.order_by(col(ForbiddenWord.category), col(ForbiddenWord.created_at).desc())
        result = await self._session.execute(stmt)
        return result.scalars().all()

    async def get_word(
        self,
        tenant_id: str,
        word_id: uuid.UUID,
    ) -> ForbiddenWord | None:
        """
        [AC-AISVC-79] Get word by ID with tenant isolation.
        """
        stmt = select(ForbiddenWord).where(
            ForbiddenWord.tenant_id == tenant_id,  # type: ignore
            ForbiddenWord.id == word_id,  # type: ignore
        )
        result = await self._session.execute(stmt)
        return result.scalar_one_or_none()

    async def update_word(
        self,
        tenant_id: str,
        word_id: uuid.UUID,
        update_data: ForbiddenWordUpdate,
    ) -> ForbiddenWord | None:
        """
        [AC-AISVC-80] Update a forbidden word.
        """
        word = await self.get_word(tenant_id, word_id)
        if not word:
            return None

        if update_data.word is not None:
            word.word = update_data.word
        if update_data.category is not None:
            if update_data.category not in self.VALID_CATEGORIES:
                raise ValueError(f"Invalid category. Must be one of: {self.VALID_CATEGORIES}")
            word.category = update_data.category
        if update_data.strategy is not None:
            if update_data.strategy not in self.VALID_STRATEGIES:
                raise ValueError(f"Invalid strategy. Must be one of: {self.VALID_STRATEGIES}")
            word.strategy = update_data.strategy
        if update_data.replacement is not None:
            word.replacement = update_data.replacement
        if update_data.fallback_reply is not None:
            word.fallback_reply = update_data.fallback_reply
        if update_data.is_enabled is not None:
            word.is_enabled = update_data.is_enabled

        word.updated_at = datetime.utcnow()
        await self._session.flush()

        self._cache.invalidate(tenant_id)

        logger.info(
            f"[AC-AISVC-80] Updated forbidden word: tenant={tenant_id}, id={word_id}"
        )
        return word

    async def delete_word(
        self,
        tenant_id: str,
        word_id: uuid.UUID,
    ) -> bool:
        """
        [AC-AISVC-81] Delete a forbidden word.
        """
        word = await self.get_word(tenant_id, word_id)
        if not word:
            return False

        await self._session.delete(word)
        await self._session.flush()

        self._cache.invalidate(tenant_id)

        logger.info(
            f"[AC-AISVC-81] Deleted forbidden word: tenant={tenant_id}, id={word_id}"
        )
        return True

    async def increment_hit_count(
        self,
        tenant_id: str,
        word_id: uuid.UUID,
    ) -> bool:
        """
        [AC-AISVC-79] Increment hit count for a word.
        """
        word = await self.get_word(tenant_id, word_id)
        if not word:
            return False

        word.hit_count += 1
        word.updated_at = datetime.utcnow()
        await self._session.flush()

        logger.debug(
            f"[AC-AISVC-79] Incremented hit count for word: tenant={tenant_id}, "
            f"id={word_id}, hit_count={word.hit_count}"
        )
        return True

    async def get_enabled_words_for_filtering(
        self,
        tenant_id: str,
    ) -> list[ForbiddenWord]:
        """
        [AC-AISVC-82] Get enabled words for filtering.
        Uses cache for performance.
        """
        cached = self._cache.get(tenant_id)
        if cached is not None:
            logger.debug(f"[AC-AISVC-82] Cache hit for words: tenant={tenant_id}")
            return cached

        stmt = (
            select(ForbiddenWord)
            .where(
                ForbiddenWord.tenant_id == tenant_id,  # type: ignore
                ForbiddenWord.is_enabled == True,  # type: ignore
            )
            .order_by(col(ForbiddenWord.category))
        )
        result = await self._session.execute(stmt)
        words = list(result.scalars().all())

        self._cache.set(tenant_id, words)
        logger.info(
            f"[AC-AISVC-82] Loaded {len(words)} enabled words from DB: tenant={tenant_id}"
        )
        return words

    def invalidate_cache(self, tenant_id: str) -> None:
        """Manually invalidate cache for a tenant."""
        self._cache.invalidate(tenant_id)

    async def word_to_info_dict(self, word: ForbiddenWord) -> dict[str, Any]:
        """Convert word entity to API response dict."""
        return {
            "id": str(word.id),
            "word": word.word,
            "category": word.category,
            "strategy": word.strategy,
            "replacement": word.replacement,
            "fallback_reply": word.fallback_reply,
            "is_enabled": word.is_enabled,
            "hit_count": word.hit_count,
            "created_at": word.created_at.isoformat(),
            "updated_at": word.updated_at.isoformat(),
        }
