"""
Message Limit Service
Giới hạn số tin nhắn theo ngày:
- Guest (không login): RATE_LIMIT_GUEST tin/ngày theo device_id
- User đã login: RATE_LIMIT_USER tin/ngày theo user_id

Lưu trữ: Redis (dùng chung với cache.py)
"""
from __future__ import annotations

import logging
from datetime import datetime

from common.cache import redis_cache
from config import RATE_LIMIT_GUEST, RATE_LIMIT_USER

logger = logging.getLogger(__name__)


# =============================================================================
# CONFIGURATION (from config.py)
# =============================================================================

# Redis key prefix
MESSAGE_COUNT_PREFIX = "msg_limit:"

class MessageLimitService:
    """
    Service quản lý giới hạn tin nhắn theo ngày.
    Dùng Redis để lưu trữ, tự động reset mỗi ngày.
    
    Limits:
    - Guest (device_id): RATE_LIMIT_GUEST (default: 10)
    - User (user_id): RATE_LIMIT_USER (default: 100)
    
    Usage:
        from common.message_limit import message_limit_service
        
        # Check trước khi chat
        can_send, info = await message_limit_service.check_limit(
            identity_key="device:abc123",  # hoặc "user:123"
            is_authenticated=False
        )
        
        if not can_send:
            return {"error": info["message"], ...}
        
        # Sau khi chat thành công
        await message_limit_service.increment(identity_key, is_authenticated)
    """
    
    _instance: MessageLimitService | None = None
    _initialized: bool = False
    
    def __new__(cls) -> MessageLimitService:
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self) -> None:
        if MessageLimitService._initialized:
            return
        
        # Fallback in-memory storage: { "identity_key": count }
        self._memory_storage: dict[str, int] = {}
        self._memory_date: str = ""
        
        # Limits from config
        self.guest_limit = RATE_LIMIT_GUEST  # Default: 10
        self.user_limit = RATE_LIMIT_USER    # Default: 100
        
        MessageLimitService._initialized = True
        logger.info(
            f"✅ MessageLimitService initialized "
            f"(Guest Limit: {self.guest_limit}, User Limit: {self.user_limit})"
        )
    
    # =========================================================================
    # HELPER METHODS
    # =========================================================================
    
    def _get_today_key(self) -> str:
        """Get today's date key (format: YYYY-MM-DD)."""
        return datetime.now().strftime("%Y-%m-%d")
    
    def _get_redis_key(self, identity_key: str) -> str:
        """
        Build Redis key.
        Format: msg_limit:2026-01-17:device_id
        Structure: Hash { "guest": int, "user": int }
        """
        today = self._get_today_key()
        return f"{MESSAGE_COUNT_PREFIX}{today}:{identity_key}"
    
    def _get_seconds_until_midnight(self) -> int:
        """
        Get seconds until next midnight (00:00).
        """
        from datetime import timedelta
        
        now = datetime.now()
        tomorrow = now.date() + timedelta(days=1)
        midnight = datetime.combine(tomorrow, datetime.min.time())
        
        return int((midnight - now).total_seconds())
    
    def _reset_memory_if_new_day(self) -> None:
        """Reset in-memory storage nếu qua ngày mới."""
        today = self._get_today_key()
        if self._memory_date != today:
            self._memory_storage.clear()
            self._memory_date = today
            logger.debug(f"🔄 Memory storage reset for new day: {today}")
    
    # =========================================================================
    # REDIS OPERATIONS
    # =========================================================================
    
    async def _get_counts_from_redis(self, identity_key: str) -> dict[str, int] | None:
        """
        Get all counts (guest, user) từ Redis Hash.
        Returns: {"guest": int, "user": int} hoặc None nếu lỗi Redis.
        """
        try:
            client = redis_cache.get_client()
            if not client:
                return None
            
            redis_key = self._get_redis_key(identity_key)
            # HGETALL trả về dict {b'guest': b'1', ...}
            data = await client.hgetall(redis_key)
            
            # Parse data
            counts = {"guest": 0, "user": 0}
            if data:
                # Redis trả về bytes trong dict keys/values
                counts["guest"] = int(data.get("guest") or data.get(b"guest") or 0)
                counts["user"] = int(data.get("user") or data.get(b"user") or 0)
            
            return counts
            
        except Exception as e:
            logger.warning(f"Redis get counts error: {e}")
            return None
    
    async def _increment_in_redis(self, identity_key: str, field: str) -> int | None:
        """
        Increment specific field ('guest' or 'user') trong Redis Hash.
        """
        try:
            client = redis_cache.get_client()
            if not client:
                return None
            
            redis_key = self._get_redis_key(identity_key)
            
            # HINCRBY field 1
            new_val = await client.hincrby(redis_key, field, 1)
            
            # Set TTL message nếu key mới tạo (coi như mới nếu chỉ có 1 field vừa set = 1)
            # Tuy nhiên để chắc chắn, cứ set expire với nx=True hoặc check ttl
            # Đơn giản nhất: Nếu new_val == 1 -> Có thể là key mới, set TTL
            if new_val == 1:
                ttl = await client.ttl(redis_key)
                if ttl < 0: # Chưa có TTL
                     await client.expire(redis_key, 48 * 3600)
            
            return new_val
            
        except Exception as e:
            logger.warning(f"Redis increment error: {e}")
            return None
    
    # =========================================================================
    # PUBLIC METHODS
    # =========================================================================
    
    async def check_limit(
        self,
        identity_key: str,
        is_authenticated: bool,
    ) -> tuple[bool, dict]:
        """
        Check logic:
        - Total (guest + user) < 100
        - Nếu Guest: thêm điều kiện guest < 10
        """
        reset_seconds = self._get_seconds_until_midnight()
        
        # 1. Get counts
        counts = await self._get_counts_from_redis(identity_key)
        
        # Fallback memory
        if counts is None:
            self._reset_memory_if_new_day()
            counts = self._memory_storage.get(identity_key, {"guest": 0, "user": 0})
        
        guest_used = counts.get("guest", 0)
        user_used = counts.get("user", 0)
        total_used = guest_used + user_used
        
        # 2. Logic Checking
        can_send = True
        limit_display = self.user_limit
        message = ""
        require_login = False
        
        # Check User Limit (Hard limit cho identity)
        if total_used >= self.user_limit:
            can_send = False
            # Thông báo khi hết tổng quota (dù là user hay guest)
            if is_authenticated:
                message = f"Bạn đã sử dụng hết {self.user_limit} tin nhắn hôm nay. Quay lại vào ngày mai nhé!"
            else:
                # Guest dùng hết user_limit tin (hiếm, vì guest bị chặn ở guest_limit rồi)
                message = f"Thiết bị này đã đạt giới hạn {self.user_limit} tin nhắn hôm nay."
        
        # Check Guest Limit (nếu chưa login và chưa bị chặn bởi total)
        elif not is_authenticated:
            limit_display = self.guest_limit
            if guest_used >= self.guest_limit:
                can_send = False
                require_login = True
                message = (
                    f"Bạn đã dùng hết {self.guest_limit} tin nhắn miễn phí. "
                    f"Đăng nhập ngay để dùng tiếp (tối đa {self.user_limit} tin/ngày)!"
                )
        
        # 3. Build Remaining Info
        # Nếu là guest: remaining = min(guest_remaining, user_remaining)
        # Thực ra guest chỉ care guest_remaining vì guest < user
        if is_authenticated:
             remaining = max(0, self.user_limit - total_used)
        else:
             # Guest bị chặn bởi guest_limit hoặc user_limit (trường hợp login rồi logout)
             guest_remaining = max(0, self.guest_limit - guest_used)
             user_remaining = max(0, self.user_limit - total_used)
             remaining = min(guest_remaining, user_remaining)

        info = {
            "limit": limit_display,
            "used": total_used if is_authenticated else guest_used, # Show cái user quan tâm
            "total_used": total_used,     # Info thêm để debug/admin
            "guest_used": guest_used,
            "user_used": user_used,
            "remaining": remaining,
            "reset_seconds": reset_seconds,
            "is_authenticated": is_authenticated,
            "require_login": require_login,
            "message": message,
        }
        
        return can_send, info
    
    async def increment(self, identity_key: str, is_authenticated: bool) -> dict:
        """
        Increment field tương ứng (guest hoặc user).
        """
        field = "user" if is_authenticated else "guest"
        
        # Redis Increment
        new_val = await self._increment_in_redis(identity_key, field)
        
        # Memory Fallback
        if new_val is None:
            self._reset_memory_if_new_day()
            if identity_key not in self._memory_storage:
                self._memory_storage[identity_key] = {"guest": 0, "user": 0}
            
            self._memory_storage[identity_key][field] += 1
        
        # Trả về info mới nhất (gọi lại check_limit để đồng bộ logic tính toán)
        # Tuy nhiên để tối ưu performance, ta tự tính lại nhanh cũng được. 
        # Nhưng gọi check_limit an toàn hơn cho đồng nhất output structure.
        _, info = await self.check_limit(identity_key, is_authenticated)
        
        logger.debug(
            f"📈 Incr {field}: {identity_key} | "
            f"Guest:{info['guest_used']} User:{info['user_used']} Total:{info['total_used']}"
        )
        
        return info
    
    async def get_usage(self, identity_key: str, is_authenticated: bool) -> dict:
        """Wrapper gọi check_limit để lấy info (nhưng bỏ qua bool result)"""
        _, info = await self.check_limit(identity_key, is_authenticated)
        return info
    
    async def reset(self, identity_key: str) -> bool:
        """Manually reset count (delete key)."""
        try:
            client = redis_cache.get_client()
            if client:
                redis_key = self._get_redis_key(identity_key)
                await client.delete(redis_key)
            
            self._memory_storage.pop(identity_key, None)
            logger.info(f"🔄 Manual reset for {identity_key}")
            return True
        except Exception as e:
            logger.error(f"Reset error: {e}")
            return False


# =============================================================================
# SINGLETON INSTANCE
# =============================================================================

message_limit_service = MessageLimitService()
