#!/usr/bin/env python3
"""
Configuration file cache module.
Provides asynchronous cached file reads to reduce file I/O overhead under concurrent load.
"""
import asyncio
import os
import json
from typing import Dict, Tuple, Optional, Any
import logging

logger = logging.getLogger('app')


class ConfigFileCache:
    """Configuration file cache.

    Provides a cache based on file modification time to avoid re-reading unchanged files.
    """

    def __init__(self):
        self._cache: Dict[str, Tuple[Any, float]] = {}  # {file_path: (content, mtime)}
        self._lock = asyncio.Lock()

    async def get_text_file(self, file_path: str) -> Optional[str]:
        """Get text file content with caching.

        Args:
            file_path: File path

        Returns:
            File content as a string, or None if the file does not exist or cannot be read
        """
        if not os.path.exists(file_path):
            return None

        current_mtime = os.path.getmtime(file_path)

        # Check whether the cache is still valid without acquiring the lock.
        if file_path in self._cache:
            cached_content, cached_mtime = self._cache[file_path]
            if current_mtime == cached_mtime:
                logger.debug(f"Using cached file: {file_path}")
                return cached_content

        # Read the file and update the cache while holding the lock.
        async with self._lock:
            # Check the cache again in case another coroutine updated it while we were waiting.
            if file_path in self._cache:
                cached_content, cached_mtime = self._cache[file_path]
                if current_mtime == cached_mtime:
                    logger.debug(f"Using cached file: {file_path}")
                    return cached_content

            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                self._cache[file_path] = (content, current_mtime)
                logger.debug(f"Cached file: {file_path}")
                return content
            except Exception as e:
                logger.error(f"Failed to read text file {file_path}: {e}")
                return None

    async def get_json_file(self, file_path: str) -> Optional[Dict]:
        """Get JSON file content with caching.

        Args:
            file_path: JSON file path

        Returns:
            Parsed dictionary, or None if the file does not exist, cannot be read,
            or contains invalid JSON
        """
        content = await self.get_text_file(file_path)
        if content:
            try:
                return json.loads(content)
            except json.JSONDecodeError as e:
                logger.error(f"Failed to parse JSON from {file_path}: {e}")
        return None

    def clear_cache(self, file_path: str = None):
        """Clear the cache.

        Args:
            file_path: File path to clear. If None, clear the entire cache.
        """
        if file_path:
            self._cache.pop(file_path, None)
            logger.debug(f"Cleared file cache: {file_path}")
        else:
            cleared_count = len(self._cache)
            self._cache.clear()
            logger.debug(f"Cleared all cache entries, total files: {cleared_count}")

    def get_cache_stats(self) -> Dict:
        """Get cache statistics.

        Returns:
            Dictionary containing cache statistics
        """
        return {
            "cached_files": len(self._cache),
            "cached_paths": list(self._cache.keys())
        }


# Global cache instance
config_cache = ConfigFileCache()
