#!/usr/bin/env python3
"""
Uni-Wizard v4 — Unified Production API

Single entry point for all uni-wizard capabilities.

Usage:
    from uni_wizard import Harness, House, Mode
    
    # Simple mode - direct execution
    harness = Harness(mode=Mode.SIMPLE)
    result = harness.execute("git_status", repo_path="/path")
    
    # Intelligent mode - with predictions and learning
    harness = Harness(house=House.EZRA, mode=Mode.INTELLIGENT)
    result = harness.execute("git_status")
    print(f"Predicted: {result.prediction.success_rate:.0%}")
    
    # Sovereign mode - full provenance and approval
    harness = Harness(house=House.TIMMY, mode=Mode.SOVEREIGN)
    result = harness.execute("deploy")
"""

from enum import Enum, auto
from typing import Dict, Any, Optional, List, Callable
from dataclasses import dataclass, field
from pathlib import Path
import json
import time
import hashlib
import asyncio
from concurrent.futures import ThreadPoolExecutor


class House(Enum):
    """Canonical wizard houses"""
    TIMMY = "timmy"      # Sovereign local conscience
    EZRA = "ezra"        # Archivist, reader
    BEZALEL = "bezalel"  # Artificer, builder
    ALLEGRO = "allegro"  # Tempo-and-dispatch, connected


class Mode(Enum):
    """Operating modes"""
    SIMPLE = "simple"         # Direct execution, no overhead
    INTELLIGENT = "intelligent"  # With predictions and learning
    SOVEREIGN = "sovereign"   # Full provenance, approval required


@dataclass
class Prediction:
    """Pre-execution prediction"""
    success_rate: float
    confidence: float
    reasoning: str
    suggested_house: Optional[str] = None
    estimated_latency_ms: float = 0.0


@dataclass
class Provenance:
    """Full execution provenance"""
    house: str
    tool: str
    mode: str
    started_at: str
    completed_at: Optional[str] = None
    input_hash: str = ""
    output_hash: str = ""
    prediction: Optional[Prediction] = None
    execution_time_ms: float = 0.0
    retry_count: int = 0
    circuit_open: bool = False


@dataclass
class ExecutionResult:
    """Unified execution result"""
    success: bool
    data: Any
    provenance: Provenance
    error: Optional[str] = None
    suggestions: List[str] = field(default_factory=list)
    
    def to_json(self) -> str:
        return json.dumps({
            "success": self.success,
            "data": self.data,
            "error": self.error,
            "provenance": {
                "house": self.provenance.house,
                "tool": self.provenance.tool,
                "mode": self.provenance.mode,
                "execution_time_ms": self.provenance.execution_time_ms,
                "prediction": {
                    "success_rate": self.provenance.prediction.success_rate,
                    "confidence": self.provenance.prediction.confidence
                } if self.provenance.prediction else None
            },
            "suggestions": self.suggestions
        }, indent=2, default=str)


class ToolRegistry:
    """Central tool registry"""
    
    def __init__(self):
        self._tools: Dict[str, Callable] = {}
        self._schemas: Dict[str, Dict] = {}
    
    def register(self, name: str, handler: Callable, schema: Dict = None):
        """Register a tool"""
        self._tools[name] = handler
        self._schemas[name] = schema or {}
        return self
    
    def get(self, name: str) -> Optional[Callable]:
        """Get tool handler"""
        return self._tools.get(name)
    
    def list_tools(self) -> List[str]:
        """List all registered tools"""
        return list(self._tools.keys())


class IntelligenceLayer:
    """
    v4 Intelligence - pattern recognition and prediction.
    Lightweight version for production.
    """
    
    def __init__(self, db_path: Path = None):
        self.patterns: Dict[str, Dict] = {}
        self.db_path = db_path or Path.home() / ".uni-wizard" / "patterns.json"
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._load_patterns()
    
    def _load_patterns(self):
        """Load patterns from disk"""
        if self.db_path.exists():
            with open(self.db_path) as f:
                self.patterns = json.load(f)
    
    def _save_patterns(self):
        """Save patterns to disk"""
        with open(self.db_path, 'w') as f:
            json.dump(self.patterns, f, indent=2)
    
    def predict(self, tool: str, house: str, params: Dict) -> Prediction:
        """Predict execution outcome"""
        key = f"{house}:{tool}"
        pattern = self.patterns.get(key, {})
        
        if not pattern or pattern.get("count", 0) < 3:
            return Prediction(
                success_rate=0.7,
                confidence=0.5,
                reasoning="Insufficient data for prediction",
                estimated_latency_ms=200
            )
        
        success_rate = pattern.get("successes", 0) / pattern.get("count", 1)
        avg_latency = pattern.get("total_latency_ms", 0) / pattern.get("count", 1)
        
        confidence = min(0.95, pattern.get("count", 0) / 20)  # Max at 20 samples
        
        return Prediction(
            success_rate=success_rate,
            confidence=confidence,
            reasoning=f"Based on {pattern.get('count')} executions",
            estimated_latency_ms=avg_latency
        )
    
    def record(self, tool: str, house: str, success: bool, latency_ms: float):
        """Record execution outcome"""
        key = f"{house}:{tool}"
        
        if key not in self.patterns:
            self.patterns[key] = {"count": 0, "successes": 0, "total_latency_ms": 0}
        
        self.patterns[key]["count"] += 1
        self.patterns[key]["successes"] += int(success)
        self.patterns[key]["total_latency_ms"] += latency_ms
        
        self._save_patterns()


class CircuitBreaker:
    """Circuit breaker pattern for fault tolerance"""
    
    def __init__(self, failure_threshold: int = 5, recovery_timeout: float = 60.0):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.failures: Dict[str, int] = {}
        self.last_failure: Dict[str, float] = {}
        self.open_circuits: set = set()
    
    def can_execute(self, tool: str) -> bool:
        """Check if tool can be executed"""
        if tool not in self.open_circuits:
            return True
        
        # Check if recovery timeout passed
        last_fail = self.last_failure.get(tool, 0)
        if time.time() - last_fail > self.recovery_timeout:
            self.open_circuits.discard(tool)
            return True
        
        return False
    
    def record_success(self, tool: str):
        """Record successful execution"""
        self.failures[tool] = 0
        self.open_circuits.discard(tool)
    
    def record_failure(self, tool: str):
        """Record failed execution"""
        self.failures[tool] = self.failures.get(tool, 0) + 1
        self.last_failure[tool] = time.time()
        
        if self.failures[tool] >= self.failure_threshold:
            self.open_circuits.add(tool)


class Harness:
    """
    Uni-Wizard v4 Unified Harness.
    
    Single API for all execution needs.
    """
    
    def __init__(
        self,
        house: House = House.TIMMY,
        mode: Mode = Mode.INTELLIGENT,
        enable_learning: bool = True,
        max_workers: int = 4
    ):
        self.house = house
        self.mode = mode
        self.enable_learning = enable_learning
        
        # Components
        self.registry = ToolRegistry()
        self.intelligence = IntelligenceLayer() if mode != Mode.SIMPLE else None
        self.circuit_breaker = CircuitBreaker()
        self.executor = ThreadPoolExecutor(max_workers=max_workers)
        
        # Metrics
        self.execution_count = 0
        self.success_count = 0
        
        # Register built-in tools
        self._register_builtin_tools()
    
    def _register_builtin_tools(self):
        """Register built-in tools"""
        # System tools
        self.registry.register("system_info", self._system_info)
        self.registry.register("health_check", self._health_check)
        
        # Git tools
        self.registry.register("git_status", self._git_status)
        self.registry.register("git_log", self._git_log)
        
        # Placeholder for actual implementations
        self.registry.register("file_read", self._not_implemented)
        self.registry.register("file_write", self._not_implemented)
    
    def _system_info(self, **params) -> Dict:
        """Get system information"""
        import platform
        return {
            "platform": platform.platform(),
            "python": platform.python_version(),
            "processor": platform.processor(),
            "hostname": platform.node()
        }
    
    def _health_check(self, **params) -> Dict:
        """Health check"""
        return {
            "status": "healthy",
            "executions": self.execution_count,
            "success_rate": self.success_count / max(1, self.execution_count)
        }
    
    def _git_status(self, repo_path: str = ".", **params) -> Dict:
        """Git status (placeholder)"""
        # Would call actual git command
        return {"status": "clean", "repo": repo_path}
    
    def _git_log(self, repo_path: str = ".", max_count: int = 10, **params) -> Dict:
        """Git log (placeholder)"""
        return {"commits": [], "repo": repo_path}
    
    def _not_implemented(self, **params) -> Dict:
        """Placeholder for unimplemented tools"""
        return {"error": "Tool not yet implemented"}
    
    def predict(self, tool: str, params: Dict = None) -> Optional[Prediction]:
        """Predict execution outcome"""
        if self.mode == Mode.SIMPLE or not self.intelligence:
            return None
        
        return self.intelligence.predict(tool, self.house.value, params or {})
    
    def execute(self, tool: str, **params) -> ExecutionResult:
        """
        Execute a tool with full v4 capabilities.
        
        Flow:
        1. Check circuit breaker
        2. Get prediction (if intelligent mode)
        3. Execute with timeout
        4. Record outcome (if learning enabled)
        5. Return result with full provenance
        """
        start_time = time.time()
        started_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
        
        # 1. Circuit breaker check
        if not self.circuit_breaker.can_execute(tool):
            return ExecutionResult(
                success=False,
                data=None,
                error=f"Circuit breaker open for {tool}",
                provenance=Provenance(
                    house=self.house.value,
                    tool=tool,
                    mode=self.mode.value,
                    started_at=started_at,
                    circuit_open=True
                ),
                suggestions=[f"Wait for circuit recovery or use alternative tool"]
            )
        
        # 2. Get prediction
        prediction = None
        if self.mode != Mode.SIMPLE:
            prediction = self.predict(tool, params)
        
        # 3. Execute
        handler = self.registry.get(tool)
        
        if not handler:
            return ExecutionResult(
                success=False,
                data=None,
                error=f"Tool '{tool}' not found",
                provenance=Provenance(
                    house=self.house.value,
                    tool=tool,
                    mode=self.mode.value,
                    started_at=started_at,
                    prediction=prediction
                )
            )
        
        try:
            # Execute with timeout for production
            result_data = handler(**params)
            success = True
            error = None
            self.circuit_breaker.record_success(tool)
            
        except Exception as e:
            success = False
            error = str(e)
            result_data = None
            self.circuit_breaker.record_failure(tool)
        
        execution_time_ms = (time.time() - start_time) * 1000
        completed_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
        
        # 4. Record for learning
        if self.enable_learning and self.intelligence:
            self.intelligence.record(tool, self.house.value, success, execution_time_ms)
        
        # Update metrics
        self.execution_count += 1
        if success:
            self.success_count += 1
        
        # Build provenance
        input_hash = hashlib.sha256(
            json.dumps(params, sort_keys=True).encode()
        ).hexdigest()[:16]
        
        output_hash = hashlib.sha256(
            json.dumps(result_data, default=str).encode()
        ).hexdigest()[:16] if result_data else ""
        
        provenance = Provenance(
            house=self.house.value,
            tool=tool,
            mode=self.mode.value,
            started_at=started_at,
            completed_at=completed_at,
            input_hash=input_hash,
            output_hash=output_hash,
            prediction=prediction,
            execution_time_ms=execution_time_ms
        )
        
        # Build suggestions
        suggestions = []
        if not success:
            suggestions.append(f"Check tool availability and parameters")
        if prediction and prediction.success_rate < 0.5:
            suggestions.append(f"Low historical success rate - consider alternative approach")
        
        return ExecutionResult(
            success=success,
            data=result_data,
            error=error,
            provenance=provenance,
            suggestions=suggestions
        )
    
    async def execute_async(self, tool: str, **params) -> ExecutionResult:
        """Async execution"""
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(self.executor, self.execute, tool, **params)
    
    def execute_batch(self, tasks: List[Dict]) -> List[ExecutionResult]:
        """
        Execute multiple tasks.
        
        tasks: [{"tool": "name", "params": {...}}, ...]
        """
        results = []
        for task in tasks:
            result = self.execute(task["tool"], **task.get("params", {}))
            results.append(result)
            
            # In SOVEREIGN mode, stop on first failure
            if self.mode == Mode.SOVEREIGN and not result.success:
                break
        
        return results
    
    def get_stats(self) -> Dict:
        """Get harness statistics"""
        return {
            "house": self.house.value,
            "mode": self.mode.value,
            "executions": self.execution_count,
            "successes": self.success_count,
            "success_rate": self.success_count / max(1, self.execution_count),
            "tools_registered": len(self.registry.list_tools()),
            "learning_enabled": self.enable_learning,
            "circuit_breaker_open": len(self.circuit_breaker.open_circuits)
        }
    
    def get_patterns(self) -> Dict:
        """Get learned patterns"""
        if not self.intelligence:
            return {}
        return self.intelligence.patterns


# Convenience factory functions
def get_harness(house: str = "timmy", mode: str = "intelligent") -> Harness:
    """Get configured harness"""
    return Harness(
        house=House(house),
        mode=Mode(mode)
    )


def get_simple_harness() -> Harness:
    """Get simple harness (no intelligence overhead)"""
    return Harness(mode=Mode.SIMPLE)


def get_intelligent_harness(house: str = "timmy") -> Harness:
    """Get intelligent harness with learning"""
    return Harness(
        house=House(house),
        mode=Mode.INTELLIGENT,
        enable_learning=True
    )


def get_sovereign_harness() -> Harness:
    """Get sovereign harness (full provenance)"""
    return Harness(
        house=House.TIMMY,
        mode=Mode.SOVEREIGN,
        enable_learning=True
    )


# CLI interface
if __name__ == "__main__":
    import argparse
    
    parser = argparse.ArgumentParser(description="Uni-Wizard v4")
    parser.add_argument("--house", default="timmy", choices=["timmy", "ezra", "bezalel", "allegro"])
    parser.add_argument("--mode", default="intelligent", choices=["simple", "intelligent", "sovereign"])
    parser.add_argument("tool", help="Tool to execute")
    parser.add_argument("--params", default="{}", help="JSON params")
    
    args = parser.parse_args()
    
    harness = Harness(house=House(args.house), mode=Mode(args.mode))
    params = json.loads(args.params)
    
    result = harness.execute(args.tool, **params)
    print(result.to_json())
