"""
RDST Scan Snippet Cache - Cache LLM responses by ORM snippet hash.

This provides deterministic query extraction by caching the LLM's
ORM-to-SQL conversion results keyed by the snippet hash.

Since:
1. AST extraction is deterministic (same code -> same snippet)
2. Snippet hash is deterministic (same snippet -> same hash)
3. LLM with temperature=0 is deterministic (same input -> same output)
4. Cache lookup is deterministic (same hash -> same cached result)

The entire pipeline is deterministic.

Cache storage: ~/.rdst/cache/scan-snippets/<project>.json
"""

import json
import hashlib
from pathlib import Path
from datetime import datetime
from typing import Dict, Optional, Any


class SnippetCache:
    """
    Cache for LLM-generated SQL from ORM snippets.

    The cache is keyed by snippet hash, which is generated from the
    normalized ORM code. This ensures:
    - Same ORM code always gets the same hash
    - Same hash always returns the same cached SQL
    - No LLM call needed for previously-seen queries
    """

    def __init__(self, project_name: str = "default"):
        self.project_name = project_name
        self.cache_dir = Path.home() / ".rdst" / "cache" / "scan-snippets"
        self.cache_file = self.cache_dir / f"{project_name}.json"
        self.cache: Dict[str, Dict[str, Any]] = {}
        self._load()

    def _load(self):
        """Load cache from disk."""
        if self.cache_file.exists():
            try:
                self.cache = json.loads(self.cache_file.read_text())
            except Exception:
                self.cache = {}

    def _save(self):
        """Save cache to disk."""
        self.cache_dir.mkdir(parents=True, exist_ok=True)
        self.cache_file.write_text(json.dumps(self.cache, indent=2))

    def get(self, snippet_hash: str) -> Optional[Dict[str, Any]]:
        """
        Get cached result for a snippet hash.

        Returns:
            Dict with 'sql', 'issues', 'cached_at' if found, None otherwise
        """
        return self.cache.get(snippet_hash)

    def set(self, snippet_hash: str, sql: str, issues: list = None, orm_snippet: str = None):
        """
        Cache an LLM result for a snippet hash.

        Args:
            snippet_hash: The hash of the ORM snippet
            sql: The SQL generated by the LLM
            issues: List of issues identified by the LLM
            orm_snippet: The original ORM snippet (for debugging)
        """
        self.cache[snippet_hash] = {
            "sql": sql,
            "issues": issues or [],
            "orm_snippet": orm_snippet,
            "cached_at": datetime.utcnow().isoformat() + "Z",
        }
        self._save()

    def has(self, snippet_hash: str) -> bool:
        """Check if a snippet hash is in the cache."""
        return snippet_hash in self.cache

    def clear(self):
        """Clear all cached entries."""
        self.cache = {}
        self._save()

    def stats(self) -> Dict[str, Any]:
        """Get cache statistics."""
        return {
            "entries": len(self.cache),
            "project": self.project_name,
            "cache_file": str(self.cache_file),
        }

    def get_or_generate(
        self,
        snippet_hash: str,
        orm_snippet: str,
        generate_fn,  # Callable that takes orm_snippet and returns (sql, issues)
    ) -> Dict[str, Any]:
        """
        Get cached result or generate and cache it.

        This is the main entry point for cached LLM conversion.

        Args:
            snippet_hash: Hash of the ORM snippet
            orm_snippet: The ORM code to convert
            generate_fn: Function to call if not cached (orm_snippet) -> (sql, issues)

        Returns:
            Dict with 'sql', 'issues', 'cached' (bool indicating if from cache)
        """
        cached = self.get(snippet_hash)
        if cached:
            return {
                "sql": cached["sql"],
                "issues": cached["issues"],
                "cached": True,
            }

        # Generate using LLM
        sql, issues = generate_fn(orm_snippet)

        # Cache the result
        self.set(snippet_hash, sql, issues, orm_snippet)

        return {
            "sql": sql,
            "issues": issues,
            "cached": False,
        }


def get_cache(project_name: str = "default") -> SnippetCache:
    """Get or create a snippet cache for a project."""
    return SnippetCache(project_name)


# Test
if __name__ == "__main__":
    cache = get_cache("test-project")
    print(f"Cache stats: {cache.stats()}")

    # Test caching
    test_hash = "abc123"
    test_sql = "SELECT * FROM users WHERE id = $1"

    if not cache.has(test_hash):
        cache.set(test_hash, test_sql, ["No index on users.id"])
        print("Cached new entry")
    else:
        result = cache.get(test_hash)
        print(f"Got cached: {result}")
