#!/usr/bin/env python3
#exonware/xwsystem/src/exonware/xwsystem/caching/secure_cache.py
#exonware/xwsystem/caching/secure_cache.py
"""
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.9.0.23
Generation Date: 01-Nov-2025
Secure cache implementations with validation, integrity checks, and rate limiting.
Security Priority #1 - Production-grade security features.
"""

import time
from typing import Any

from collections.abc import Hashable
from .lru_cache import LRUCache
from .lfu_cache import LFUCache
from .ttl_cache import TTLCache
from .validation import (
    validate_cache_key,
    validate_cache_value,
    DEFAULT_MAX_KEY_SIZE,
    DEFAULT_MAX_VALUE_SIZE_MB,
)
from .integrity import create_secure_entry, verify_entry_integrity, CacheEntry
from .rate_limiter import RateLimiter
from .errors import CacheValidationError, CacheIntegrityError, CacheRateLimitError


class SecureLRUCache(LRUCache):
    """
    LRU Cache with security features.
    Additional security features:
        - Input validation (keys and values)
        - Integrity verification with checksums
        - Rate limiting to prevent DoS
        - Optional mode for performance vs security tradeoff
    """

    def __init__(
        self,
        capacity: int = 128,
        ttl: float | None = None,
        name: str | None = None,
        enable_integrity: bool = True,
        enable_rate_limit: bool = True,
        max_ops_per_second: int = 10000,
        max_key_size: int = DEFAULT_MAX_KEY_SIZE,
        max_value_size_mb: float = DEFAULT_MAX_VALUE_SIZE_MB,
    ):
        """
        Initialize secure LRU cache.
        Args:
            capacity: Maximum cache size
            ttl: Optional TTL in seconds
            name: Cache name for debugging
            enable_integrity: Enable integrity verification
            enable_rate_limit: Enable rate limiting
            max_ops_per_second: Maximum operations per second
            max_key_size: Maximum key size in bytes
            max_value_size_mb: Maximum value size in MB
        """
        super().__init__(capacity, ttl, name)
        self.enable_integrity = enable_integrity
        self.enable_rate_limit = enable_rate_limit
        self.max_key_size = max_key_size
        self.max_value_size_mb = max_value_size_mb
        # Security components
        # Only create rate limiter if explicitly enabled
        # This ensures rate limiting is truly disabled when enable_rate_limit=False
        if self.enable_rate_limit:
            self.rate_limiter = RateLimiter(max_ops_per_second=max_ops_per_second)
        else:
            # Explicitly ensure rate_limiter doesn't exist when disabled
            # This prevents any accidental access
            if hasattr(self, 'rate_limiter'):
                delattr(self, 'rate_limiter')
        # Track integrity violations
        self._integrity_violations = 0

    def _check_rate_limit(self) -> None:
        """
        Check rate limit if enabled.
        Raises:
            CacheRateLimitError: If rate limit is exceeded
        Note:
            This method follows GUIDE_TEST.md by explicitly raising
            errors rather than silently ignoring them. Rate limiting
            is a security feature (Priority #1) and violations are
            reported.
        """
        # Triple-check: only check rate limit if explicitly enabled
        # and rate limiter exists (defensive programming)
        # This ensures rate limiting is truly disabled when enable_rate_limit=False
        if not self.enable_rate_limit:
            return  # Early return if rate limiting is disabled
        if not hasattr(self, 'rate_limiter'):
            return  # Early return if rate limiter doesn't exist
        # Rate limiter will raise CacheRateLimitError if limit exceeded
        # We let it propagate to caller for proper error handling
        self.rate_limiter.acquire()

    def put(self, key: Hashable, value: Any) -> None:
        """
        Put value with security checks.
        Args:
            key: Cache key
            value: Value to cache
        Raises:
            CacheValidationError: If validation fails
            CacheRateLimitError: If rate limit exceeded
        """
        # Rate limiting - only check if explicitly enabled
        # This ensures rate limiting is truly disabled when enable_rate_limit=False
        if self.enable_rate_limit:
            self._check_rate_limit()
        # Validate key
        validate_cache_key(key, max_size=self.max_key_size)
        # Validate value
        validate_cache_value(value, max_size_mb=self.max_value_size_mb)
        # Store with integrity if enabled
        if self.enable_integrity:
            # Wrap value in secure entry
            entry = create_secure_entry(
                key=key,
                value=value,
                created_at=time.time()
            )
            super().put(key, entry)
        else:
            super().put(key, value)

    def get(self, key: Hashable, default: Any = None) -> Any:
        """
        Get value with integrity verification.
        Args:
            key: Cache key
            default: Default value if not found
        Returns:
            Cached value or default
        Raises:
            CacheIntegrityError: If integrity check fails
        """
        # Rate limiting - only check if explicitly enabled
        # This ensures rate limiting is truly disabled when enable_rate_limit=False
        if self.enable_rate_limit:
            self._check_rate_limit()
        # Check if key exists in cache first to handle None values correctly
        # The parent's get() method treats None as "not found", so we need to
        # check existence directly to distinguish between "key not found" and "value is None"
        with self._lock:
            if key not in self._cache:
                return default
            # Key exists - get the node to access the value
            node = self._cache[key]
            # Check TTL if enabled
            if self.ttl and time.time() - node.access_time > self.ttl:
                self._remove_node(node)
                del self._cache[key]
                return default
            # Move to head (most recently used) - use parent's method
            self._move_to_head(node)
            node.access_time = time.time()
            self._hits += 1
            # Get the actual value from the node
            result = node.value
        # Verify integrity if enabled
        if self.enable_integrity:
            # If result is a CacheEntry, verify and extract value
            if isinstance(result, CacheEntry):
                try:
                    verify_entry_integrity(result)
                    result.access_count += 1
                    return result.value
                except CacheIntegrityError as e:
                    # Log violation and remove corrupted entry
                    self._integrity_violations += 1
                    self.delete(key)
                    raise e
            else:
                # If integrity is enabled but result is not a CacheEntry,
                # this might be a legacy entry or corruption - return as-is
                return result
        # Integrity disabled - return value directly (including None)
        return result

    def get_security_stats(self) -> dict:
        """
        Get security-related statistics.
        Returns:
            Dictionary with security stats
        """
        stats = {
            'enable_integrity': self.enable_integrity,
            'enable_rate_limit': self.enable_rate_limit,
            'integrity_violations': self._integrity_violations,
            'max_key_size': self.max_key_size,
            'max_value_size_mb': self.max_value_size_mb,
        }
        if self.enable_rate_limit:
            stats['rate_limiter'] = self.rate_limiter.get_stats()
        return stats


class SecureLFUCache(LFUCache):
    """
    LFU Cache with security features.
    Additional security features:
        - Input validation (keys and values)
        - Rate limiting to prevent DoS
    """

    def __init__(
        self,
        capacity: int = 128,
        name: str | None = None,
        enable_rate_limit: bool = True,
        max_ops_per_second: int = 10000,
        max_key_size: int = DEFAULT_MAX_KEY_SIZE,
        max_value_size_mb: float = DEFAULT_MAX_VALUE_SIZE_MB,
    ):
        """
        Initialize secure LFU cache.
        Args:
            capacity: Maximum cache size
            name: Cache name for debugging
            enable_rate_limit: Enable rate limiting
            max_ops_per_second: Maximum operations per second
            max_key_size: Maximum key size in bytes
            max_value_size_mb: Maximum value size in MB
        """
        super().__init__(capacity, name)
        self.enable_rate_limit = enable_rate_limit
        self.max_key_size = max_key_size
        self.max_value_size_mb = max_value_size_mb
        if self.enable_rate_limit:
            self.rate_limiter = RateLimiter(max_ops_per_second=max_ops_per_second)

    def _check_rate_limit(self) -> None:
        """Check rate limit if enabled."""
        if self.enable_rate_limit:
            self.rate_limiter.acquire()

    def put(self, key: Hashable, value: Any) -> None:
        """Put value with security checks."""
        self._check_rate_limit()
        validate_cache_key(key, max_size=self.max_key_size)
        validate_cache_value(value, max_size_mb=self.max_value_size_mb)
        super().put(key, value)

    def get(self, key: Hashable, default: Any = None) -> Any:
        """Get value with rate limiting."""
        self._check_rate_limit()
        return super().get(key, default)


class SecureTTLCache(TTLCache):
    """
    TTL Cache with security features.
    Additional security features:
        - Input validation (keys and values)
        - Rate limiting to prevent DoS
    """

    def __init__(
        self,
        capacity: int = 128,
        ttl: float = 300.0,
        cleanup_interval: float = 60.0,
        name: str = "secure_ttl_cache",
        enable_rate_limit: bool = True,
        max_ops_per_second: int = 10000,
        max_key_size: int = DEFAULT_MAX_KEY_SIZE,
        max_value_size_mb: float = DEFAULT_MAX_VALUE_SIZE_MB,
    ):
        """
        Initialize secure TTL cache.
        Args:
            capacity: Maximum cache size
            ttl: Time to live in seconds
            cleanup_interval: Cleanup interval in seconds
            name: Cache name for debugging
            enable_rate_limit: Enable rate limiting
            max_ops_per_second: Maximum operations per second
            max_key_size: Maximum key size in bytes
            max_value_size_mb: Maximum value size in MB
        """
        super().__init__(capacity, ttl, cleanup_interval, name)
        self.enable_rate_limit = enable_rate_limit
        self.max_key_size = max_key_size
        self.max_value_size_mb = max_value_size_mb
        if self.enable_rate_limit:
            self.rate_limiter = RateLimiter(max_ops_per_second=max_ops_per_second)

    def _check_rate_limit(self) -> None:
        """Check rate limit if enabled."""
        if self.enable_rate_limit:
            self.rate_limiter.acquire()

    def put(self, key: str, value: Any, ttl: float | None = None) -> bool:
        """Put value with security checks."""
        self._check_rate_limit()
        validate_cache_key(key, max_size=self.max_key_size)
        validate_cache_value(value, max_size_mb=self.max_value_size_mb)
        return super().put(key, value, ttl)

    def get(self, key: str, default: Any = None) -> Any:
        """Get value with rate limiting."""
        self._check_rate_limit()
        return super().get(key, default)
__all__ = [
    'SecureLRUCache',
    'SecureLFUCache',
    'SecureTTLCache',
]
