"""
Git Initializer - Handles git operations and GitHub integration

Provides functionality to:
- Initialize local git repositories
- Create GitHub repositories via API
- Push code to GitHub
- Manage git configuration and commits
- Handle authentication and errors
"""

import logging
import subprocess
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

from socratic_system.config.constants import (
    GIT_OPERATION_TIMEOUT_SECONDS,
    GIT_VERSION_CHECK_TIMEOUT_SECONDS,
)

logger = logging.getLogger("socrates.utils.git_initializer")


class GitInitializationError(Exception):
    """Raised when git initialization fails"""

    pass


class GitOperationError(Exception):
    """Raised when git operation fails"""

    pass


class GitHubError(Exception):
    """Raised when GitHub API operation fails"""

    pass

class GitConfigError(Exception):
    """Raised when git configuration fails"""

    pass


class RepositoryInitializationError(Exception):
    """Raised when repository initialization fails"""

    pass


class CommitError(Exception):
    """Raised when commit operation fails"""

    pass


class RepositoryRemoteError(Exception):
    """Raised when remote repository operations fail"""

    pass



class GitInitializer:
    """Initialize git repositories and manage GitHub integration"""

    @staticmethod
    def is_git_installed() -> bool:
        """Check if git is installed on the system."""
        try:
            subprocess.run(
                ["git", "--version"],
                capture_output=True,
                check=True,
                timeout=GIT_VERSION_CHECK_TIMEOUT_SECONDS,
            )
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            return False

    @staticmethod
    def initialize_repository(
        project_root: Path,
        initial_commit_message: Optional[str] = None,
        author_name: str = "Socrates AI",
        author_email: str = "noreply@socrates.ai",
    ) -> Tuple[bool, str]:
        """
        Initialize git repository in project directory.

        Args:
            project_root: Path to project directory
            initial_commit_message: Message for initial commit
            author_name: Git author name
            author_email: Git author email

        Returns:
            Tuple of (success: bool, message: str)
        """
        if not isinstance(project_root, Path):
            project_root = Path(project_root)

        if not project_root.is_dir():
            msg = f"Project directory does not exist: {project_root}"
            logger.error(msg)
            return False, msg

        try:
            # Check if git is installed
            if not GitInitializer.is_git_installed():
                msg = "Git is not installed on this system"
                logger.error(msg)
                return False, msg

            # Check if already a git repo
            git_dir = project_root / ".git"
            if git_dir.exists():
                logger.warning(f"Repository already initialized: {project_root}")
                return True, "Repository already initialized"

            logger.info(f"Initializing git repository: {project_root}")

            # Initialize repository
            subprocess.run(
                ["git", "init"],
                cwd=project_root,
                capture_output=True,
                check=True,
                timeout=GIT_OPERATION_TIMEOUT_SECONDS,
            )
            logger.info("Git repository initialized")

            # Configure git user (local to this repo)
            subprocess.run(
                ["git", "config", "user.name", author_name],
                cwd=project_root,
                capture_output=True,
                check=True,
                timeout=GIT_OPERATION_TIMEOUT_SECONDS,
            )
            logger.info(f"Git user configured: {author_name}")

            subprocess.run(
                ["git", "config", "user.email", author_email],
                cwd=project_root,
                capture_output=True,
                check=True,
                timeout=GIT_OPERATION_TIMEOUT_SECONDS,
            )
            logger.info(f"Git email configured: {author_email}")

            # Stage all files
            subprocess.run(
                ["git", "add", "."],
                cwd=project_root,
                capture_output=True,
                check=True,
                timeout=GIT_OPERATION_TIMEOUT_SECONDS,
            )
            logger.info("All files staged")

            # Create initial commit
            if initial_commit_message is None:
                initial_commit_message = (
                    f"Initial commit: Generated by Socrates AI on {datetime.now().isoformat()}"
                )

            subprocess.run(
                ["git", "commit", "-m", initial_commit_message],
                cwd=project_root,
                capture_output=True,
                check=True,
                timeout=GIT_OPERATION_TIMEOUT_SECONDS,
            )
            logger.info(f"Initial commit created: {initial_commit_message}")

            return True, "Git repository initialized successfully"

        except subprocess.TimeoutExpired:
            msg = f"Git command timed out after {GIT_OPERATION_TIMEOUT_SECONDS} seconds"
            logger.error(msg)
            return False, msg
        except subprocess.CalledProcessError as e:
            msg = f"Git command failed: {e.stderr.decode('utf-8') if e.stderr else str(e)}"
            logger.error(msg)
            return False, msg
        except Exception as e:
            msg = f"Error initializing git repository: {str(e)}"
            logger.error(msg)
            return False, msg

    @staticmethod
    def create_github_repository(
        repo_name: str,
        description: str,
        private: bool,
        github_token: str,
    ) -> Tuple[bool, Dict[str, Any]]:
        """
        Create GitHub repository via GitHub API.

        Args:
            repo_name: Name of repository to create
            description: Repository description
            private: Whether repository should be private
            github_token: GitHub Personal Access Token

        Returns:
            Tuple of (success: bool, repo_data: dict)
        """
        import requests

        try:
            logger.info(f"Creating GitHub repository: {repo_name}")

            headers = {
                "Authorization": f"token {github_token}",
                "Accept": "application/vnd.github.v3+json",
                "User-Agent": "Socrates-AI",
            }

            payload = {
                "name": repo_name,
                "description": description,
                "private": private,
                "auto_init": False,
            }

            response = requests.post(
                "https://api.github.com/user/repos",
                headers=headers,
                json=payload,
                timeout=GIT_OPERATION_TIMEOUT_SECONDS,
            )

            if response.status_code == 201:
                repo_data = response.json()
                logger.info(f"GitHub repository created: {repo_data['html_url']}")
                return True, repo_data
            elif response.status_code == 401:
                msg = "GitHub authentication failed: Invalid or expired token"
                logger.error(msg)
                return False, {"error": msg}
            elif response.status_code == 422:
                msg = "Repository already exists or invalid name"
                logger.error(msg)
                return False, {"error": msg}
            else:
                msg = f"GitHub API error: {response.status_code} - {response.text}"
                logger.error(msg)
                return False, {"error": msg}

        except requests.RequestException as e:
            msg = f"Network error communicating with GitHub: {str(e)}"
            logger.error(msg)
            return False, {"error": msg}
        except Exception as e:
            msg = f"Error creating GitHub repository: {str(e)}"
            logger.error(msg)
            return False, {"error": msg}

    @staticmethod
    def push_to_github(
        project_root: Path,
        remote_url: str,
        branch: str = "main",
        force: bool = False,
    ) -> Tuple[bool, str]:
        """
        Push local git repository to GitHub.

        Args:
            project_root: Path to project directory
            remote_url: GitHub repository URL (HTTPS or SSH)
            branch: Branch name to push (default: main)
            force: Whether to force push (use with caution)

        Returns:
            Tuple of (success: bool, message: str)
        """
        if not isinstance(project_root, Path):
            project_root = Path(project_root)

        try:
            logger.info(f"Pushing to GitHub: {remote_url}")

            # Check if git repo exists
            git_dir = project_root / ".git"
            if not git_dir.exists():
                msg = f"Not a git repository: {project_root}"
                logger.error(msg)
                return False, msg

            # Add remote
            try:
                subprocess.run(
                    ["git", "remote", "add", "origin", remote_url],
                    cwd=project_root,
                    capture_output=True,
                    check=False,
                    timeout=GIT_OPERATION_TIMEOUT_SECONDS,
                )
            except subprocess.CalledProcessError:
                # Remote may already exist, try to update it
                subprocess.run(
                    ["git", "remote", "set-url", "origin", remote_url],
                    cwd=project_root,
                    capture_output=True,
                    check=True,
                    timeout=GIT_OPERATION_TIMEOUT_SECONDS,
                )

            logger.info(f"Remote 'origin' configured: {remote_url}")

            # Create or update branch
            try:
                # Get current branch
                result = subprocess.run(
                    ["git", "rev-parse", "--abbrev-ref", "HEAD"],
                    cwd=project_root,
                    capture_output=True,
                    check=True,
                    timeout=GIT_OPERATION_TIMEOUT_SECONDS,
                    text=True,
                )
                current_branch = result.stdout.strip()

                # If on different branch, rename it
                if current_branch != branch:
                    subprocess.run(
                        ["git", "branch", "-M", branch],
                        cwd=project_root,
                        capture_output=True,
                        check=True,
                        timeout=GIT_OPERATION_TIMEOUT_SECONDS,
                    )
                    logger.info(f"Branch renamed to: {branch}")
            except subprocess.CalledProcessError:
                # Branch doesn't exist, create it
                subprocess.run(
                    ["git", "checkout", "-b", branch],
                    cwd=project_root,
                    capture_output=True,
                    check=False,
                    timeout=GIT_OPERATION_TIMEOUT_SECONDS,
                )

            # Push to remote
            push_args = ["git", "push", "-u", "origin", branch]
            if force:
                push_args.append("-f")
                logger.warning("Force pushing to GitHub (--force)")

            result = subprocess.run(
                push_args,
                cwd=project_root,
                capture_output=True,
                check=False,
                timeout=30,
                text=True,
            )

            if result.returncode == 0:
                msg = f"Successfully pushed to {remote_url} (branch: {branch})"
                logger.info(msg)
                return True, msg
            else:
                error_msg = result.stderr or result.stdout
                # Check if it's an authentication error
                if "401" in error_msg or "authentication" in error_msg.lower():
                    msg = "GitHub authentication failed. Check your token or SSH key."
                elif "already exists" in error_msg.lower():
                    msg = "Branch already exists on remote. Use force push if needed."
                else:
                    msg = f"Push failed: {error_msg}"
                logger.error(msg)
                return False, msg

        except subprocess.TimeoutExpired:
            msg = f"Git command timed out after {GIT_OPERATION_TIMEOUT_SECONDS} seconds"
            logger.error(msg)
            return False, msg
        except subprocess.CalledProcessError as e:
            msg = f"Git command failed: {e.stderr.decode('utf-8') if e.stderr else str(e)}"
            logger.error(msg)
            return False, msg
        except Exception as e:
            msg = f"Error pushing to GitHub: {str(e)}"
            logger.error(msg)
            return False, msg

    @staticmethod
    def get_repository_status(project_root: Path) -> Dict[str, Any]:
        """
        Get current git repository status.

        Args:
            project_root: Path to project directory

        Returns:
            Dictionary with repository status information
        """
        if not isinstance(project_root, Path):
            project_root = Path(project_root)

        status: Dict[str, Any] = {
            "is_git_repo": False,
            "branch": None,
            "remotes": [],
            "uncommitted_changes": False,
            "untracked_files": False,
        }

        try:
            git_dir = project_root / ".git"
            if not git_dir.exists():
                return status

            status["is_git_repo"] = True

            # Get current branch
            try:
                result = subprocess.run(
                    ["git", "rev-parse", "--abbrev-ref", "HEAD"],
                    cwd=project_root,
                    capture_output=True,
                    check=True,
                    timeout=GIT_VERSION_CHECK_TIMEOUT_SECONDS,
                    text=True,
                )
                status["branch"] = result.stdout.strip()
            except subprocess.CalledProcessError:
                pass

            # Get remotes
            try:
                result = subprocess.run(
                    ["git", "remote", "-v"],
                    cwd=project_root,
                    capture_output=True,
                    check=True,
                    timeout=GIT_VERSION_CHECK_TIMEOUT_SECONDS,
                    text=True,
                )
                remote_lines = [
                    line.split()[0] for line in result.stdout.strip().split("\n") if line
                ]
                status["remotes"] = list(set(remote_lines))  # Remove duplicates
            except subprocess.CalledProcessError:
                pass

            # Check for uncommitted changes
            try:
                result = subprocess.run(
                    ["git", "status", "--short"],
                    cwd=project_root,
                    capture_output=True,
                    check=True,
                    timeout=GIT_VERSION_CHECK_TIMEOUT_SECONDS,
                    text=True,
                )
                changes = result.stdout.strip()
                if changes:
                    status["uncommitted_changes"] = True
                    # Check for untracked files (lines starting with ??)
                    untracked = [line for line in changes.split("\n") if line.startswith("??")]
                    status["untracked_files"] = len(untracked) > 0
            except subprocess.CalledProcessError:
                pass

            return status

        except Exception as e:
            logger.error(f"Error getting repository status: {str(e)}")
            return status

    @staticmethod
    def get_github_user_info(github_token: str) -> Tuple[bool, Dict[str, Any]]:
        """
        Get authenticated GitHub user information.

        Args:
            github_token: GitHub Personal Access Token

        Returns:
            Tuple of (success: bool, user_data: dict)
        """
        import requests

        try:
            headers = {
                "Authorization": f"token {github_token}",
                "Accept": "application/vnd.github.v3+json",
                "User-Agent": "Socrates-AI",
            }

            response = requests.get(
                "https://api.github.com/user",
                headers=headers,
                timeout=GIT_OPERATION_TIMEOUT_SECONDS,
            )

            if response.status_code == 200:
                user_data = response.json()
                logger.info(f"GitHub user authenticated: {user_data.get('login')}")
                return True, user_data
            elif response.status_code == 401:
                msg = "GitHub authentication failed: Invalid or expired token"
                logger.error(msg)
                return False, {"error": msg}
            else:
                msg = f"GitHub API error: {response.status_code}"
                logger.error(msg)
                return False, {"error": msg}

        except requests.RequestException as e:
            msg = f"Network error: {str(e)}"
            logger.error(msg)
            return False, {"error": msg}
        except Exception as e:
            msg = f"Error getting user info: {str(e)}"
            logger.error(msg)
            return False, {"error": msg}
