"""SKILL synchronization logic."""

import hashlib
import re
import shutil
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

import yaml

from .base_sync import BaseSync, VIBE_PACKAGE_DIR
from .version_utils import normalize_support_field, validate_metadata, validate_support_agents


class SkillSync(BaseSync):
    """Handles SKILL synchronization operations."""

    def __init__(
        self,
        base_dir: str,
        target_agents: List[str],
        current_versions: Dict[str, str],
        debug_mode: bool = False,
        tracker: Optional[Any] = None,
        parent_config_path: Optional[str] = None,
    ):
        """
        Initialize SkillSync.

        Args:
            base_dir: Base directory for operations
            target_agents: List of target agents
            current_versions: Dictionary mapping agent names to current versions
            debug_mode: Enable debug logging
            tracker: PackageTracker instance
            parent_config_path: Path to parent config file
        """
        super().__init__(
            base_dir, target_agents, current_versions, debug_mode, tracker, parent_config_path
        )
        self.vibe_skills_dir = self.vibe_package_dir / "skills"
        self.vibe_skills_dir.mkdir(parents=True, exist_ok=True)
        self._debug_log(f"Created skills directory: {self.vibe_skills_dir}")

        # Cache for cloned repositories: {(git_url, git_tag): repo_cache_dir}
        self._repo_cache: Dict[Tuple[str, str], Path] = {}
        # Track which cache dirs are used by which skills: {repo_cache_dir: {skill_names}}
        self._cache_refs: Dict[Path, set] = {}

    def _get_cache_key(self, git_url: str, git_tag: str) -> Tuple[str, str]:
        """Generate cache key from git URL and tag."""
        return (git_url, git_tag)

    def _get_repo_cache_dir(self, git_url: str, git_tag: str) -> Path:
        """
        Generate unique cache directory for a repository.

        Uses URL hash to avoid collisions between different repos with same name.
        Format: .vibe_package/skills/_cache/{url_hash_short}_{tag_sanitized}/
        """
        # Create hash of full URL to uniquely identify the repository
        url_hash = hashlib.sha256(git_url.encode()).hexdigest()[:12]
        # Sanitize tag for filesystem (replace special chars)
        safe_tag = re.sub(r'[^a-zA-Z0-9._-]', '_', git_tag)
        cache_name = f"{url_hash}_{safe_tag}"
        return self.vibe_skills_dir / "_cache" / cache_name

    def _get_or_clone_repo(self, git_url: str, git_tag: str) -> Path:
        """
        Get cached repository or clone if not exists.

        Args:
            git_url: Git repository URL
            git_tag: Tag or branch to checkout

        Returns:
            Path to cached repository directory
        """
        cache_key = self._get_cache_key(git_url, git_tag)

        # Return cached repo if already cloned
        if cache_key in self._repo_cache:
            cache_dir = self._repo_cache[cache_key]
            self._debug_log(f"Using cached repository: {cache_dir}")
            return cache_dir

        # Generate cache directory path
        cache_dir = self._get_repo_cache_dir(git_url, git_tag)
        self._debug_log(f"Cache directory: {cache_dir}")

        # Clone repository
        self._debug_log(f"Cloning repository {git_url} (tag: {git_tag})...")
        self._clone_repository(git_url, git_tag, cache_dir)
        self._debug_log("Repository cloned successfully")

        # Store in cache
        self._repo_cache[cache_key] = cache_dir
        self._cache_refs[cache_dir] = set()

        return cache_dir

    def sync_skill(self, skill_name: str, skill_config: Dict[str, Any]) -> List[str]:
        """
        Synchronize a SKILL from git repository.

        Uses repository caching to avoid cloning the same repo multiple times.
        Each skill gets its own working copy from the cache.

        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})")

        # Get or clone repository (cached)
        repo_cache_dir = self._get_or_clone_repo(git_url, git_tag)

        # Create skill-specific working directory
        # Use skill name + unique suffix to avoid collisions
        work_dir = self.vibe_skills_dir / f"{skill_name}_{hashlib.sha256(skill_name.encode()).hexdigest()[:8]}"
        self._debug_log(f"Working directory: {work_dir}")

        # Track cache reference
        self._cache_refs[repo_cache_dir].add(skill_name)

        try:
            # Copy from cache to working directory
            self._debug_log(f"Copying from cache to working directory...")
            if work_dir.exists():
                shutil.rmtree(work_dir)
            shutil.copytree(repo_cache_dir, work_dir)

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

            # Handle single directory case
            self._debug_log("Checking for single directory structure...")
            work_dir = self._handle_single_directory(work_dir)
            self._debug_log(f"Working directory: {work_dir}")

            # Verify that files exist
            self._debug_log("Verifying directory contents")
            items = list(work_dir.iterdir())
            self._debug_log(f"Directory contains {len(items)} items")

            # Remove .git directory from working copy
            git_dir = work_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 = work_dir / "SKILL.md"
            self._debug_log(f"Checking for SKILL.md at: {skill_md_path}")

            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 work_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 and validate metadata
            meta_data = normalize_support_field(meta_data)
            self._debug_log(f"Normalized metadata: {meta_data}")

            metadata_warnings = validate_metadata(meta_data, f"SKILL '{skill_name}'")
            warnings.extend(metadata_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(work_dir, target_dir)
                print(f"[OK] Synced SKILL '{skill_name}' to .{agent}/skills/")
                self._debug_log(f"Successfully copied to .{agent}/skills/")

        except Exception:
            # Clean up on error
            if work_dir.exists():
                self._debug_log("Cleaning up working directory...")
                shutil.rmtree(work_dir, ignore_errors=True)
            raise
        finally:
            # Clean up skill working directory
            self._debug_log("Cleaning up working directory...")
            if work_dir.exists():
                shutil.rmtree(work_dir, ignore_errors=True)
            self._debug_log("Cleanup complete")

        return warnings

    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")

        if len(items) == 1 and items[0].is_dir():
            sub_dir = items[0]
            self._debug_log(f"Found single subdirectory: {sub_dir.name}")

            sub_items = list(sub_dir.iterdir())
            self._debug_log(f"Subdirectory contains {len(sub_items)} items")

            if sub_items:
                self._debug_log("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

                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}")
        else:
            self._debug_log("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()

        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 cleanup_cache(self) -> None:
        """
        Clean up cached repositories after all skills are processed.
        Call this method after all sync_skill() calls are complete.
        """
        self._debug_log(f"Cleaning up {len(self._repo_cache)} cached repositories...")

        for cache_key, cache_dir in self._repo_cache.items():
            try:
                if cache_dir.exists():
                    self._debug_log(f"Removing cache: {cache_dir}")
                    shutil.rmtree(cache_dir, ignore_errors=True)
            except Exception as e:
                self._debug_log(f"Failed to remove cache {cache_dir}: {e}")

        # Clear cache tracking
        self._repo_cache.clear()
        self._cache_refs.clear()
        self._debug_log("Cache cleanup complete")
