"""
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, timedelta
from zoneinfo import ZoneInfo

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

logger = logging.getLogger(__name__)


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

        self._memory_storage: dict[str, int] = {}
        self._memory_date: str = ""

        self.guest_limit = RATE_LIMIT_GUEST  # Default: 10
        self.user_limit = RATE_LIMIT_USER  # Default: 100

        MessageLimitService._initialized = True
        logger.info(
            f"✅ MessageLimitService initialized (Guest Limit: {self.guest_limit}, User Limit: {self.user_limit})"
        )

    def _get_today_key(self) -> str:
        """Get today's date key (format: YYYY-MM-DD)."""
        now = datetime.now(ZoneInfo("Asia/Ho_Chi_Minh"))
        return 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 }
        Redis key sẽ có dạng "msg_limit:2026-02-04:csdcsc"
        """
        today = self._get_today_key()
        return f"{MESSAGE_COUNT_PREFIX}{today}:{identity_key}"

    def _get_seconds_until_reset(self) -> int:
        """
        Get seconds until next reset time (00:00 Asia/Ho_Chi_Minh).
        """
        tz = ZoneInfo("Asia/Ho_Chi_Minh")
        now = datetime.now(tz)
        reset_time = now.replace(hour=0, minute=0, second=0, microsecond=0)
        if now >= reset_time:
            reset_time = reset_time + timedelta(days=1)

        return int((reset_time - now).total_seconds())

    async def _get_reset_seconds(self, identity_key: str) -> int:
        """Get remaining TTL from Redis if available, else fallback."""
        try:
            client = redis_cache.get_client()
            if client:
                redis_key = self._get_redis_key(identity_key)
                ttl = await client.ttl(redis_key)
                if ttl and ttl > 0:
                    return int(ttl)
        except Exception as e:
            logger.warning(f"Redis ttl error: {e}")

        return self._get_seconds_until_reset()

    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}")

    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.
        Hash format structure:
        Redis Key: "msg_limit:2026-02-04:csdcsc"
                        ├── guest: 3
                        ├── user: 5
                        └── ...
        """
        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)

            new_val = await client.hincrby(redis_key, field, 1)

            # Set TTL nếu mới tạo key - Reset đúng lúc 00:00 hằng ngày
            if new_val == 1:
                ttl = await client.ttl(redis_key)
                if ttl < 0:  # Chưa có TTL
                    # Set expire = seconds until next reset (00:00)
                    seconds_until_reset = self._get_seconds_until_reset()
                    await client.expire(redis_key, seconds_until_reset)

            return new_val

        except Exception as e:
            logger.warning(f"Redis increment error: {e}")
            return None

    async def check_limit(
        self,
        identity_key: str,
        is_authenticated: bool,
    ) -> tuple[bool, dict]:
        """
        Check logic (SEPARATE per identity type):
        - User (đã login): user_used >= 100 → Block (Quota riêng: 100 tin/ngày)
        - Guest (chưa login): guest_used >= 10 → Block (Quota riêng: 10 tin/ngày)
        """
        reset_seconds = await self._get_reset_seconds(identity_key)

        # 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 (SEPARATE quotas)
        can_send = True
        limit_display = self.user_limit
        message = ""
        require_login = False

        # ✅ User Limit (separate - chỉ check user_used)
        if is_authenticated and user_used >= self.user_limit:
            can_send = False
            message = "Bạn đã sử dụng hết tin nhắn hôm nay. Vui lòng quay lại vào hôm sau để dùng tiếp!"

        # ✅ Guest Limit (separate - chỉ check guest_used)
        elif not is_authenticated and guest_used >= self.guest_limit:
            can_send = False
            limit_display = self.guest_limit
            require_login = True
            message = "Bạn đã sử dụng hết tin nhắn hôm nay. Đăng nhập ngay để dùng tiếp: https://canifa.com/login"

        # 3. Build Remaining Info
        remaining = max(0, self.user_limit - user_used) if is_authenticated else max(0, self.guest_limit - guest_used)

        # Determine Error Code
        error_code = None
        if not can_send:
            error_code = "GUEST_LIMIT_EXCEEDED" if require_login else "USER_LIMIT_EXCEEDED"

        info = {
            "limit": limit_display,
            "used": user_used if is_authenticated else guest_used,
            "total_used": total_used,
            "guest_used": guest_used,
            "user_used": user_used,
            "remaining": remaining,
            "reset_seconds": reset_seconds,
            "is_authenticated": is_authenticated,
            "require_login": require_login,
            "message": message,
            "error_code": error_code,
        }

        return can_send, info

    async def increment(self, identity_key: str, is_authenticated: bool) -> dict:
        """
        Increment field tương ứng (guest hoặc user).
        ⚡ Optimized: Tránh duplicate Redis call (HINCRBY + HGETALL)
        """
        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
            new_val = self._memory_storage[identity_key][field]

        try:
            client = redis_cache.get_client()
            other_field = "guest" if field == "user" else "user"
            other_val = 0

            if client:
                redis_key = self._get_redis_key(identity_key)
                other_val_raw = await client.hget(redis_key, other_field)
                other_val = int(other_val_raw) if other_val_raw else 0

            # Calculate counts
            guest_used = new_val if field == "guest" else other_val
            user_used = new_val if field == "user" else other_val
            total_used = guest_used + user_used

            # Calculate remaining
            limit_display = self.user_limit if is_authenticated else self.guest_limit
            if is_authenticated:
                remaining = max(0, self.user_limit - total_used)
            else:
                guest_remaining = max(0, self.guest_limit - guest_used)
                user_remaining = max(0, self.user_limit - total_used)
                remaining = min(guest_remaining, user_remaining)

            reset_seconds = await self._get_reset_seconds(identity_key)
            info = {
                "limit": limit_display,
                "used": total_used if is_authenticated else guest_used,
                "total_used": total_used,
                "guest_used": guest_used,
                "user_used": user_used,
                "remaining": remaining,
                "reset_seconds": reset_seconds,
                "is_authenticated": is_authenticated,
            }

        except Exception as e:
            # Fallback to check_limit if calculation fails
            logger.warning(f"Increment calculation failed, using check_limit: {e}")
            _, 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()
