"""
Cache directory service for MarkiTect - Convention over Configuration.

This module provides standardized cache directory resolution following best practices:
- Project-local cache in .ast_cache/ (like .git/, node_modules/)
- Respects XDG Base Directory Specification for system caches
- Provides fallback behavior for different environments
"""

import os
from pathlib import Path
from typing import Optional


class CacheDirectoryService:
    """
    Service for resolving cache directory locations following conventions.

    Convention over Configuration approach:
    1. Project-local: .ast_cache/ in current working directory (default)
    2. User cache: ~/.cache/markitect/ (XDG Base Directory compliant)
    3. System temp: /tmp/markitect-cache/ (fallback)
    """

    def get_cache_directory(self, prefer_local: bool = True) -> Path:
        """
        Get cache directory following convention over configuration.

        Args:
            prefer_local: Whether to prefer project-local cache over user cache

        Returns:
            Path to cache directory (created if needed)
        """
        if prefer_local:
            # Project-local cache (like .git/, node_modules/)
            cache_dir = Path.cwd() / ".ast_cache"
        else:
            # User cache following XDG Base Directory Specification
            cache_dir = self._get_user_cache_directory()

        # Ensure directory exists
        cache_dir.mkdir(parents=True, exist_ok=True)
        return cache_dir

    def get_project_cache_directory(self) -> Path:
        """Get project-local cache directory (.ast_cache in current directory)."""
        cache_dir = Path.cwd() / ".ast_cache"
        cache_dir.mkdir(parents=True, exist_ok=True)
        return cache_dir

    def get_user_cache_directory(self) -> Path:
        """Get user cache directory following XDG Base Directory Specification."""
        cache_dir = self._get_user_cache_directory()
        cache_dir.mkdir(parents=True, exist_ok=True)
        return cache_dir

    def _get_user_cache_directory(self) -> Path:
        """Get user cache directory path (XDG compliant)."""
        # Follow XDG Base Directory Specification
        xdg_cache_home = os.environ.get('XDG_CACHE_HOME')
        if xdg_cache_home:
            return Path(xdg_cache_home) / "markitect"
        else:
            # Fallback: ~/.cache/markitect (Linux/macOS) or equivalent
            return Path.home() / ".cache" / "markitect"

    def find_cache_files(self, cache_dir: Optional[Path] = None) -> list[Path]:
        """
        Find all AST cache files in specified or default cache directory.

        Args:
            cache_dir: Cache directory to search (defaults to project cache)

        Returns:
            List of cache file paths
        """
        if cache_dir is None:
            cache_dir = self.get_project_cache_directory()

        if not cache_dir.exists():
            return []

        return list(cache_dir.glob("*.ast.json"))

    def get_cache_stats(self, cache_dir: Optional[Path] = None) -> dict:
        """
        Get cache statistics for specified or default cache directory.

        Args:
            cache_dir: Cache directory to analyze (defaults to project cache)

        Returns:
            Dictionary with cache statistics
        """
        if cache_dir is None:
            cache_dir = self.get_project_cache_directory()

        cache_files = self.find_cache_files(cache_dir)

        if not cache_files:
            return {
                'directory': str(cache_dir.absolute()),
                'exists': cache_dir.exists(),
                'total_files': 0,
                'total_size': 0,
                'size_formatted': '0 bytes'
            }

        total_size = sum(f.stat().st_size for f in cache_files)

        # Format size in appropriate units
        if total_size < 1024:
            size_formatted = f"{total_size} bytes"
        elif total_size < 1024 * 1024:
            size_formatted = f"{total_size / 1024:.1f} KB"
        else:
            size_formatted = f"{total_size / (1024 * 1024):.1f} MB"

        return {
            'directory': str(cache_dir.absolute()),
            'exists': cache_dir.exists(),
            'total_files': len(cache_files),
            'total_size': total_size,
            'size_formatted': size_formatted,
            'files': [str(f) for f in cache_files]
        }

    def clean_cache(self, cache_dir: Optional[Path] = None) -> dict:
        """
        Clean all cache files from specified or default cache directory.

        Args:
            cache_dir: Cache directory to clean (defaults to project cache)

        Returns:
            Dictionary with cleaning results
        """
        if cache_dir is None:
            cache_dir = self.get_project_cache_directory()

        if not cache_dir.exists():
            return {
                'success': True,
                'message': 'Cache directory does not exist - nothing to clean',
                'files_removed': 0
            }

        cache_files = self.find_cache_files(cache_dir)

        if not cache_files:
            return {
                'success': True,
                'message': 'Cache is already empty - nothing to clean',
                'files_removed': 0
            }

        removed_count = 0
        errors = []

        for cache_file in cache_files:
            try:
                cache_file.unlink()
                removed_count += 1
            except (OSError, PermissionError) as e:
                errors.append(f"Could not remove {cache_file}: {e}")
            except Exception as e:
                # Log unexpected errors but continue cleanup
                from infrastructure.logging import get_logger
                logger = get_logger(__name__)
                logger.warning(
                    f"Unexpected error removing cache file {cache_file}: {e}"
                )
                errors.append(f"Unexpected error removing {cache_file}: {e}")

        if errors:
            return {
                'success': False,
                'message': f"Removed {removed_count} files with {len(errors)} errors",
                'files_removed': removed_count,
                'errors': errors
            }
        else:
            return {
                'success': True,
                'message': f"Cache cleaned successfully - removed {removed_count} file(s)",
                'files_removed': removed_count
            }

    def invalidate_file_cache(self, file_path: str, cache_dir: Optional[Path] = None) -> dict:
        """
        Invalidate cache for specific file.

        Args:
            file_path: Path to file whose cache should be invalidated
            cache_dir: Cache directory to search (defaults to project cache)

        Returns:
            Dictionary with invalidation results
        """
        if cache_dir is None:
            cache_dir = self.get_project_cache_directory()

        source_path = Path(file_path)
        cache_filename = f"{source_path.name}.ast.json"
        cache_file = cache_dir / cache_filename

        if not cache_file.exists():
            return {
                'success': True,
                'message': f'No cache found for {source_path.name} - nothing to invalidate',
                'file_removed': False
            }

        try:
            cache_file.unlink()
            return {
                'success': True,
                'message': f'Cache invalidated for {source_path.name}',
                'file_removed': True,
                'cache_file': str(cache_file)
            }
        except (OSError, PermissionError) as e:
            return {
                'success': False,
                'message': f'File system error removing cache for {source_path.name}: {e}',
                'file_removed': False,
                'error': str(e)
            }
        except Exception as e:
            from infrastructure.logging import get_logger
            logger = get_logger(__name__)
            logger.error(
                f"Unexpected error removing cache for {source_path.name}: {e}",
                exc_info=True
            )
            return {
                'success': False,
                'message': f'Unexpected error removing cache for {source_path.name}: {e}',
                'file_removed': False,
                'error': str(e)
            }