"""
Execution monitoring and logging for sandbox operations.
"""

import json
import time
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, asdict
from datetime import datetime
import threading


@dataclass
class ExecutionLog:
    """Single execution log entry."""
    timestamp: str
    code: str
    success: bool
    output: str
    error: Optional[str]
    execution_time: float
    memory_used: int
    sandbox_type: str  # "docker" or "restricted"
    context: Optional[Dict[str, Any]]


class ExecutionMonitor:
    """
    Monitor and log all code executions.
    
    Features:
    - Thread-safe logging
    - Execution history
    - Performance metrics
    - Audit trail
    """
    
    def __init__(self, max_history: int = 1000):
        """
        Initialize execution monitor.
        
        Args:
            max_history: Maximum number of executions to keep in memory
        """
        self.max_history = max_history
        self.history: List[ExecutionLog] = []
        self._lock = threading.Lock()
        self._stats = {
            "total_executions": 0,
            "successful_executions": 0,
            "failed_executions": 0,
            "total_execution_time": 0.0,
            "total_memory_used": 0,
        }
    
    def log_execution(
        self,
        code: str,
        success: bool,
        output: str,
        error: Optional[str],
        execution_time: float,
        memory_used: int,
        sandbox_type: str,
        context: Optional[Dict[str, Any]] = None
    ):
        """
        Log a code execution.
        
        Args:
            code: Executed code
            success: Whether execution succeeded
            output: Execution output
            error: Error message if failed
            execution_time: Time taken in seconds
            memory_used: Memory used in bytes
            sandbox_type: Type of sandbox used
            context: Execution context
        """
        with self._lock:
            # Create log entry
            log_entry = ExecutionLog(
                timestamp=datetime.now().isoformat(),
                code=code,
                success=success,
                output=output,
                error=error,
                execution_time=execution_time,
                memory_used=memory_used,
                sandbox_type=sandbox_type,
                context=context
            )
            
            # Add to history
            self.history.append(log_entry)
            
            # Trim history if needed
            if len(self.history) > self.max_history:
                self.history = self.history[-self.max_history:]
            
            # Update stats
            self._stats["total_executions"] += 1
            if success:
                self._stats["successful_executions"] += 1
            else:
                self._stats["failed_executions"] += 1
            self._stats["total_execution_time"] += execution_time
            self._stats["total_memory_used"] += memory_used
    
    def get_history(self, limit: int = 10) -> List[Dict[str, Any]]:
        """
        Get recent execution history.
        
        Args:
            limit: Number of recent executions to return
        
        Returns:
            List of execution logs
        """
        with self._lock:
            recent = self.history[-limit:]
            return [asdict(log) for log in recent]
    
    def get_stats(self) -> Dict[str, Any]:
        """
        Get execution statistics.
        
        Returns:
            Dictionary of statistics
        """
        with self._lock:
            stats = self._stats.copy()
            
            # Calculate averages
            if stats["total_executions"] > 0:
                stats["avg_execution_time"] = (
                    stats["total_execution_time"] / stats["total_executions"]
                )
                stats["avg_memory_used"] = (
                    stats["total_memory_used"] / stats["total_executions"]
                )
                stats["success_rate"] = (
                    stats["successful_executions"] / stats["total_executions"]
                )
            else:
                stats["avg_execution_time"] = 0.0
                stats["avg_memory_used"] = 0
                stats["success_rate"] = 0.0
            
            return stats
    
    def export_logs(self, filepath: str):
        """
        Export execution logs to JSON file.
        
        Args:
            filepath: Path to output file
        """
        with self._lock:
            logs = [asdict(log) for log in self.history]
            with open(filepath, 'w') as f:
                json.dump({
                    "logs": logs,
                    "stats": self.get_stats()
                }, f, indent=2)
    
    def clear_history(self):
        """Clear execution history."""
        with self._lock:
            self.history.clear()
            self._stats = {
                "total_executions": 0,
                "successful_executions": 0,
                "failed_executions": 0,
                "total_execution_time": 0.0,
                "total_memory_used": 0,
            }
    
    def search_logs(
        self,
        keyword: Optional[str] = None,
        success: Optional[bool] = None,
        sandbox_type: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        Search execution logs.
        
        Args:
            keyword: Search in code and output
            success: Filter by success status
            sandbox_type: Filter by sandbox type
        
        Returns:
            Filtered execution logs
        """
        with self._lock:
            results = self.history.copy()
            
            # Filter by keyword
            if keyword:
                results = [
                    log for log in results
                    if keyword.lower() in log.code.lower() or
                       keyword.lower() in log.output.lower()
                ]
            
            # Filter by success
            if success is not None:
                results = [log for log in results if log.success == success]
            
            # Filter by sandbox type
            if sandbox_type:
                results = [
                    log for log in results
                    if log.sandbox_type == sandbox_type
                ]
            
            return [asdict(log) for log in results]


# Global monitor instance
_global_monitor = ExecutionMonitor()


def get_monitor() -> ExecutionMonitor:
    """Get global execution monitor instance."""
    return _global_monitor
