"""
Redis cache layer for monitoring data.
[AC-AISVC-91, AC-AISVC-92] Redis-based caching for Dashboard statistics.
"""

import json
import logging
from datetime import datetime
from typing import Any

import redis.asyncio as redis

from app.core.config import get_settings

logger = logging.getLogger(__name__)


class MonitoringCache:
    """
    [AC-AISVC-91, AC-AISVC-92] Redis cache layer for monitoring data.

    Features:
    - Dashboard stats caching (60s TTL)
    - Incremental counters (90 days TTL)
    - Top N leaderboards
    """

    def __init__(self, redis_client: redis.Redis | None = None):
        self._redis = redis_client
        self._settings = get_settings()
        self._enabled = self._settings.redis_enabled

    async def _get_client(self) -> redis.Redis | None:
        if not self._enabled:
            return None
        if self._redis is None:
            try:
                self._redis = redis.from_url(
                    self._settings.redis_url,
                    encoding="utf-8",
                    decode_responses=True,
                )
            except Exception as e:
                logger.warning(f"[MonitoringCache] Failed to connect to Redis: {e}")
                self._enabled = False
                return None
        return self._redis

    async def incr_counter(
        self,
        tenant_id: str,
        metric: str,
        date: str | None = None,
    ) -> int:
        """
        Increment a counter atomically.

        Key format: stats:{tenant_id}:counter:{metric}:{date}
        TTL: 90 days (7776000 seconds)

        Args:
            tenant_id: Tenant ID for isolation
            metric: Metric name (e.g., 'intent_hit', 'template_use', 'flow_activate', 'guardrail_block')
            date: Date string (YYYY-MM-DD), defaults to today

        Returns:
            New counter value
        """
        client = await self._get_client()
        if client is None:
            return 0

        if date is None:
            date = datetime.utcnow().strftime("%Y-%m-%d")

        key = f"stats:{tenant_id}:counter:{metric}:{date}"

        try:
            count = await client.incr(key)
            await client.expire(key, self._settings.stats_counter_ttl)
            return count
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to increment counter: {e}")
            return 0

    async def incr_counter_by(
        self,
        tenant_id: str,
        metric: str,
        amount: int,
        date: str | None = None,
    ) -> int:
        """
        Increment a counter by a specific amount atomically.

        Key format: stats:{tenant_id}:counter:{metric}:{date}
        TTL: 90 days

        Args:
            tenant_id: Tenant ID for isolation
            metric: Metric name
            amount: Amount to increment by
            date: Date string (YYYY-MM-DD), defaults to today

        Returns:
            New counter value
        """
        client = await self._get_client()
        if client is None:
            return 0

        if date is None:
            date = datetime.utcnow().strftime("%Y-%m-%d")

        key = f"stats:{tenant_id}:counter:{metric}:{date}"

        try:
            count = await client.incrby(key, amount)
            await client.expire(key, self._settings.stats_counter_ttl)
            return count
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to increment counter: {e}")
            return 0

    async def get_counter(
        self,
        tenant_id: str,
        metric: str,
        date: str | None = None,
    ) -> int:
        """
        Get counter value.

        Args:
            tenant_id: Tenant ID for isolation
            metric: Metric name
            date: Date string (YYYY-MM-DD), defaults to today

        Returns:
            Counter value (0 if not found)
        """
        client = await self._get_client()
        if client is None:
            return 0

        if date is None:
            date = datetime.utcnow().strftime("%Y-%m-%d")

        key = f"stats:{tenant_id}:counter:{metric}:{date}"

        try:
            value = await client.get(key)
            return int(value) if value else 0
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to get counter: {e}")
            return 0

    async def get_dashboard_stats(
        self,
        tenant_id: str,
    ) -> dict[str, Any] | None:
        """
        Get cached Dashboard statistics.

        Key format: stats:{tenant_id}:dashboard
        TTL: 60 seconds

        Args:
            tenant_id: Tenant ID for isolation

        Returns:
            Cached stats dict or None if not found
        """
        client = await self._get_client()
        if client is None:
            return None

        key = f"stats:{tenant_id}:dashboard"

        try:
            data = await client.get(key)
            if data:
                return json.loads(data)
            return None
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to get dashboard stats: {e}")
            return None

    async def set_dashboard_stats(
        self,
        tenant_id: str,
        stats: dict[str, Any],
    ) -> bool:
        """
        Set Dashboard statistics cache.

        Args:
            tenant_id: Tenant ID for isolation
            stats: Stats dict to cache

        Returns:
            True if successful, False otherwise
        """
        client = await self._get_client()
        if client is None:
            return False

        key = f"stats:{tenant_id}:dashboard"

        try:
            await client.setex(
                key,
                self._settings.dashboard_cache_ttl,
                json.dumps(stats, default=str),
            )
            return True
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to set dashboard stats: {e}")
            return False

    async def invalidate_dashboard_stats(
        self,
        tenant_id: str,
    ) -> bool:
        """
        Invalidate Dashboard statistics cache.

        Args:
            tenant_id: Tenant ID for isolation

        Returns:
            True if successful, False otherwise
        """
        client = await self._get_client()
        if client is None:
            return False

        key = f"stats:{tenant_id}:dashboard"

        try:
            await client.delete(key)
            return True
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to invalidate dashboard stats: {e}")
            return False

    async def add_to_leaderboard(
        self,
        tenant_id: str,
        leaderboard: str,
        member: str,
        score: float,
    ) -> bool:
        """
        Add/update a member in a leaderboard (sorted set).

        Key format: stats:{tenant_id}:leaderboard:{name}

        Args:
            tenant_id: Tenant ID for isolation
            leaderboard: Leaderboard name (e.g., 'intent_rules', 'templates', 'flows')
            member: Member identifier
            score: Score value

        Returns:
            True if successful
        """
        client = await self._get_client()
        if client is None:
            return False

        key = f"stats:{tenant_id}:leaderboard:{leaderboard}"

        try:
            await client.zadd(key, {member: score})
            await client.expire(key, self._settings.stats_counter_ttl)
            return True
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to add to leaderboard: {e}")
            return False

    async def get_leaderboard(
        self,
        tenant_id: str,
        leaderboard: str,
        limit: int = 5,
        desc: bool = True,
    ) -> list[tuple[str, float]]:
        """
        Get top N members from a leaderboard.

        Args:
            tenant_id: Tenant ID for isolation
            leaderboard: Leaderboard name
            limit: Maximum number of results
            desc: Sort descending (highest first)

        Returns:
            List of (member, score) tuples
        """
        client = await self._get_client()
        if client is None:
            return []

        key = f"stats:{tenant_id}:leaderboard:{leaderboard}"

        try:
            if desc:
                results = await client.zrevrangebyscore(
                    key,
                    min=0,
                    max=float("inf"),
                    start=0,
                    num=limit,
                    withscores=True,
                )
            else:
                results = await client.zrangebyscore(
                    key,
                    min=0,
                    max=float("inf"),
                    start=0,
                    num=limit,
                    withscores=True,
                )
            return results
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to get leaderboard: {e}")
            return []

    async def incr_leaderboard_member(
        self,
        tenant_id: str,
        leaderboard: str,
        member: str,
        increment: float = 1.0,
    ) -> bool:
        """
        Increment a member's score in a leaderboard.

        Args:
            tenant_id: Tenant ID for isolation
            leaderboard: Leaderboard name
            member: Member identifier
            increment: Amount to increment

        Returns:
            True if successful
        """
        client = await self._get_client()
        if client is None:
            return False

        key = f"stats:{tenant_id}:leaderboard:{leaderboard}"

        try:
            await client.zincrby(key, increment, member)
            await client.expire(key, self._settings.stats_counter_ttl)
            return True
        except Exception as e:
            logger.warning(f"[MonitoringCache] Failed to incr leaderboard member: {e}")
            return False

    async def close(self) -> None:
        """Close Redis connection."""
        if self._redis:
            await self._redis.close()


_monitoring_cache: MonitoringCache | None = None


def get_monitoring_cache() -> MonitoringCache:
    """Get singleton MonitoringCache instance."""
    global _monitoring_cache
    if _monitoring_cache is None:
        _monitoring_cache = MonitoringCache()
    return _monitoring_cache
