"""
Tool Result Caching for groknroll.

Caches results from read-only tools (read, grep, glob, list) with file system watching
for automatic invalidation.

Example:
    cache = ToolCache()

    # Cache tool result
    await cache.set_result("read", {"path": "/path/to/file"}, file_content)

    # Get cached result
    result = await cache.get_result("read", {"path": "/path/to/file"})

    # Invalidate on file change
    cache.invalidate_path("/path/to/file")
"""

import hashlib
import json
from pathlib import Path
from typing import Any, Optional, Set

from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer

from groknroll.core.cache import get_cache_manager


class FileSystemWatcher(FileSystemEventHandler):
    """
    Watch file system for changes and invalidate cache.
    """

    def __init__(self, cache: "ToolCache"):
        """
        Initialize file system watcher.

        Args:
            cache: ToolCache instance to notify of changes
        """
        self.cache = cache

    def on_modified(self, event):
        """Handle file modification."""
        if not event.is_directory:
            self.cache.invalidate_path(event.src_path)

    def on_created(self, event):
        """Handle file creation."""
        if not event.is_directory:
            self.cache.invalidate_path(event.src_path)

    def on_deleted(self, event):
        """Handle file deletion."""
        if not event.is_directory:
            self.cache.invalidate_path(event.src_path)

    def on_moved(self, event):
        """Handle file move."""
        if not event.is_directory:
            self.cache.invalidate_path(event.src_path)
            self.cache.invalidate_path(event.dest_path)


class ToolCache:
    """
    Cache for tool execution results.

    Caches results from read-only tools and invalidates on file system changes.
    """

    # Tools whose results can be cached
    CACHEABLE_TOOLS = {
        "read",  # File reading
        "grep",  # Text search
        "glob",  # File pattern matching
        "list",  # Directory listing
        "git",  # Git operations (some)
    }

    def __init__(self, watch_directories: Optional[list[str]] = None):
        """
        Initialize tool cache.

        Args:
            watch_directories: Directories to watch for file changes
        """
        self.cache_manager = get_cache_manager()
        self.watched_paths: Set[str] = set()
        self.observer: Optional[Observer] = None

        # Start file system watcher
        if watch_directories:
            self.start_watching(watch_directories)

    def _get_cache_key(self, tool_name: str, params: dict[str, Any]) -> str:
        """
        Generate cache key from tool name and parameters.

        Args:
            tool_name: Name of the tool
            params: Tool parameters

        Returns:
            Cache key string
        """
        # Sort params for consistent hashing
        params_json = json.dumps(params, sort_keys=True)
        params_hash = hashlib.sha256(params_json.encode()).hexdigest()[:16]

        return f"tool:{tool_name}:{params_hash}"

    def is_cacheable(self, tool_name: str) -> bool:
        """
        Check if tool results can be cached.

        Args:
            tool_name: Name of the tool

        Returns:
            True if cacheable
        """
        return tool_name in self.CACHEABLE_TOOLS

    async def get_result(
        self, tool_name: str, params: dict[str, Any]
    ) -> Optional[Any]:
        """
        Get cached tool result.

        Args:
            tool_name: Name of the tool
            params: Tool parameters

        Returns:
            Cached result or None if not found/expired
        """
        if not self.is_cacheable(tool_name):
            return None

        cache_key = self._get_cache_key(tool_name, params)
        return await self.cache_manager.get(cache_key)

    async def set_result(
        self,
        tool_name: str,
        params: dict[str, Any],
        result: Any,
        ttl: Optional[int] = 300,
    ):
        """
        Cache tool result.

        Args:
            tool_name: Name of the tool
            params: Tool parameters
            result: Tool result to cache
            ttl: Time-to-live in seconds (default: 5 minutes)
        """
        if not self.is_cacheable(tool_name):
            return

        cache_key = self._get_cache_key(tool_name, params)
        await self.cache_manager.set(cache_key, result, ttl=ttl)

        # Track file paths for invalidation
        if "path" in params:
            path = params["path"]
            if isinstance(path, (str, Path)):
                self.watched_paths.add(str(Path(path).resolve()))

    def invalidate_path(self, file_path: str):
        """
        Invalidate all cache entries related to a file path.

        Args:
            file_path: Path to file that changed
        """
        # This is a simplified version - a production implementation
        # would need to track path-to-key mappings
        resolved_path = str(Path(file_path).resolve())

        if resolved_path in self.watched_paths:
            # In a real implementation, we'd invalidate specific keys
            # For now, this marks the path as changed
            self.watched_paths.discard(resolved_path)

    def start_watching(self, directories: list[str]):
        """
        Start watching directories for file changes.

        Args:
            directories: List of directory paths to watch
        """
        if self.observer is not None:
            return  # Already watching

        self.observer = Observer()
        handler = FileSystemWatcher(self)

        for directory in directories:
            if Path(directory).exists():
                self.observer.schedule(handler, directory, recursive=True)

        self.observer.start()

    def stop_watching(self):
        """Stop watching file system."""
        if self.observer is not None:
            self.observer.stop()
            self.observer.join()
            self.observer = None

    def get_stats(self) -> dict[str, Any]:
        """Get cache statistics."""
        return {
            "cache_stats": self.cache_manager.get_stats(),
            "watched_paths": len(self.watched_paths),
            "watching": self.observer is not None and self.observer.is_alive(),
        }


# Global tool cache instance
_tool_cache: Optional[ToolCache] = None


def get_tool_cache() -> ToolCache:
    """Get or create global tool cache."""
    global _tool_cache
    if _tool_cache is None:
        _tool_cache = ToolCache()
    return _tool_cache
