# base.py - SystemBase Foundation for All Organs

"""
SystemBase - The Foundation Blueprint for All Shadow Monarch Organs

This class provides the universal interface and infrastructure that all organs inherit.
Each organ is a self-contained, learning system with its own specialized purpose.

Key Features:
- Universal calling interface (sync, async, streaming)
- Tool auto-injection (brain, json, files, web, dupes, experience)
- Stage-by-stage pipeline processing
- Individual organ learning
- Run manifest generation
- Error handling and recovery
- Seamless inter-organ communication
"""

import asyncio
import json
import time
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Union, Callable
from abc import ABC, abstractmethod

# Import core tools (auto-injected)
try:
    from brain import get_brain
    HAS_BRAIN = True
except ImportError:
    HAS_BRAIN = False
    get_brain = None

try:
    from json_mage import modify
    HAS_JSON_MAGE = True
except ImportError:
    HAS_JSON_MAGE = False
    modify = None

try:
    import simple_file
    HAS_SIMPLE_FILE = True
except ImportError:
    HAS_SIMPLE_FILE = False
    simple_file = None

try:
    from experience import get_experience
    HAS_EXPERIENCE = True
except ImportError:
    HAS_EXPERIENCE = False
    get_experience = None

try:
    from tool_fluency_v2 import GrimoireBridge
    HAS_FLUENCY = True
except ImportError:
    HAS_FLUENCY = False
    GrimoireBridge = None


# StageResult data class for pipeline stage tracking
class StageResult:
    """Result of a pipeline stage execution"""
    def __init__(self, stage_name: str, success: bool, input_count: int,
                 output_count: int, duration: float, output_path: Path,
                 metadata: Dict = None):
        self.stage_name = stage_name
        self.success = success
        self.input_count = input_count
        self.output_count = output_count
        self.duration = duration
        self.output_path = output_path
        self.metadata = metadata or {}


class SystemBase(ABC):
    """
    Foundation class for all Shadow Monarch Organs
    
    Each organ is a specialized system that:
    - Has a specific processing purpose
    - Learns independently from experience
    - Can communicate with other organs
    - Follows the same universal interface
    - Generates detailed run manifests
    """
    
    def __init__(self, **kwargs):
        """Initialize organ with auto-injected tools and configuration"""
        self.organ_name = self.__class__.__name__
        self.config = kwargs.get('config', {})
        self.run_id = f"{self.organ_name.lower()}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
        self.run_start_time = None
        self.current_stage = None
        
        # Auto-inject tools
        self._inject_tools()
        
        # Initialize learning system
        if HAS_EXPERIENCE:
            self.experience = get_experience()
        
        # Stage tracking
        self.stages_completed = []
        self.stages_failed = []
        self.current_message = None
        self.works = True  # For streaming mode
        
        print(f"💀 {self.organ_name} initialized - Organ #{self._get_organ_number()}")
    
    def _inject_tools(self):
        """Auto-inject all available Grimoire tools"""
        # Brain (AI capabilities)
        if HAS_BRAIN:
            try:
                provider = self.config.get('ai_provider', 'deepseek')
                self.brain = get_brain(provider)
            except Exception as e:
                print(f"⚠️  Brain injection failed: {e}")
                self.brain = None
        else:
            self.brain = None
        
        # JSON Mage (data manipulation)
        if HAS_JSON_MAGE:
            self.json = modify
        else:
            self.json = None
        
        # Simple File (I/O operations)
        if HAS_SIMPLE_FILE:
            self.files = simple_file
        else:
            self.files = None
        
        # Experience (learning system)
        if HAS_EXPERIENCE:
            try:
                self.experience = get_experience()
            except Exception as e:
                print(f"⚠️  Experience injection failed: {e}")
                self.experience = None
        else:
            self.experience = None
        
        # Web tools (internet access)
        self.web = self._get_web_tools()
        
        # Duplicate detection
        self.dupes = self._get_duplicate_tools()
        
        # Fluency bridge (tool optimization)
        if HAS_FLUENCY and GrimoireBridge:
            try:
                GrimoireBridge.connect(self.organ_name.lower(), self)
            except Exception:
                pass  # Silent failure
    
    def _get_web_tools(self) -> Dict[str, Callable]:
        """Get web/internet access tools"""
        try:
            import internet
            return {
                'get': getattr(internet, 'get', None),
                'runfor': getattr(internet, 'runfor', None),
                'waitfor': getattr(internet, 'waitfor', None),
            }
        except ImportError:
            return {}
    
    def _get_duplicate_tools(self) -> Dict[str, Callable]:
        """Get duplicate detection tools"""
        try:
            import duplicate_tools
            return {
                'smart_duplicate_check': getattr(duplicate_tools, 'smart_duplicate_check', None),
                'smart_duplicate_del': getattr(duplicate_tools, 'smart_duplicate_del', None),
            }
        except ImportError:
            return {}
    
    def _get_organ_number(self) -> int:
        """Get organ number (1 = DataSystem, 2 = PersonalisationSystem, etc.)"""
        organ_numbers = {
            'DataSystem': 1,
            'PersonalisationSystem': 2,
            # Add more organs here as they're created
        }
        return organ_numbers.get(self.organ_name, 99)
    
    # ==================== UNIVERSAL INTERFACE ====================
    
    def __call__(self, *args, **kwargs):
        """Universal calling interface - supports sync calls"""
        return self.run(*args, **kwargs)
    
    async def run(self, *args, **kwargs):
        """
        Universal async run interface
        
        This method MUST be implemented by each organ subclass.
        It should contain the organ's specific processing logic.
        """
        self.run_start_time = time.time()
        
        try:
            # Call the organ-specific processing
            result = await self._process(*args, **kwargs)
            
            # Generate manifest
            manifest = await self._generate_manifest(result)
            
            return {
                'organ': self.organ_name,
                'run_id': self.run_id,
                'result': result,
                'manifest': manifest,
                'success': True,
                'duration': time.time() - self.run_start_time
            }
            
        except Exception as e:
            # Handle errors
            error_result = await self._handle_error(e, args, kwargs)
            return error_result
    
    def waitfor(self, coro):
        """Blocking wait for async operation"""
        try:
            import asyncio
            return asyncio.run(coro)
        except RuntimeError:
            # Already in async context
            loop = asyncio.get_event_loop()
            return loop.run_until_complete(coro)
    
    # ==================== STREAMING INTERFACE ====================
    
    def _start_streaming(self):
        """Start streaming mode - for incremental processing"""
        self.works = True
        self.current_message = None
    
    def _stream_message(self, message: Dict[str, Any]):
        """Send message during streaming"""
        self.current_message = message
    
    def _stop_streaming(self):
        """Stop streaming mode"""
        self.works = False
    
    # ==================== ABSTRACT METHODS ====================
    
    @abstractmethod
    async def _process(self, *args, **kwargs):
        """
        Organ-specific processing logic
        
        This is where each organ implements its unique functionality.
        Should return the processed result.
        """
        pass
    
    # ==================== STAGE PIPELINE ====================
    
    async def _run_stage(self, stage_name: str, stage_func: Callable, *args, **kwargs):
        """
        Run a single stage with error handling and tracking
        
        Args:
            stage_name: Name of the stage for tracking
            stage_func: Function to execute for this stage
            *args, **kwargs: Arguments to pass to stage function
        """
        self.current_stage = stage_name
        
        try:
            start_time = time.time()
            result = await stage_func(*args, **kwargs)
            duration = time.time() - start_time
            
            # Record successful stage
            self.stages_completed.append({
                'stage': stage_name,
                'success': True,
                'duration': duration,
                'timestamp': datetime.now().isoformat()
            })
            
            # Learn from successful execution
            if self.experience:
                self.experience.record_pattern({
                    'organ': self.organ_name,
                    'stage': stage_name,
                    'success': True,
                    'duration': duration
                })
            
            print(f"✅ {self.organ_name} - Stage '{stage_name}' completed in {duration:.2f}s")
            return result
            
        except Exception as e:
            # Record failed stage
            self.stages_failed.append({
                'stage': stage_name,
                'success': False,
                'error': str(e),
                'timestamp': datetime.now().isoformat()
            })
            
            # Learn from failure
            if self.experience:
                self.experience.record_failure({
                    'organ': self.organ_name,
                    'stage': stage_name,
                    'error': str(e),
                    'error_type': type(e).__name__
                })
            
            print(f"❌ {self.organ_name} - Stage '{stage_name}' failed: {e}")
            raise e
    
    # ==================== MANIFEST GENERATION ====================
    
    async def _generate_manifest(self, result: Any) -> Dict[str, Any]:
        """
        Generate detailed run manifest following the organ pattern
        
        Each organ should override this to add organ-specific manifest data.
        """
        duration = time.time() - self.run_start_time if self.run_start_time else 0
        
        manifest = {
            'organ_name': self.organ_name,
            'organ_number': self._get_organ_number(),
            'run_id': self.run_id,
            'run_start': datetime.fromtimestamp(self.run_start_time).isoformat() if self.run_start_time else None,
            'run_duration': duration,
            'stages_completed': len(self.stages_completed),
            'stages_failed': len(self.stages_failed),
            'stage_details': {
                'completed': self.stages_completed,
                'failed': self.stages_failed
            },
            'tools_injected': {
                'brain': self.brain is not None,
                'json_mage': self.json is not None,
                'simple_file': self.files is not None,
                'experience': self.experience is not None,
                'web_tools': bool(self.web),
                'duplicate_tools': bool(self.dupes),
            },
            'configuration': self.config,
            'success': len(self.stages_failed) == 0
        }
        
        # Save manifest to runs directory
        await self._save_manifest(manifest)
        
        return manifest
    
    async def _save_manifest(self, manifest: Dict[str, Any]):
        """Save manifest to runs directory"""
        try:
            runs_dir = Path("runs")
            runs_dir.mkdir(exist_ok=True)
            
            run_dir = runs_dir / self.run_id
            run_dir.mkdir(exist_ok=True)
            
            manifest_file = run_dir / "manifest.json"
            manifest_file.write_text(json.dumps(manifest, indent=2))
            
            print(f"📊 Manifest saved: {manifest_file}")
            
        except Exception as e:
            print(f"⚠️  Failed to save manifest: {e}")
    
    # ==================== ERROR HANDLING ====================
    
    async def _handle_error(self, error: Exception, args: tuple, kwargs: dict) -> Dict[str, Any]:
        """Handle errors and return structured error response"""
        error_manifest = {
            'organ_name': self.organ_name,
            'run_id': self.run_id,
            'error': str(error),
            'error_type': type(error).__name__,
            'stage': self.current_stage,
            'success': False,
            'timestamp': datetime.now().isoformat()
        }
        
        return {
            'organ': self.organ_name,
            'run_id': self.run_id,
            'result': None,
            'manifest': error_manifest,
            'success': False,
            'error': str(error)
        }
    
    # ==================== UTILITY METHODS ====================
    
    def get_summary(self) -> Dict[str, Any]:
        """Get organ summary and status"""
        return {
            'organ_name': self.organ_name,
            'organ_number': self._get_organ_number(),
            'run_id': self.run_id,
            'current_stage': self.current_stage,
            'stages_completed': len(self.stages_completed),
            'stages_failed': len(self.stages_failed),
            'tools_available': {
                'brain': self.brain is not None,
                'json': self.json is not None,
                'files': self.files is not None,
                'experience': self.experience is not None,
                'web': bool(self.web),
                'dupes': bool(self.dupes),
            },
            'streaming_active': self.works and self.current_message is not None
        }
    
    def __repr__(self):
        """String representation"""
        return f"<{self.organ_name} organ_number={self._get_organ_number()} run_id={self.run_id}>"
