"""Base synchronization logic for git-based dependencies."""

import shutil
import subprocess
import time
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional


# Constants
VIBE_PACKAGE_DIR = ".vibe_package"
GIT_TIMEOUT_CLONE = 60
GIT_TIMEOUT_CHECKOUT = 30
GIT_TIMEOUT_CLEANUP = 10
MAX_RETRIES = 5


class BaseSync:
    """Base class for git-based 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 BaseSync.

        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 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_DIR

        # Create .vibe_package directory explicitly
        self.vibe_package_dir.mkdir(parents=True, exist_ok=True)

        self._debug_log(f"BaseSync 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] [{self.__class__.__name__}] {message}")

    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:
            if path.is_file():
                path.chmod(stat.S_IWRITE)
            elif path.is_dir():
                for item in path.rglob("*"):
                    if item.is_file():
                        try:
                            item.chmod(stat.S_IWRITE)
                        except (OSError, PermissionError):
                            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 = MAX_RETRIES) -> None:
        """
        Remove .git directory with retry mechanism.

        Args:
            git_dir: Path to .git directory
            max_retries: Maximum number of retry attempts
        """
        for attempt in range(max_retries):
            try:
                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:
                    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=GIT_TIMEOUT_CLEANUP,
                        )
                        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}")
                        print("Warning: Failed to remove .git directory, skipping...")
                        return

    def _kill_git_processes(self) -> None:
        """Kill any git processes that might be 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}")
        time.sleep(1)

    def _run_git_gc(self, target_dir: Path) -> None:
        """Run 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}")

    def _run_git_clean(self, target_dir: Path) -> None:
        """Run git clean to release file locks."""
        try:
            subprocess.run(
                ["git", "clean", "-fdx"],
                cwd=target_dir,
                capture_output=True,
                text=True,
                timeout=GIT_TIMEOUT_CLEANUP,
            )
            self._debug_log("Git clean completed")
        except Exception as e:
            self._debug_log(f"Git clean failed: {e}")

    def _remove_directory_with_fallback(self, target_dir: Path) -> Path:
        """
        Remove directory with fallback strategies.

        Args:
            target_dir: Directory to remove

        Returns:
            Path to use (original or new if fallback was used)
        """
        if not target_dir.exists():
            return target_dir

        self._debug_log(f"Removing existing directory: {target_dir}")

        self._run_git_gc(target_dir)
        self._run_git_clean(target_dir)

        try:
            shutil.rmtree(target_dir)
            self._debug_log("Directory removed successfully")
            return target_dir
        except Exception as e:
            self._debug_log(f"Failed to remove directory: {e}")
            # Try to remove only .git directory first
            git_dir = target_dir / ".git"
            if git_dir.exists():
                try:
                    self._remove_git_directory(git_dir)
                    shutil.rmtree(target_dir)
                    self._debug_log("Directory removed after removing .git")
                    return target_dir
                except Exception as e2:
                    self._debug_log(f"Still failed after removing .git: {e2}")
                    # Fallback: move to backup
                    temp_backup = (
                        target_dir.parent
                        / f"{target_dir.name}_backup_{int(time.time())}_{uuid.uuid4().hex[:8]}"
                    )
                    try:
                        shutil.move(str(target_dir), str(temp_backup))
                        self._debug_log("Old directory moved to backup")
                        return target_dir
                    except Exception as e3:
                        self._debug_log(f"Failed to move to backup: {e3}")
                        # Last resort: use new unique directory
                        new_dir = (
                            target_dir.parent
                            / f"{target_dir.name}_{int(time.time())}_{uuid.uuid4().hex[:8]}"
                        )
                        self._debug_log(f"Using new directory: {new_dir}")
                        return new_dir
        return target_dir

    def _clone_repository(self, git_url: str, git_tag: str, target_dir: Path) -> None:
        """
        Clone a git repository and checkout specific tag/branch.

        Args:
            git_url: Git repository URL
            git_tag: Tag or branch to checkout
            target_dir: Target directory for clone
        """
        # Kill any existing git processes
        if target_dir.exists():
            self._kill_git_processes()
            target_dir = self._remove_directory_with_fallback(target_dir)

        # Clone repository
        try:
            result = subprocess.run(
                ["git", "clone", git_url, str(target_dir)],
                check=True,
                capture_output=True,
                text=True,
                timeout=GIT_TIMEOUT_CLONE,
            )
            self._debug_log("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
        except subprocess.TimeoutExpired:
            raise RuntimeError(f"Git clone timed out after {GIT_TIMEOUT_CLONE}s")

        # 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:
            subprocess.run(
                ["git", "checkout", git_tag],
                cwd=target_dir,
                check=True,
                capture_output=True,
                text=True,
                timeout=GIT_TIMEOUT_CHECKOUT,
            )
            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 += "\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 += "\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
        except subprocess.TimeoutExpired:
            raise RuntimeError(f"Git checkout timed out after {GIT_TIMEOUT_CHECKOUT}s")

    def _copy_to_target(self, source_dir: Path, target_dir: Path) -> None:
        """
        Copy directory contents to target directory, excluding .git directory.

        Args:
            source_dir: Source directory
            target_dir: Target directory
        """
        if target_dir.exists():
            shutil.rmtree(target_dir)

        target_dir.parent.mkdir(parents=True, exist_ok=True)
        shutil.copytree(source_dir, target_dir, ignore=shutil.ignore_patterns(".git"))
