"""
Thread-safe per-user in-memory permission cache with LRU eviction.
Replaces Casbin's monolithic policy store with granular per-user caching.
"""

import threading
import time
from collections import OrderedDict
from dataclasses import dataclass, field
from typing import Optional, Set, Tuple

from abs_utils.logger import setup_logger

logger = setup_logger(__name__)


@dataclass
class PermissionCacheEntry:
    """Cached permission data for a single user."""
    role_permissions: Set[Tuple[str, str, str]]     # (resource, action, module) from roles
    direct_permissions: Set[Tuple[str, str, str]]    # (resource, action, module) direct
    is_super_admin: bool
    created_at: float = field(default_factory=time.monotonic)
    ttl: float = 300.0


class PermissionCache:
    """
    LRU per-user permission cache with TTL expiration.

    - get_user() returns None on miss or expiry (moves entry to end for LRU)
    - set_user() inserts/updates and evicts LRU if at max_size
    - invalidate_user() removes a single user
    - invalidate_users() removes multiple users
    - invalidate_all() clears everything
    """

    def __init__(self, ttl: float = 300.0, max_size: int = 10_000, sweep_interval: float = 60.0):
        self._user_cache: OrderedDict[str, PermissionCacheEntry] = OrderedDict()
        self._lock = threading.Lock()
        self._ttl = ttl
        self._max_size = max_size
        self._sweep_interval = sweep_interval
        self._running = True
        self._sweep_thread = threading.Thread(target=self._sweep_loop, daemon=True)
        self._sweep_thread.start()

    def get_user(self, user_uuid: str) -> Optional[PermissionCacheEntry]:
        with self._lock:
            entry = self._user_cache.get(user_uuid)
            if entry is None:
                return None
            if (time.monotonic() - entry.created_at) > entry.ttl:
                del self._user_cache[user_uuid]
                return None
            self._user_cache.move_to_end(user_uuid)
            return entry

    def set_user(self, user_uuid: str, entry: PermissionCacheEntry) -> None:
        with self._lock:
            if user_uuid in self._user_cache:
                del self._user_cache[user_uuid]
            elif len(self._user_cache) >= self._max_size:
                self._user_cache.popitem(last=False)
            self._user_cache[user_uuid] = entry

    def invalidate_user(self, user_uuid: str) -> None:
        with self._lock:
            self._user_cache.pop(user_uuid, None)

    def invalidate_users(self, user_uuids: list) -> None:
        with self._lock:
            for uid in user_uuids:
                self._user_cache.pop(uid, None)

    def invalidate_all(self) -> None:
        with self._lock:
            self._user_cache.clear()

    def _sweep_loop(self) -> None:
        while self._running:
            time.sleep(self._sweep_interval)
            if self._running:
                self._sweep_expired()

    def _sweep_expired(self) -> None:
        with self._lock:
            now = time.monotonic()
            expired_keys = [
                uid for uid, entry in self._user_cache.items()
                if (now - entry.created_at) > entry.ttl
            ]
            for uid in expired_keys:
                del self._user_cache[uid]
        if expired_keys:
            logger.debug(f"[RBAC] Cache sweep: removed {len(expired_keys)} expired entries")

    def close(self) -> None:
        self._running = False
