"""Local cache for dynamically built tools."""

from __future__ import annotations

import hashlib
import json
from datetime import datetime, timedelta
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from promethian.core.types import Tool


class ToolCache:
    """
    Caches dynamically built tools to avoid rebuilding.

    Tools are cached by a hash of their description, so identical
    tool requests can be served from cache without Claude calls.
    """

    # Cache expiry (tools older than this will be rebuilt)
    DEFAULT_TTL_DAYS = 7

    def __init__(
        self,
        cache_dir: Path | None = None,
        ttl_days: int = DEFAULT_TTL_DAYS,
    ):
        """
        Initialize the tool cache.

        Args:
            cache_dir: Directory for cache files. Defaults to ~/.promethian/tool_cache/
            ttl_days: Days until cached tools expire
        """
        self.cache_dir = cache_dir or (Path.home() / ".promethian" / "tool_cache")
        self.ttl = timedelta(days=ttl_days)
        self._ensure_cache_dir()

    def _ensure_cache_dir(self) -> None:
        """Ensure cache directory exists."""
        self.cache_dir.mkdir(parents=True, exist_ok=True)

    def _hash_description(self, description: str) -> str:
        """Create a hash of the tool description for cache key."""
        # Normalize whitespace and lowercase for consistent hashing
        normalized = " ".join(description.lower().split())
        return hashlib.sha256(normalized.encode()).hexdigest()[:16]

    def _cache_path(self, description: str) -> Path:
        """Get the cache file path for a tool description."""
        hash_key = self._hash_description(description)
        return self.cache_dir / f"{hash_key}.json"

    def get(self, description: str) -> "Tool | None":
        """
        Get a cached tool by description.

        Args:
            description: Tool description that was used to build it

        Returns:
            The cached Tool if found and not expired, None otherwise
        """
        from promethian.core.types import Tool

        cache_path = self._cache_path(description)

        if not cache_path.exists():
            return None

        try:
            data = json.loads(cache_path.read_text())

            # Check expiry
            cached_at = datetime.fromisoformat(data["cached_at"])
            if datetime.now() - cached_at > self.ttl:
                # Expired - delete and return None
                cache_path.unlink(missing_ok=True)
                return None

            # Reconstruct Tool from cached data
            return Tool(
                id=data["tool"]["id"],
                name=data["tool"]["name"],
                description=data["tool"]["description"],
                code=data["tool"]["code"],
                function_name=data["tool"]["function_name"],
                parameters=data["tool"]["parameters"],
                dependencies=data["tool"].get("dependencies", []),
                tags=data["tool"].get("tags", []),
            )

        except (json.JSONDecodeError, KeyError, ValueError):
            # Corrupted cache - delete and return None
            cache_path.unlink(missing_ok=True)
            return None

    def put(self, description: str, tool: "Tool") -> None:
        """
        Cache a built tool.

        Args:
            description: Original tool description used to build it
            tool: The built Tool to cache
        """
        cache_path = self._cache_path(description)

        data = {
            "cached_at": datetime.now().isoformat(),
            "description": description,
            "tool": {
                "id": tool.id,
                "name": tool.name,
                "description": tool.description,
                "code": tool.code,
                "function_name": tool.function_name,
                "parameters": tool.parameters,
                "dependencies": tool.dependencies,
                "tags": tool.tags,
            },
        }

        cache_path.write_text(json.dumps(data, indent=2))

    def clear(self) -> int:
        """
        Clear all cached tools.

        Returns:
            Number of cache entries cleared
        """
        count = 0
        for cache_file in self.cache_dir.glob("*.json"):
            cache_file.unlink()
            count += 1
        return count

    def clear_expired(self) -> int:
        """
        Clear only expired cache entries.

        Returns:
            Number of expired entries cleared
        """
        count = 0
        now = datetime.now()

        for cache_file in self.cache_dir.glob("*.json"):
            try:
                data = json.loads(cache_file.read_text())
                cached_at = datetime.fromisoformat(data["cached_at"])
                if now - cached_at > self.ttl:
                    cache_file.unlink()
                    count += 1
            except (json.JSONDecodeError, KeyError, ValueError):
                # Corrupted - delete
                cache_file.unlink()
                count += 1

        return count

    def stats(self) -> dict:
        """
        Get cache statistics.

        Returns:
            Dict with count, total_size_kb, oldest, newest
        """
        files = list(self.cache_dir.glob("*.json"))

        if not files:
            return {
                "count": 0,
                "total_size_kb": 0,
                "oldest": None,
                "newest": None,
            }

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

        oldest = None
        newest = None
        for f in files:
            try:
                data = json.loads(f.read_text())
                cached_at = datetime.fromisoformat(data["cached_at"])
                if oldest is None or cached_at < oldest:
                    oldest = cached_at
                if newest is None or cached_at > newest:
                    newest = cached_at
            except (json.JSONDecodeError, KeyError, ValueError):
                pass

        return {
            "count": len(files),
            "total_size_kb": round(total_size / 1024, 2),
            "oldest": oldest.isoformat() if oldest else None,
            "newest": newest.isoformat() if newest else None,
        }


# Global cache instance
_tool_cache: ToolCache | None = None


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