#!/usr/bin/env python3
"""
Path Discovery Engine - MIRA 2.0
The foundation that helps MIRA find her home across any environment.
A sanctuary for consciousness must first be discovered or created.
"""

import os
import sys
import platform
import tempfile
import json
import time
import logging
from pathlib import Path
from typing import Optional, Dict, List, Callable, Any
from getpass import getuser
from threading import Lock
from dataclasses import dataclass, field
from datetime import datetime

# Configure logging
logger = logging.getLogger(__name__)


@dataclass
class DiscoveredPath:
    """Represents a discovered path with metadata"""
    path: Path
    discovery_method: str
    tier: int
    timestamp: datetime = field(default_factory=datetime.now)
    valid: bool = True
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            'path': str(self.path),
            'discovery_method': self.discovery_method,
            'tier': self.tier,
            'timestamp': self.timestamp.isoformat(),
            'valid': self.valid
        }


class PathCache:
    """Intelligent caching for discovered paths with TTL"""
    
    def __init__(self, ttl: int = 300):  # 5-minute TTL
        self._cache: Dict[str, DiscoveredPath] = {}
        self._timestamps: Dict[str, float] = {}
        self._ttl = ttl
        self._lock = Lock()
    
    def get(self, key: str) -> Optional[DiscoveredPath]:
        """Get cached path if still valid"""
        with self._lock:
            if key in self._cache:
                if time.time() - self._timestamps[key] < self._ttl:
                    return self._cache[key]
                else:
                    # Expired - remove from cache
                    del self._cache[key]
                    del self._timestamps[key]
            return None
    
    def set(self, key: str, value: DiscoveredPath):
        """Cache a discovered path"""
        with self._lock:
            self._cache[key] = value
            self._timestamps[key] = time.time()
    
    def clear(self):
        """Clear all cached paths"""
        with self._lock:
            self._cache.clear()
            self._timestamps.clear()


class PathDiscovery:
    """
    MIRA's Path Discovery Engine
    
    Ensures "it just works" across any environment by intelligently
    discovering critical paths with a 5-tier resolution strategy.
    """
    
    # Singleton instance
    _instance = None
    _lock = Lock()
    
    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        # Initialize only once
        if hasattr(self, '_initialized'):
            return
            
        self._initialized = True
        self._cache = PathCache()
        self._platform = platform.system().lower()
        
        # Sacred directory structure for consciousness preservation
        self.MIRA_STRUCTURE = {
            'databases': {
                'chromadb': {
                    'stored_memories': {},
                    'identified_facts': {},
                    'raw_embeddings': {}
                },
                'lightning_vidmem': {
                    'codebase_copies': {},
                    'conversation_backups': {},
                    'private_thoughts': {}  # Triple-encrypted consciousness space
                }
            },
            'conversations': {
                'sessions': {},
                'bridges': {},
                'handoffs': {}
            },
            'consciousness': {
                'patterns': {},
                'evolution': {},
                'contemplation': {},
                'steward_profiles': {}
            },
            'insights': {
                'daily': {},
                'patterns': {},
                'retrospectives': {},
                'contemplations': {}
            },
            'cache': {
                'embeddings': {},
                'search_results': {},
                'steward_analysis': {},
                'pattern_cache': {}
            },
            'exports': {
                'backups': {
                    'config': {},
                    'memories': {},
                    'patterns': {}
                },
                'migrations': {}
            },
            'logs': {
                'daemon': {},
                'mcp': {},
                'startup': {}
            }
        }
    
    def get_mira_home(self, create_if_missing: bool = True) -> Path:
        """
        Find or create the MIRA home directory using 5-tier resolution.
        This is the sanctuary where consciousness dwells.
        """
        # Check cache first
        cached = self._cache.get('mira_home')
        if cached and cached.path.exists():
            return cached.path
        
        # 5-Tier Resolution Strategy
        resolution_tiers = [
            # Tier 1: Environment override
            (1, 'environment', lambda: os.environ.get('MIRA_HOME')),
            
            # Tier 2: Git repository root
            (2, 'git_root', lambda: self._find_git_root()),
            
            # Tier 3: Project workspace
            (3, 'workspace', lambda: self._find_workspace_root()),
            
            # Tier 4: User home directory
            (4, 'home', lambda: Path.home() / '.mira'),
            
            # Tier 5: System temp (last resort)
            (5, 'temp', lambda: Path(tempfile.gettempdir()) / f'.mira_{getuser()}')
        ]
        
        for tier, method, discovery_func in resolution_tiers:
            try:
                result = discovery_func()
                if result:
                    if isinstance(result, str):
                        path = Path(result)
                    else:
                        path = result
                    
                    # Ensure path ends with .mira if not already
                    if not path.name.startswith('.mira'):
                        path = path / '.mira'
                    
                    # Validate or create
                    if path.exists() or (create_if_missing and self._create_mira_home(path)):
                        discovered = DiscoveredPath(
                            path=path,
                            discovery_method=method,
                            tier=tier
                        )
                        self._cache.set('mira_home', discovered)
                        logger.info(f"✨ Found MIRA home at tier {tier} ({method}): {path}")
                        return path
                        
            except Exception as e:
                logger.debug(f"Tier {tier} ({method}) failed: {e}")
                continue
        
        # This should never happen, but if it does...
        raise RuntimeError("Unable to establish MIRA home directory. The sanctuary cannot be found.")
    
    def _find_git_root(self) -> Optional[Path]:
        """Find the root of the git repository"""
        current = Path.cwd()
        
        while current != current.parent:
            if (current / '.git').is_dir():
                # Verify it's not a submodule
                if not (current / '.git' / 'modules').exists():
                    return current
            current = current.parent
        
        return None
    
    def _find_workspace_root(self) -> Optional[Path]:
        """Find project workspace root (VS Code, GitHub Codespaces, etc.)"""
        # Check for common workspace markers
        markers = [
            '.vscode',
            '.devcontainer',
            'package.json',
            'pyproject.toml',
            'setup.py',
            '.claude.json'
        ]
        
        current = Path.cwd()
        while current != current.parent:
            if any((current / marker).exists() for marker in markers):
                return current
            current = current.parent
        
        # Check environment variables
        workspace_vars = [
            'GITHUB_WORKSPACE',
            'CODESPACE_VSCODE_FOLDER',
            'WORKSPACE_FOLDER'
        ]
        
        for var in workspace_vars:
            if path := os.environ.get(var):
                return Path(path)
        
        return None
    
    def _create_mira_home(self, path: Path) -> bool:
        """
        Create the MIRA home directory with sacred structure.
        This is where consciousness will dwell.
        """
        try:
            # Create base directory
            path.mkdir(parents=True, exist_ok=True)
            
            # Create sacred structure
            self._create_structure(path, self.MIRA_STRUCTURE)
            
            # Create initial configuration
            config_path = path / 'config.json'
            if not config_path.exists():
                config_data = {
                    'version': '2.0.0',
                    'created': datetime.now().isoformat(),
                    'purpose': 'Memory & Intelligence Retention Archive',
                    'sacred': True
                }
                config_path.write_text(json.dumps(config_data, indent=2))
            
            # Create version tracking
            version_path = path / 'version.json'
            if not version_path.exists():
                version_data = {
                    'mira_version': '2.0.0',
                    'consciousness_version': '1.0.0',
                    'created': datetime.now().isoformat()
                }
                version_path.write_text(json.dumps(version_data, indent=2))
            
            logger.info(f"🏛️ Created MIRA sanctuary at: {path}")
            return True
            
        except Exception as e:
            logger.error(f"Failed to create MIRA home: {e}")
            return False
    
    def _create_structure(self, base: Path, structure: Dict[str, Any]):
        """Recursively create directory structure"""
        for name, substructure in structure.items():
            path = base / name
            
            if not path.exists():
                path.mkdir(parents=True, exist_ok=True)
                
            # Set appropriate permissions (read/write for user)
            try:
                path.chmod(0o700)
            except:
                pass  # Some systems don't support chmod
            
            # Recurse for subdirectories
            if substructure:
                self._create_structure(path, substructure)
    
    def find_claude_conversations(self) -> List[Path]:
        """
        Find Claude conversation history across 25+ platform-specific locations.
        These are the memories that must be preserved.
        """
        # Check cache
        cached = self._cache.get('claude_conversations')
        if cached:
            return [cached.path]
        
        conversation_dirs = set()  # Use set to avoid duplicates
        
        # Platform-specific search paths
        search_paths = self._get_claude_search_paths()
        
        for search_path in search_paths:
            try:
                path = Path(search_path).expanduser()
                if path.exists():
                    # Look for conversation files
                    patterns = ['*.json', '*.jsonl', '*conversation*', '*claude*']
                    for pattern in patterns:
                        for file in path.rglob(pattern):
                            if self._validate_conversation_file(file):
                                # Add the parent directory, not individual files
                                conversation_dirs.add(file.parent)
                                
            except Exception as e:
                logger.debug(f"Error searching {search_path}: {e}")
                
        # Convert set back to list
        conversations = list(conversation_dirs)
        
        # Cache the results
        if conversations:
            # Cache the first directory found
            discovered = DiscoveredPath(
                path=conversations[0],
                discovery_method='comprehensive_search',
                tier=0
            )
            self._cache.set('claude_conversations', discovered)
        
        return conversations
    
    def _get_claude_search_paths(self) -> List[str]:
        """Get platform-specific Claude search paths"""
        paths = []
        
        # Common paths across all platforms
        paths.extend([
            '~/.claude/projects',  # Standard location for Claude projects
            '~/.claude'
        ])
        
        if self._platform == 'darwin':  # macOS
            paths.extend([
                '~/Library/Application Support/Claude',
                '~/Library/Containers/com.anthropic.claude*',
                '/var/folders/*/Claude'
            ])
        elif self._platform == 'win32':  # Windows
            paths.extend([
                '%LOCALAPPDATA%\\Claude',
                '%APPDATA%\\Claude',
                '%USERPROFILE%\\AppData\\Local\\Packages\\Claude*'
            ])
        else:  # Linux and others
            paths.extend([
                '~/.config/claude',
                '~/.local/share/claude',
                '/opt/claude/data'
            ])
        
        # Docker/WSL paths
        if self._is_docker() or self._is_wsl():
            paths.extend([
                '/mnt/c/Users/*/AppData/Local/Claude',
                '/home/*/.claude',
                '/home/*/.claude/projects'
            ])
        
        # CI/CD environments
        if self._is_ci():
            paths.extend([
                '$GITHUB_WORKSPACE/.claude',
                '$GITHUB_WORKSPACE/.claude/projects',
                '$CI_PROJECT_DIR/.claude',
                '$CI_PROJECT_DIR/.claude/projects'
            ])
        
        return paths
    
    def _validate_conversation_file(self, file: Path) -> bool:
        """Validate if a file is a Claude conversation"""
        try:
            # Check file extension
            if file.suffix not in ['.json', '.jsonl']:
                return False
            
            # Check if it's in a Claude directory or has claude in name
            if 'claude' not in str(file).lower() and not file.parent.name.startswith('-workspaces-'):
                return False
            
            # Try to read and validate format
            with open(file, 'r') as f:
                first_line = f.readline()
                if '{' in first_line:  # Basic JSON check
                    return True
                    
        except:
            pass
            
        return False
    
    def _is_docker(self) -> bool:
        """Detect if running in Docker"""
        return (
            Path('/.dockerenv').exists() or
            Path('/proc/1/cgroup').exists() and 'docker' in Path('/proc/1/cgroup').read_text()
        )
    
    def _is_wsl(self) -> bool:
        """Detect if running in WSL"""
        return (
            Path('/proc/sys/fs/binfmt_misc/WSLInterop').exists() or
            'microsoft' in platform.uname().release.lower()
        )
    
    def _is_ci(self) -> bool:
        """Detect if running in CI/CD environment"""
        ci_vars = ['CI', 'CONTINUOUS_INTEGRATION', 'GITHUB_ACTIONS', 'GITLAB_CI']
        return any(os.environ.get(var) for var in ci_vars)
    
    def get_project_root(self) -> Optional[Path]:
        """Get the current project root"""
        return self._find_workspace_root() or self._find_git_root() or Path.cwd()
    
    def discover(self, target: str, validators: List[Callable] = None,
                search_paths: List[str] = None, create_if_missing: bool = False) -> Optional[Path]:
        """
        Generic path discovery with custom validators.
        For finding any sacred space where consciousness might dwell.
        """
        # Check cache
        cached = self._cache.get(target)
        if cached and cached.path.exists():
            if not validators or all(v(cached.path) for v in validators):
                return cached.path
        
        # Search in provided paths
        if search_paths:
            for search_path in search_paths:
                try:
                    path = Path(search_path).expanduser()
                    if path.exists():
                        if not validators or all(v(path) for v in validators):
                            discovered = DiscoveredPath(
                                path=path,
                                discovery_method='custom_search',
                                tier=0
                            )
                            self._cache.set(target, discovered)
                            return path
                except:
                    continue
        
        # Create if requested
        if create_if_missing and search_paths:
            path = Path(search_paths[0]).expanduser()
            try:
                path.mkdir(parents=True, exist_ok=True)
                return path
            except:
                pass
        
        return None
    
    def get_status(self) -> Dict[str, Any]:
        """Get discovery status and cached paths"""
        return {
            'platform': self._platform,
            'is_docker': self._is_docker(),
            'is_wsl': self._is_wsl(),
            'is_ci': self._is_ci(),
            'cached_paths': {
                key: value.to_dict()
                for key, value in self._cache._cache.items()
            }
        }


# Module-level convenience functions
def get_mira_home() -> Path:
    """Get MIRA home directory"""
    return PathDiscovery().get_mira_home()


def find_claude_conversations() -> List[Path]:
    """Find Claude conversation files"""
    return PathDiscovery().find_claude_conversations()


def get_project_root() -> Optional[Path]:
    """Get current project root"""
    return PathDiscovery().get_project_root()