"""SubAgent synchronization logic."""

import os
import shutil
import subprocess
from pathlib import Path
from typing import Dict, Any, List
import yaml
import re

from .version_utils import validate_support_agents, validate_metadata, normalize_support_field


class SubAgentSync:
    """Handles SubAgent synchronization operations."""
    
    def __init__(self, base_dir: str, target_agents: List[str], current_versions: Dict[str, str], debug_mode: bool = False, tracker=None, parent_config_path: str = None):
        """
        Initialize SubAgentSync.
        
        Args:
            base_dir: Base directory for operations (where vibe_package.json is located)
            target_agents: List of target agents
            current_versions: Dictionary mapping agent names to current versions
            debug_mode: Enable debug logging
            tracker: PackageTracker instance to track loaded packages
            parent_config_path: Path to parent config file
        """
        self.base_dir = Path(base_dir).resolve()
        self.target_agents = target_agents
        self.current_versions = current_versions
        self.debug_mode = debug_mode
        self.tracker = tracker
        self.parent_config_path = parent_config_path
        self.vibe_package_dir = self.base_dir / '.vibe_package'
        self.vibe_agents_dir = self.vibe_package_dir / 'agents'
        
        # Create .vibe_package directory explicitly
        self.vibe_package_dir.mkdir(parents=True, exist_ok=True)
        self.vibe_agents_dir.mkdir(parents=True, exist_ok=True)
        
        self._debug_log(f"SubAgentSync initialized with base_dir: {self.base_dir}")
        self._debug_log(f"Created .vibe_package directory: {self.vibe_package_dir}")
    
    def _debug_log(self, message: str) -> None:
        """Print debug message if debug mode is enabled."""
        if self.debug_mode:
            print(f"[DEBUG] [SubAgentSync] {message}")
    
    def sync_subagent(self, subagent_name: str, subagent_config: Dict[str, Any]) -> List[str]:
        """
        Synchronize a SubAgent from git repository.
        
        Args:
            subagent_name: Name of the SubAgent
            subagent_config: SubAgent configuration dictionary
        
        Returns:
            List of warning messages
        """
        warnings = []
        
        git_url = subagent_config['git']
        git_tag = subagent_config['git_tag']
        
        self._debug_log(f"Syncing SubAgent '{subagent_name}' from {git_url} (tag/branch: {git_tag})")
        
        # Create temporary directory for cloning
        temp_dir = self.vibe_agents_dir / subagent_name
        self._debug_log(f"Temporary directory: {temp_dir}")
        
        try:
            # Clone the repository
            self._debug_log("Cloning repository...")
            self._clone_repository(git_url, git_tag, temp_dir)
            self._debug_log("Repository cloned successfully")

            # Handle subdirectory path if specified
            subagent_path = subagent_config.get('path')
            if subagent_path:
                self._debug_log(f"Using subdirectory path: {subagent_path}")
                temp_dir = temp_dir / subagent_path
                if not temp_dir.exists():
                    raise FileNotFoundError(
                        f"Path '{subagent_path}' not found in repository '{git_url}'"
                    )
                self._debug_log(f"Working in subdirectory: {temp_dir}")

            # Remove .git directory
            git_dir = temp_dir / '.git'
            if git_dir.exists():
                self._debug_log("Removing .git directory...")
                self._remove_git_directory(git_dir)
                self._debug_log(".git directory removed")

            # Find markdown files in root directory only
            self._debug_log("Searching for markdown files in root directory...")
            md_files = list(temp_dir.glob('*.md'))
            self._debug_log(f"Found {len(md_files)} markdown file(s) in root directory: {[f.name for f in md_files]}")
            
            if not md_files:
                self._debug_log("No markdown files found!")
                raise FileNotFoundError(f"No markdown file found in {subagent_name}")
            
            # Read and validate metadata from first md file
            subagent_md_path = md_files[0]
            self._debug_log(f"Using first markdown file for validation: {subagent_md_path.name}")
            
            # Read and validate SubAgent metadata
            self._debug_log("Reading SubAgent metadata...")
            meta_data = self._read_subagent_metadata(subagent_md_path)
            self._debug_log(f"Raw metadata: {meta_data}")
            # Normalize support field
            meta_data = normalize_support_field(meta_data)
            self._debug_log(f"Normalized metadata: version={meta_data.get('version')}, support_agents={meta_data.get('support_agents')}")
            
            # Validate metadata for required fields
            self._debug_log("Validating metadata for required fields...")
            metadata_warnings = validate_metadata(meta_data, f"SubAgent '{subagent_name}'")
            warnings.extend(metadata_warnings)
            self._debug_log(f"Metadata validation complete, {len(metadata_warnings)} warnings")
            
            # Validate support_agents
            self._debug_log("Validating support_agents...")
            support_agents = meta_data.get('support_agents', {})
            version_warnings = validate_support_agents(
                support_agents,
                self.target_agents,
                self.current_versions,
                f"SubAgent '{subagent_name}'"
            )
            warnings.extend(version_warnings)
            self._debug_log(f"Support_agents validation complete, {len(version_warnings)} warnings")
            
            # Copy to target directories
            self._debug_log(f"Copying to target agents: {self.target_agents}")
            for agent in self.target_agents:
                target_agents_dir = self.base_dir / f'.{agent}' / 'agents'
                target_agents_dir.mkdir(parents=True, exist_ok=True)
                
                # Copy all md files from root directory
                self._debug_log(f"Copying {len(md_files)} md file(s) to: {target_agents_dir}")
                for md_file in md_files:
                    target_file = target_agents_dir / md_file.name
                    self._debug_log(f"  Copying: {md_file.name}")
                    shutil.copy2(md_file, target_file)
                
                print(f"[OK] Synced SubAgent '{subagent_name}' ({len(md_files)} file(s)) to .{agent}/agents/")
                self._debug_log(f"Successfully copied to .{agent}/agents/")
            
            # Check for vibe_package.json and sync dependencies
            vibe_package_json = temp_dir / 'vibe_package.json'
            self._debug_log(f"Checking for vibe_package.json at: {vibe_package_json}")
            self._debug_log(f"File exists: {vibe_package_json.exists()}")
            self._debug_log(f"Tracker available: {self.tracker is not None}")
            
            if vibe_package_json.exists():
                self._debug_log(f"Found vibe_package.json in SubAgent '{subagent_name}'")
                print(f"\nFound vibe_package.json in SubAgent '{subagent_name}'")
                if self.tracker:
                    self._sync_subagent_dependencies(vibe_package_json, subagent_name)
                else:
                    print(f"Warning: No tracker available, skipping dependency check")
            else:
                self._debug_log(f"No vibe_package.json found in SubAgent '{subagent_name}'")
                # List all files in temp_dir for debugging
                all_files = list(temp_dir.iterdir())
                self._debug_log(f"Files in temp_dir: {[f.name for f in all_files]}")
            
        except Exception as e:
            self._debug_log(f"Error occurred: {e}")
            # Clean up on error
            if temp_dir.exists():
                self._debug_log("Cleaning up temporary directory...")
                shutil.rmtree(temp_dir, ignore_errors=True)
            raise e
        
        # Clean up temp directory
        self._debug_log("Cleaning up temporary directory...")
        if temp_dir.exists():
            shutil.rmtree(temp_dir, ignore_errors=True)
        self._debug_log("Cleanup complete")
        
        return warnings
    
    def _clear_readonly_attributes(self, path: Path) -> None:
        """
        Clear read-only attributes from files and directories recursively.
        """
        import stat
        
        if not path.exists():
            return
        
        try:
            # Clear read-only attribute from the path itself
            if path.is_file():
                path.chmod(stat.S_IWRITE)
            elif path.is_dir():
                # Recursively clear attributes from contents
                for item in path.rglob('*'):
                    if item.is_file():
                        try:
                            item.chmod(stat.S_IWRITE)
                        except:
                            pass
        except Exception as e:
            self._debug_log(f"Failed to clear readonly attributes: {e}")
    
    def _remove_git_directory(self, git_dir: Path, max_retries: int = 5) -> None:
        """
        Remove .git directory with retry mechanism. If removal fails, skip and continue.
        
        Args:
            git_dir: Path to .git directory
            max_retries: Maximum number of retry attempts
        """
        import time
        
        for attempt in range(max_retries):
            try:
                # Clear read-only attributes before removal
                self._clear_readonly_attributes(git_dir)
                shutil.rmtree(git_dir)
                self._debug_log(f"Successfully removed .git directory on attempt {attempt + 1}")
                return
            except (OSError, PermissionError) as e:
                self._debug_log(f"Attempt {attempt + 1} failed to remove .git: {e}")
                if attempt < max_retries - 1:
                    # Wait before retry with increasing delay
                    time.sleep(0.5 * (attempt + 1))
                    continue
                else:
                    # Last attempt failed, try using git to clean up
                    self._debug_log("Attempting git clean as last resort")
                    try:
                        subprocess.run(
                            ['git', 'clean', '-fdx'],
                            cwd=git_dir.parent,
                            capture_output=True,
                            text=True,
                            timeout=10
                        )
                        self._debug_log("Git clean completed, trying rmtree again")
                        # Try again after git clean
                        shutil.rmtree(git_dir)
                        self._debug_log("Successfully removed .git after git clean")
                        return
                    except Exception as e2:
                        self._debug_log(f"Git clean and rmtree also failed: {e2}")
                        # If all attempts fail, just skip and continue
                        print(f"Warning: Failed to remove .git directory, skipping...")
                        return
    
    def _clone_repository(self, git_url: str, git_tag: str, target_dir: Path) -> None:
        """Clone a git repository and checkout specific tag/branch."""
        # Kill any existing git processes that might be holding locks on this directory
        if target_dir.exists():
            self._debug_log("Attempting to kill any git processes holding locks...")
            try:
                subprocess.run(
                    ['taskkill', '/F', '/IM', 'git.exe'],
                    capture_output=True,
                    text=True,
                    timeout=5
                )
                self._debug_log("Attempted to kill git.exe processes")
            except Exception as e:
                self._debug_log(f"Failed to kill git processes: {e}")
            
            # Wait a bit for file handles to be released
            import time
            time.sleep(1)
        
        # Remove existing directory if it exists
        if target_dir.exists():
            self._debug_log(f"Removing existing directory: {target_dir}")
            
            # Try to use git gc to clean up and release locks
            try:
                subprocess.run(
                    ['git', 'gc', '--prune=now', '--aggressive'],
                    cwd=target_dir,
                    capture_output=True,
                    text=True,
                    timeout=30
                )
                self._debug_log("Git gc completed")
            except Exception as e:
                self._debug_log(f"Git gc failed: {e}")
            
            # Try to use git clean first to release file locks
            try:
                subprocess.run(
                    ['git', 'clean', '-fdx'],
                    cwd=target_dir,
                    capture_output=True,
                    text=True,
                    timeout=5
                )
                self._debug_log("Git clean completed")
            except Exception as e:
                self._debug_log(f"Git clean failed: {e}")
            
            # Now try to remove the directory
            try:
                shutil.rmtree(target_dir)
                self._debug_log("Directory removed successfully")
            except Exception as e:
                self._debug_log(f"Failed to remove directory: {e}")
                # Try to remove only .git directory first to release locks
                git_dir = target_dir / '.git'
                if git_dir.exists():
                    try:
                        self._remove_git_directory(git_dir)
                        self._debug_log("Removed .git directory, trying to remove rest")
                        # Now try to remove the rest
                        shutil.rmtree(target_dir)
                        self._debug_log("Directory removed after removing .git")
                    except Exception as e2:
                        self._debug_log(f"Still failed after removing .git: {e2}")
                        # If removal still fails, try using a different approach
                        # Use a temporary name and move the old directory
                        import time
                        import uuid
                        temp_backup = target_dir.parent / f"{target_dir.name}_backup_{int(time.time())}_{uuid.uuid4().hex[:8]}"
                        self._debug_log(f"Moving to backup: {temp_backup}")
                        try:
                            shutil.move(str(target_dir), str(temp_backup))
                            self._debug_log("Old directory moved to backup")
                        except Exception as e3:
                            self._debug_log(f"Failed to move to backup: {e3}")
                            # If all else fails, just create a new unique directory
                            target_dir = target_dir.parent / f"{target_dir.name}_{int(time.time())}_{uuid.uuid4().hex[:8]}"
                            self._debug_log(f"Using new directory: {target_dir}")
        
        # Clone repository
        try:
            result = subprocess.run(
                ['git', 'clone', git_url, str(target_dir)],
                check=True,
                capture_output=True,
                text=True
            )
        except subprocess.CalledProcessError as e:
            error_msg = f"Failed to clone repository from '{git_url}'"
            if e.stderr:
                error_msg += f"\nGit error: {e.stderr.strip()}"
            raise RuntimeError(error_msg) from e
        
        # Checkout tag or branch
        try:
            subprocess.run(
                ['git', 'checkout', git_tag],
                cwd=target_dir,
                check=True,
                capture_output=True,
                text=True
            )
        except subprocess.CalledProcessError as e:
            # Get available branches and tags
            branches_result = subprocess.run(
                ['git', 'branch', '-a'],
                cwd=target_dir,
                capture_output=True,
                text=True
            )
            tags_result = subprocess.run(
                ['git', 'tag'],
                cwd=target_dir,
                capture_output=True,
                text=True
            )
            
            available_branches = branches_result.stdout.strip().split('\n') if branches_result.stdout.strip() else []
            available_tags = tags_result.stdout.strip().split('\n') if tags_result.stdout.strip() else []
            
            error_msg = f"Failed to checkout '{git_tag}' from repository '{git_url}'"
            error_msg += f"\n\nAvailable branches:"
            if available_branches:
                for branch in available_branches:
                    error_msg += f"\n  - {branch.strip().replace('*', '').strip()}"
            else:
                error_msg += "\n  (none)"
            
            error_msg += f"\n\nAvailable tags:"
            if available_tags:
                for tag in available_tags:
                    error_msg += f"\n  - {tag.strip()}"
            else:
                error_msg += "\n  (none)"
            
            if e.stderr:
                error_msg += f"\n\nGit error: {e.stderr.strip()}"
            
            raise RuntimeError(error_msg) from e
    
    def _read_subagent_metadata(self, subagent_md_path: Path) -> Dict[str, Any]:
        """
        Read metadata from SubAgent markdown file.
        
        Args:
            subagent_md_path: Path to SubAgent markdown file
        
        Returns:
            Dictionary containing metadata
        """
        with open(subagent_md_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        # Extract YAML frontmatter
        match = re.match(r'^---\s*\n(.*?)\n---\s*\n', content, re.DOTALL)
        if not match:
            return {}
        
        yaml_content = match.group(1)
        return yaml.safe_load(yaml_content)
    
    def _sync_subagent_dependencies(self, config_path: Path, subagent_name: str) -> None:
        """
        Sync dependencies from a SubAgent's vibe_package.json.
        
        Args:
            config_path: Path to vibe_package.json
            subagent_name: Name of the SubAgent (for logging)
        """
        self._debug_log(f"Syncing dependencies from {subagent_name}'s vibe_package.json")
        self._debug_log(f"Config path: {config_path}")
        self._debug_log(f"Config exists: {config_path.exists()}")
        
        # Check for circular dependencies
        if self.tracker.check_circular(config_path):
            self._debug_log(f"Warning: Circular dependency detected, skipping {config_path}")
            print(f"Warning: Circular dependency detected, skipping {config_path}")
            return
        
        # Check if already loaded
        if self.tracker.is_config_loaded(config_path):
            self._debug_log(f"Config already loaded, skipping: {config_path}")
            return
        
        # Mark as loaded and push to stack
        self.tracker.mark_config_loaded(config_path)
        self.tracker.push_config(config_path)
        
        # Initialize temporary config path
        temp_config_path = None
        
        try:
            # Load configuration
            import json
            import tempfile
            import os
            from .config_parser import VibePackageConfig
            
            # Read the config file
            with open(config_path, 'r', encoding='utf-8') as f:
                config_data = json.load(f)
            
            # Check if target_agents field needs to use parent's target_agents
            # "parent", empty array, or empty string means use parent's target_agents
            original_target_agents = config_data.get('target_agents', [])
            temp_config_path = None
            
            should_use_parent_target_agents = (
                not original_target_agents or  # empty or missing
                original_target_agents == ["parent"] or  # explicit "parent"
                (isinstance(original_target_agents, str) and original_target_agents == "parent")  # string "parent"
            )
            
            if should_use_parent_target_agents:
                self._debug_log(f"Config target_agents is 'parent' or empty, using parent's: {self.target_agents}")
                
                # Create a temporary config file with parent's target_agents
                config_data['target_agents'] = self.target_agents
                
                # Create a temporary file
                with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as temp_file:
                    json.dump(config_data, temp_file, indent=2, ensure_ascii=False)
                    temp_config_path = temp_file.name
                
                # Load from temporary file
                config = VibePackageConfig(temp_config_path)
            else:
                # Load from original file
                config = VibePackageConfig(str(config_path))
            
            # Use parent's target_agents (ignore the config's target_agents)
            target_agents = self.target_agents
            
            print(f"\n{'='*60}")
            print(f"Syncing dependencies from SubAgent: {subagent_name}")
            print(f"{'='*60}")
            
            # Use parent's target_agents, ignore the config's target_agents
            target_agents = self.target_agents
            
            # Sync SKILLs
            skills = config.get_skills()
            if skills:
                print(f"\nFound {len(skills)} SKILL(s) in SubAgent dependencies")
                from .skill_sync import SkillSync
                skill_sync = SkillSync(
                    str(self.base_dir),
                    target_agents,
                    self.current_versions,
                    self.debug_mode,
                    self.tracker,
                    str(config_path)
                )
                
                for skill_name, skill_config in skills.items():
                    if self.tracker.is_skill_loaded(skill_name):
                        self._debug_log(f"SKILL '{skill_name}' already loaded, skipping")
                        continue
                    
                    try:
                        print(f"\nProcessing SKILL: {skill_name}")
                        warnings = skill_sync.sync_skill(skill_name, skill_config)
                        self.tracker.mark_skill_loaded(skill_name)
                    except Exception as e:
                        self._debug_log(f"Error syncing SKILL '{skill_name}': {e}")
                        print(f"Error syncing SKILL '{skill_name}': {e}")
            
            # Sync SubAgents (recursive)
            subagents = config.get_subagents()
            if subagents:
                print(f"\nFound {len(subagents)} SubAgent(s) in SubAgent dependencies")
                
                for subagent_dep_name, subagent_dep_config in subagents.items():
                    if self.tracker.is_subagent_loaded(subagent_dep_name):
                        self._debug_log(f"SubAgent '{subagent_dep_name}' already loaded, skipping")
                        continue
                    
                    try:
                        print(f"\nProcessing SubAgent: {subagent_dep_name}")
                        warnings = self.sync_subagent(subagent_dep_name, subagent_dep_config)
                        self.tracker.mark_subagent_loaded(subagent_dep_name)
                    except Exception as e:
                        self._debug_log(f"Error syncing SubAgent '{subagent_dep_name}': {e}")
                        print(f"Error syncing SubAgent '{subagent_dep_name}': {e}")
            
            # Install MCPs
            mcps = config.get_mcps()
            if mcps:
                print(f"\nFound {len(mcps)} MCP(s) in SubAgent dependencies")
                from .mcp_installer import MCPInstaller
                mcp_installer = MCPInstaller(
                    str(self.base_dir),
                    target_agents,
                    self.debug_mode,
                    self.tracker
                )
                
                for mcp_name, mcp_config in mcps.items():
                    if self.tracker.is_mcp_loaded(mcp_name):
                        self._debug_log(f"MCP '{mcp_name}' already installed, skipping")
                        continue
                    
                    try:
                        print(f"\nProcessing MCP: {mcp_name}")
                        mcp_installer.install_mcp(mcp_name, mcp_config)
                        self.tracker.mark_mcp_loaded(mcp_name)
                    except Exception as e:
                        self._debug_log(f"Error installing MCP '{mcp_name}': {e}")
                        print(f"Error installing MCP '{mcp_name}': {e}")
            
            print(f"\n{'='*60}")
            print(f"Finished syncing dependencies from SubAgent: {subagent_name}")
            print(f"{'='*60}")
            
        except Exception as e:
            self._debug_log(f"Error syncing dependencies from {subagent_name}: {e}")
            print(f"Error syncing dependencies from {subagent_name}: {e}")
        finally:
            # Pop from stack
            self.tracker.pop_config()
            
            # Clean up temporary config file if it was created
            if temp_config_path and os.path.exists(temp_config_path):
                try:
                    os.unlink(temp_config_path)
                    self._debug_log(f"Cleaned up temporary config file: {temp_config_path}")
                except Exception as e:
                    self._debug_log(f"Failed to clean up temporary config file: {e}")