"""
Git operations manager for cloning and updating repositories.
"""
import os
import logging
import shutil
from pathlib import Path
from typing import Optional, Tuple
from git import Repo, GitCommandError
from git.exc import InvalidGitRepositoryError

logger = logging.getLogger(__name__)


class GitManager:
    """Manages Git operations for repository cloning and updates."""
    
    def __init__(self, repos_dir: str = "./repos"):
        """
        Initialize Git manager.
        
        Args:
            repos_dir: Directory where repositories will be cloned
        """
        self.repos_dir = Path(repos_dir)
        self.repos_dir.mkdir(parents=True, exist_ok=True)
        self._configure_ssh_for_bitbucket()
    
    def _configure_ssh_for_bitbucket(self):
        """
        Configure SSH to handle Bitbucket host key verification.
        Sets GIT_SSH_COMMAND environment variable to accept Bitbucket's host key.
        """
        import platform
        
        # Ensure .ssh directory exists
        ssh_dir = os.path.expanduser("~/.ssh")
        os.makedirs(ssh_dir, exist_ok=True)
        
        # Get known_hosts path
        known_hosts_path = os.path.expanduser("~/.ssh/known_hosts")
        
        # Configure SSH to automatically accept Bitbucket's host key
        # Use 'accept-new' (SSH 7.6+) - adds host key if not present, fails if key changed
        # For older SSH versions, this will fail gracefully and we can try 'no'
        if platform.system() == 'Windows':
            # On Windows, escape the path properly
            ssh_command = f'ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="{known_hosts_path}"'
        else:
            # Linux/Mac
            ssh_command = f'ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile={known_hosts_path}'
        
        # Set GIT_SSH_COMMAND environment variable for this process
        # GitPython will use this when executing git commands
        os.environ['GIT_SSH_COMMAND'] = ssh_command
        logger.info(f"Configured SSH for Bitbucket (known_hosts: {known_hosts_path})")
    
    def _is_repo_empty_or_invalid(self, repo_path: Path) -> bool:
        """
        Check if repository directory is empty or contains an invalid git repository.
        
        Args:
            repo_path: Path to repository directory
            
        Returns:
            True if directory is empty or contains invalid git repository
        """
        if not repo_path.exists():
            return True
        
        # Check if .git exists and is valid first
        git_dir = repo_path / ".git"
        if git_dir.exists():
            # Try to validate git repository
            try:
                repo = Repo(repo_path)
                # Check if repository is valid by trying to access remotes
                _ = repo.remotes
                return False  # Valid repository
            except (InvalidGitRepositoryError, GitCommandError, Exception) as e:
                logger.warning(f"Repository at {repo_path} appears to be invalid/corrupted: {e}")
                return True
        
        # No .git directory - check if directory is empty
        try:
            # Check if directory has any files/subdirectories
            items = list(repo_path.iterdir())
            if not items:
                # Directory exists but is completely empty
                logger.info(f"Repository directory {repo_path} is empty")
                return True
            else:
                # Directory has files but no .git - not a valid git repository
                logger.info(f"Repository directory {repo_path} exists but has no .git directory")
                return True
        except Exception as e:
            logger.warning(f"Error checking directory contents for {repo_path}: {e}")
            return True
    
    def clone_or_update(self, repo_slug: str, clone_url: str, username: str = None, password: str = None) -> Tuple[Optional[Path], bool]:
        """
        Clone repository if it doesn't exist, or update if it does.
        Uses SSH for cloning (SSH key must be set up).
        
        Args:
            repo_slug: Repository slug/name
            clone_url: Repository clone URL (SSH format: git@bitbucket.org:workspace/repo.git)
            username: Optional - not used for SSH (kept for backward compatibility)
            password: Optional - not used for SSH (kept for backward compatibility)
            
        Returns:
            Tuple of (Path to repository directory, is_first_clone), or (None, False) if failed
        """
        repo_path = self.repos_dir / repo_slug
        
        # Use SSH URL directly - no authentication needed (uses SSH keys)
        # SSH URL format: git@bitbucket.org:workspace/repo.git
        authenticated_url = clone_url
        
        # Log the URL (masked for security)
        if authenticated_url:
            # SSH URLs are safe to log (no passwords), but mask the full path for brevity
            # Format: git@bitbucket.org:workspace/repo.git -> git@bitbucket.org:*****
            if "@" in authenticated_url and ":" in authenticated_url:
                parts = authenticated_url.split(":", 1)
                if len(parts) == 2:
                    masked_url = parts[0] + ":*****"
                else:
                    masked_url = authenticated_url
            else:
                masked_url = authenticated_url
            logger.info(f"Using SSH clone URL: {masked_url}")
        else:
            logger.error(f"No clone URL provided for {repo_slug}")
            return None, False
        
        is_first_clone = False
        try:
            # Check if repository is empty or invalid - if so, remove and clone fresh
            if self._is_repo_empty_or_invalid(repo_path):
                if repo_path.exists():
                    logger.info(f"Repository directory for {repo_slug} is empty or invalid, removing and cloning fresh")
                    try:
                        shutil.rmtree(repo_path)
                        logger.info(f"Removed invalid/empty repository directory: {repo_slug}")
                    except Exception as e:
                        logger.error(f"Failed to remove invalid repository directory {repo_slug}: {e}")
                        return None, False
                
                # Clone new repository
                is_first_clone = True
                logger.info(f"Cloning repository: {repo_slug}")
                repo = Repo.clone_from(authenticated_url, repo_path)
                logger.info(f"Successfully cloned repository: {repo_slug}")
            elif repo_path.exists() and (repo_path / ".git").exists():
                # Repository exists and is valid, update it
                logger.info(f"Updating repository: {repo_slug}")
                repo = Repo(repo_path)
                
                # Check if origin remote exists and is configured
                try:
                    origin = repo.remote(name="origin")
                    
                    # Fix refspec if not set
                    if not origin.refs:
                        logger.debug(f"Configuring refspec for {repo_slug}")
                        repo.git.config("--add", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")
                        origin = repo.remote(name="origin")  # Re-get after config
                    
                    # Fetch latest changes
                    origin.fetch()
                    
                    # Get current branch and pull
                    try:
                        current_branch = repo.active_branch.name
                        repo.git.pull()
                        logger.info(f"Successfully updated repository: {repo_slug} (branch: {current_branch})")
                    except Exception as e:
                        logger.warning(f"Could not pull {repo_slug}: {e}. Repository may be in detached HEAD state.")
                        # Try to fetch anyway
                        origin.fetch()
                        logger.info(f"Fetched changes for {repo_slug} (could not pull)")
                except ValueError:
                    # Remote doesn't exist, add it
                    logger.info(f"Adding origin remote for {repo_slug}")
                    origin = repo.create_remote("origin", authenticated_url)
                    origin.fetch()
                    logger.info(f"Successfully added remote and fetched for {repo_slug}")
            
            # Return repo path and whether this was a first clone
            return repo_path, is_first_clone
        except GitCommandError as e:
            error_msg = str(e)
            logger.error(f"Git operation failed for {repo_slug}: {e}")
            
            # Check for authentication/SSH errors
            if "Permission denied" in error_msg or "Host key verification failed" in error_msg:
                logger.error(f"SSH authentication failed for {repo_slug}. Please verify:")
                logger.error(f"  - SSH key is set up and added to Bitbucket")
                logger.error(f"  - SSH key is in SSH agent or configured in ~/.ssh/config")
                logger.error(f"  - Repository exists and you have access")
            elif "Authentication failed" in error_msg or "fatal: unable to access" in error_msg:
                logger.error(f"Authentication failed for {repo_slug}. Please verify:")
                logger.error(f"  - SSH key is properly configured")
                logger.error(f"  - Repository exists and you have access")
            
            return None, False
        except Exception as e:
            logger.error(f"Unexpected error handling repository {repo_slug}: {e}")
            return None, False
    
    def has_sonar_properties(self, repo_path: Path) -> bool:
        """
        Check if repository has sonar-project.properties file.
        
        Args:
            repo_path: Path to repository directory
            
        Returns:
            True if sonar-project.properties exists
        """
        sonar_props = repo_path / "sonar-project.properties"
        exists = sonar_props.exists()
        
        if not exists:
            logger.warning(f"sonar-project.properties not found in {repo_path}")
        
        return exists
    
    def get_sonar_properties_path(self, repo_path: Path) -> Optional[Path]:
        """
        Get path to sonar-project.properties file.
        
        Args:
            repo_path: Path to repository directory
            
        Returns:
            Path to sonar-project.properties, or None if not found
        """
        sonar_props = repo_path / "sonar-project.properties"
        if sonar_props.exists():
            return sonar_props
        return None
    
    def cleanup_repo(self, repo_slug: str) -> bool:
        """
        Remove a cloned repository (optional cleanup).
        
        Args:
            repo_slug: Repository slug/name
            
        Returns:
            True if successfully removed
        """
        repo_path = self.repos_dir / repo_slug
        try:
            if repo_path.exists():
                shutil.rmtree(repo_path)
                logger.info(f"Cleaned up repository: {repo_slug}")
                return True
        except Exception as e:
            logger.error(f"Failed to cleanup repository {repo_slug}: {e}")
        return False

