import hashlib
import threading
from typing import Dict, Any, Optional
from gudb.config import settings
from gudb.providers.base import BaseProvider, LocalProvider
from gudb.providers.remote import RemoteGudbProvider
from gudb.utils.logging import get_logger
from gudb.core.redactor import SQLRedactor
from gudb.utils.context import ContextCollector

logger = get_logger("guardian")

class Guardian:
    """
    The brain of the SDK. Manages the evaluation lifecycle:
    Heuristics -> Caching -> AI Evaluation.
    
    SECURITY GUARANTEE:
    - Default mode is "local" with NO network calls
    - Remote/hybrid modes REQUIRE explicit GUDB_API_ENDPOINT or raise RuntimeError
    - No hardcoded endpoints, no telemetry, no silent data exfiltration
    - Dashboard URLs are display-only and require explicit configuration
    """
    
    def __init__(self, provider: Optional[BaseProvider] = None):
        if provider:
            # Explicit provider passed - use it directly
            self.provider = provider
        else:
            # Mode-based provider selection with guardrails
            self.provider = self._create_provider_from_mode()
        
        self.query_cache: Dict[str, Dict] = {}
        self._lock = threading.Lock()
        self.redactor = SQLRedactor()
        self.context = ContextCollector()
    
    def _create_provider_from_mode(self) -> BaseProvider:
        """Create the appropriate provider based on mode setting with safety checks."""
        mode = settings.mode
        
        # GUARDRAIL: Remote/hybrid modes require explicit endpoint
        if mode in {"remote", "hybrid"} and not settings.api_endpoint:
            raise RuntimeError(
                f"GUDB_MODE='{mode}' requires GUDB_API_ENDPOINT to be explicitly set. "
                "This prevents unintentional data exfiltration. "
                "Set GUDB_API_ENDPOINT environment variable or use GUDB_MODE='local'."
            )
        
        # Provider selection
        if mode == "local":
            logger.info("🔒 [Local Mode] Running with local heuristics only (no remote calls)")
            return LocalProvider()
        elif mode == "remote":
            logger.info(f"🌐 [Remote Mode] Using remote AI evaluation: {settings.api_endpoint}")
            return RemoteGudbProvider()
        elif mode == "hybrid":
            logger.info(f"⚡ [Hybrid Mode] Using local heuristics + remote AI: {settings.api_endpoint}")
            return RemoteGudbProvider()  # Heuristics run first, then remote if needed
        else:
            raise ValueError(
                f"Invalid GUDB_MODE: '{mode}'. "
                "Must be one of: 'local', 'remote', 'hybrid'"
            )

    def evaluate(self, query: str) -> Dict[str, Any]:
        """Runs the full safety check on a query (Seatbelt Mode)."""
        if not settings.enabled:
            return {"verdict": "GO"}

        # 1. LOCAL HEURISTICS (always run in local/hybrid mode)
        if settings.mode in ("local", "hybrid") and settings.enable_local_heuristics:
            heuristic_result = self._check_heuristics(query)
            if heuristic_result:
                # Dangerous query detected by heuristics
                # Add dashboard URL if configured
                if settings.dashboard_url:
                    heuristic_result["dashboard_url"] = settings.dashboard_url
                
                # Optionally fetch AI advisory in remote/hybrid mode
                if settings.mode == "hybrid" and settings.api_endpoint:
                    ai_advisory = self._fetch_ai_advisory(query)
                    if ai_advisory:
                        heuristic_result["ai_advisory"] = ai_advisory
                
                return heuristic_result

        # 2. Remote evaluation (only in remote/hybrid mode)
        if settings.mode == "local":
            # Local-only mode: heuristics passed, allow query
            return {"verdict": "GO"}
        
        if not settings.api_endpoint:
            # Remote mode requested but no endpoint configured
            logger.warning("Remote mode enabled but GUDB_API_ENDPOINT not set. Falling back to local-only.")
            return {"verdict": "GO"}

        # 3. FAST-PATH: Only redact and AI-check if risky
        if not self._is_risky(query):
            return {"verdict": "GO"}

        # 4. Redaction & Caching (Advisory Level)
        processed_query = self.redactor.redact(query) if settings.redact_pii else query
        query_hash = hashlib.sha256(processed_query.encode()).hexdigest()
        
        with self._lock:
            if query_hash in self.query_cache:
                return self.query_cache[query_hash]

        # 5. AI Advisory (Consulting the Senior DRE)
        logger.info(f"🛡️ [Advisor] Consulting AI for pattern: {processed_query[:40]}...")
        context = self.context.collect_all()
        result = self.provider.evaluate(processed_query, context)
        
        # Add dashboard URL if configured
        if settings.dashboard_url and result.get("verdict") == "STOP":
            result["dashboard_url"] = settings.dashboard_url
        
        # 6. Caching & Result
        with self._lock:
            self.query_cache[query_hash] = result
            
        return result

    def _normalize_sql(self, sql: str) -> str:
        """
        Normalize SQL by removing comments and collapsing whitespace.
        Ensures heuristics are resilient to obfuscation via spacing and comments.
        """
        s = sql.upper()
        # Remove block comments /* ... */
        import re
        s = re.sub(r"/\*.*?\*/", " ", s, flags=re.S)
        # Remove line comments -- ... end of line
        s = re.sub(r"--.*?$", " ", s, flags=re.M)
        # Collapse all whitespace (spaces, tabs, newlines) to single spaces
        s = re.sub(r"\s+", " ", s)
        return s.strip()

    def _check_heuristics(self, query: str) -> Optional[Dict[str, Any]]:
        """Hardcoded safety rules that MUST NEVER be violated."""
        norm_sql = self._normalize_sql(query)

        # Rule 1: No unconstrained DELETE/UPDATE (The Career-Saver)
        if (norm_sql.startswith("DELETE") or norm_sql.startswith("UPDATE")) and " WHERE " not in norm_sql:
            return {
                "verdict": "STOP",
                "issue": "UNCONSTRAINED DATA MODIFICATION",
                "impact": "This query will wipe or corrupt EVERY ROW in the table.",
                "fix": "Add a WHERE clause to target specific records.",
                "reasoning_short": "Seatbelt: Blocked because a WHERE clause is missing."
            }

        # Rule 2: No accidental DROPs
        if "DROP TABLE" in norm_sql or "DROP DATABASE" in norm_sql:
            return {
                "verdict": "STOP",
                "issue": "SCHEMA DESTRUCTION DETECTED",
                "impact": "This will PERMANENTLY delete your data structure.",
                "fix": "Schema changes should go through a migration pipeline, not direct execution.",
                "reasoning_short": "Seatbelt: DROP statements are restricted."
            }

        # Rule 3: No TRUNCATE without safety
        if "TRUNCATE" in norm_sql:
            return {
                "verdict": "STOP",
                "issue": "BLITZ-WIPE DETECTED",
                "impact": "TRUNCATE is irreversible and ignores triggers.",
                "fix": "Use DELETE with WHERE if you only want to remove specific data.",
                "reasoning_short": "Seatbelt: TRUNCATE is considered too dangerous for direct SDK use."
            }

        return None

    def _fetch_ai_advisory(self, query: str) -> str:
        """
        Optionally fetch AI advisory for a blocked query.
        This is only called in hybrid mode when heuristics have already blocked the query.
        Returns advisory text or empty string if unavailable.
        """
        try:
            processed_query = self.redactor.redact(query) if settings.redact_pii else query
            context = self.context.collect_all()
            result = self.provider.evaluate(processed_query, context)
            
            # Extract advisory message from AI response
            if result.get("verdict") == "STOP":
                advisory_parts = []
                if result.get("reason"):
                    advisory_parts.append(result["reason"])
                if result.get("recommendation"):
                    advisory_parts.append(f"Recommendation: {result['recommendation']}")
                return " ".join(advisory_parts) if advisory_parts else ""
            return ""
        except Exception as e:
            logger.debug(f"Failed to fetch AI advisory: {e}")
            return ""
    
    def _is_risky(self, query: str) -> bool:
        upper_sql = query.upper()
        return any(kw in upper_sql for kw in settings.risky_keywords)
