"""
Behavior rule service for AI Service.
[AC-AISVC-84, AC-AISVC-85] Behavior rule CRUD 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 (
    BehaviorRule,
    BehaviorRuleCategory,
    BehaviorRuleCreate,
    BehaviorRuleUpdate,
)

logger = logging.getLogger(__name__)

BEHAVIOR_CACHE_TTL_SECONDS = 60


class BehaviorRuleCache:
    """
    [AC-AISVC-84] In-memory cache for behavior rules.
    Key: tenant_id
    Value: (rules_list, cached_at)
    TTL: 60 seconds
    """

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

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

    def set(self, tenant_id: str, rules: list[BehaviorRule]) -> None:
        """Cache rules for a tenant."""
        self._cache[tenant_id] = (rules, 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 behavior rule cache for tenant={tenant_id}")


_behavior_cache = BehaviorRuleCache()


class BehaviorRuleService:
    """
    [AC-AISVC-84, AC-AISVC-85] Service for managing behavior rules.

    Features:
    - Rule CRUD with tenant isolation
    - In-memory caching with TTL
    - Cache invalidation on CRUD operations
    - Rules are injected into Prompt system instruction
    """

    VALID_CATEGORIES = [c.value for c in BehaviorRuleCategory]

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

    async def create_rule(
        self,
        tenant_id: str,
        create_data: BehaviorRuleCreate,
    ) -> BehaviorRule:
        """
        [AC-AISVC-84] Create a new behavior rule.
        """
        if create_data.category not in self.VALID_CATEGORIES:
            raise ValueError(f"Invalid category. Must be one of: {self.VALID_CATEGORIES}")

        rule = BehaviorRule(
            tenant_id=tenant_id,
            rule_text=create_data.rule_text,
            category=create_data.category,
            is_enabled=True,
        )
        self._session.add(rule)
        await self._session.flush()

        self._cache.invalidate(tenant_id)

        logger.info(
            f"[AC-AISVC-84] Created behavior rule: tenant={tenant_id}, "
            f"id={rule.id}, category={rule.category}"
        )
        return rule

    async def list_rules(
        self,
        tenant_id: str,
        category: str | None = None,
    ) -> Sequence[BehaviorRule]:
        """
        [AC-AISVC-85] List rules for a tenant with optional filters.
        """
        stmt = select(BehaviorRule).where(
            BehaviorRule.tenant_id == tenant_id  # type: ignore
        )

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

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

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

    async def update_rule(
        self,
        tenant_id: str,
        rule_id: uuid.UUID,
        update_data: BehaviorRuleUpdate,
    ) -> BehaviorRule | None:
        """
        [AC-AISVC-85] Update a behavior rule.
        """
        rule = await self.get_rule(tenant_id, rule_id)
        if not rule:
            return None

        if update_data.rule_text is not None:
            rule.rule_text = update_data.rule_text
        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}")
            rule.category = update_data.category
        if update_data.is_enabled is not None:
            rule.is_enabled = update_data.is_enabled

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

        self._cache.invalidate(tenant_id)

        logger.info(
            f"[AC-AISVC-85] Updated behavior rule: tenant={tenant_id}, id={rule_id}"
        )
        return rule

    async def delete_rule(
        self,
        tenant_id: str,
        rule_id: uuid.UUID,
    ) -> bool:
        """
        [AC-AISVC-85] Delete a behavior rule.
        """
        rule = await self.get_rule(tenant_id, rule_id)
        if not rule:
            return False

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

        self._cache.invalidate(tenant_id)

        logger.info(
            f"[AC-AISVC-85] Deleted behavior rule: tenant={tenant_id}, id={rule_id}"
        )
        return True

    async def get_enabled_rules_for_injection(
        self,
        tenant_id: str,
    ) -> list[BehaviorRule]:
        """
        [AC-AISVC-84] Get enabled rules for Prompt injection.
        Uses cache for performance.
        """
        cached = self._cache.get(tenant_id)
        if cached is not None:
            logger.debug(f"[AC-AISVC-84] Cache hit for behavior rules: tenant={tenant_id}")
            return cached

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

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

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

    async def rule_to_info_dict(self, rule: BehaviorRule) -> dict[str, Any]:
        """Convert rule entity to API response dict."""
        return {
            "id": str(rule.id),
            "rule_text": rule.rule_text,
            "category": rule.category,
            "is_enabled": rule.is_enabled,
            "created_at": rule.created_at.isoformat(),
            "updated_at": rule.updated_at.isoformat(),
        }

    async def format_rules_for_prompt(
        self,
        tenant_id: str,
    ) -> str:
        """
        [AC-AISVC-84] Format behavior rules for Prompt injection.
        Returns formatted string to append to system instruction.
        """
        rules = await self.get_enabled_rules_for_injection(tenant_id)

        if not rules:
            return ""

        lines = ["\n\n[行为约束 - 以下规则必须严格遵守]"]
        for i, rule in enumerate(rules, 1):
            lines.append(f"{i}. {rule.rule_text}")

        return "\n".join(lines)
