"""SKILL synchronization logic."""

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

from .version_utils import validate_support_agents, validate_metadata, normalize_support_field


class SkillSync:
    """Handles SKILL 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 SkillSync.
        
        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_skills_dir = self.vibe_package_dir / 'skills'
        
        # Create .vibe_package directory explicitly
        self.vibe_package_dir.mkdir(parents=True, exist_ok=True)
        self.vibe_skills_dir.mkdir(parents=True, exist_ok=True)
        
        self._debug_log(f"SkillSync 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] [SkillSync] {message}")
    
    def sync_skill(self, skill_name: str, skill_config: Dict[str, Any]) -> List[str]:
        """
        Synchronize a SKILL from git repository.
        
        Args:
            skill_name: Name of the SKILL
            skill_config: SKILL configuration dictionary
        
        Returns:
            List of warning messages
        """
        warnings = []
        
        git_url = skill_config['git']
        git_tag = skill_config['git_tag']
        
        self._debug_log(f"Syncing SKILL '{skill_name}' from {git_url} (tag/branch: {git_tag})")
        
        # Create temporary directory for cloning
        temp_dir = self.vibe_skills_dir / skill_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
            skill_path = skill_config.get('path')
            if skill_path:
                self._debug_log(f"Using subdirectory path: {skill_path}")
                temp_dir = temp_dir / skill_path
                if not temp_dir.exists():
                    raise FileNotFoundError(
                        f"Path '{skill_path}' not found in repository '{git_url}'"
                    )
                self._debug_log(f"Working in subdirectory: {temp_dir}")

            # Handle single directory case
            self._debug_log("Checking for single directory structure...")
            temp_dir = self._handle_single_directory(temp_dir)
            self._debug_log(f"Working directory: {temp_dir}")
            
            # Verify that files exist after handling single directory
            self._debug_log(f"Verifying directory contents after single directory handling")
            items = list(temp_dir.iterdir())
            self._debug_log(f"Directory contains {len(items)} items: {[item.name for item in items[:10]]}")
            
            # 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")
            
            # Check for SKILL.md
            skill_md_path = temp_dir / 'SKILL.md'
            self._debug_log(f"Checking for SKILL.md at: {skill_md_path}")
            
            # List all files in the directory for debugging
            all_files = list(temp_dir.rglob('*'))
            all_files = [f for f in all_files if f.is_file()]
            self._debug_log(f"All files in directory: {[str(f.relative_to(temp_dir)) for f in all_files[:20]]}")
            
            if not skill_md_path.exists():
                self._debug_log("SKILL.md not found!")
                self._debug_log(f"Available markdown files: {[f.name for f in temp_dir.glob('*.md')]}")
                raise FileNotFoundError(f"SKILL.md not found in {skill_name}")
            self._debug_log("SKILL.md found")
            
            # Read and validate SKILL.md
            self._debug_log("Reading SKILL.md metadata...")
            meta_data = self._read_skill_metadata(skill_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"SKILL '{skill_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"SKILL '{skill_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_dir = self.base_dir / f'.{agent}' / 'skills' / skill_name
                self._debug_log(f"Copying to: {target_dir}")
                self._copy_to_target(temp_dir, target_dir)
                print(f"[OK] Synced SKILL '{skill_name}' to .{agent}/skills/")
                self._debug_log(f"Successfully copied to .{agent}/skills/")
            
        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.
        
        Args:
            path: Path to clear attributes from
        """
        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
            )
            self._debug_log(f"Clone completed successfully")
        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
        
        # Verify clone was successful
        if not target_dir.exists():
            raise RuntimeError(f"Clone completed but target directory does not exist: {target_dir}")
        
        # Checkout tag or branch
        try:
            checkout_result = subprocess.run(
                ['git', 'checkout', git_tag],
                cwd=target_dir,
                check=True,
                capture_output=True,
                text=True
            )
            self._debug_log(f"Checkout completed successfully: {git_tag}")
        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 _handle_single_directory(self, dir_path: Path) -> Path:
        """
        Handle case where repository root has only one directory containing files.
        
        Args:
            dir_path: Directory to check
        
        Returns:
            Path to use (either original or the single subdirectory)
        """
        items = list(dir_path.iterdir())
        self._debug_log(f"Checking directory structure: {len(items)} items found")
        
        # Check if there's exactly one item and it's a directory
        if len(items) == 1 and items[0].is_dir():
            sub_dir = items[0]
            self._debug_log(f"Found single subdirectory: {sub_dir.name}")
            
            # Check if subdirectory has files
            sub_items = list(sub_dir.iterdir())
            self._debug_log(f"Subdirectory contains {len(sub_items)} items")
            
            if sub_items:
                # Move all files from subdirectory to parent
                self._debug_log(f"Moving files from subdirectory to parent")
                for item in sub_items:
                    try:
                        shutil.move(str(item), str(dir_path / item.name))
                        self._debug_log(f"Moved: {item.name}")
                    except Exception as e:
                        self._debug_log(f"Failed to move {item.name}: {e}")
                        raise
                
                # Remove empty subdirectory
                try:
                    shutil.rmtree(sub_dir)
                    self._debug_log(f"Removed empty subdirectory: {sub_dir.name}")
                except Exception as e:
                    self._debug_log(f"Failed to remove subdirectory {sub_dir.name}: {e}")
                    # Non-fatal, continue
            else:
                self._debug_log(f"Subdirectory is empty, skipping")
        else:
            self._debug_log(f"Directory structure is normal (not single subdirectory)")
        
        return dir_path
    
    def _read_skill_metadata(self, skill_md_path: Path) -> Dict[str, Any]:
        """
        Read metadata from SKILL.md file.
        
        Args:
            skill_md_path: Path to SKILL.md file
        
        Returns:
            Dictionary containing metadata
        """
        with open(skill_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 _copy_to_target(self, source_dir: Path, target_dir: Path) -> None:
        """Copy directory contents to target directory, excluding .git directory."""
        # Remove existing target directory if it exists
        if target_dir.exists():
            shutil.rmtree(target_dir)
        
        # Create target directory
        target_dir.parent.mkdir(parents=True, exist_ok=True)
        
        # Copy contents, excluding .git directory
        shutil.copytree(source_dir, target_dir, ignore=shutil.ignore_patterns('.git'))