"""
Git version management for tracking code modifications
"""

import git
from pathlib import Path
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
from datetime import datetime


@dataclass
class VersionInfo:
    """
    Version information

    Attributes:
        tag_name: Version tag name
        commit_sha: Commit SHA
        message: Commit/tag message
        timestamp: Creation timestamp
        author: Author name
    """

    tag_name: str
    commit_sha: str
    message: str
    timestamp: int
    author: str


class GitManager:
    """
    Git version manager for tracking and managing code versions

    This class provides functionality to create version tags, commit changes,
    rollback to previous versions, and query version history.
    """

    def __init__(self, repo_path: str):
        """
        Initialize Git manager

        Args:
            repo_path: Path to Git repository

        Raises:
            git.InvalidGitRepositoryError: If path is not a valid Git repository
        """
        self.repo_path = Path(repo_path)
        try:
            self.repo = git.Repo(repo_path)
        except git.InvalidGitRepositoryError:
            # Initialize a new repository if it doesn't exist
            self.repo = git.Repo.init(repo_path)

    def create_version_tag(self, tag_name: str, message: str) -> str:
        """
        Create a version tag at current HEAD

        Args:
            tag_name: Name for the version tag (e.g., 'v1.0.0', 'strategy-v2')
            message: Tag message describing the version

        Returns:
            Tag SHA

        Raises:
            git.GitCommandError: If tag creation fails
        """
        # Create annotated tag
        tag = self.repo.create_tag(tag_name, message=message)
        return tag.commit.hexsha

    def commit_changes(self, files: List[str], message: str) -> str:
        """
        Commit changes to specified files

        Args:
            files: List of file paths to commit (relative to repo root)
            message: Commit message

        Returns:
            Commit SHA

        Raises:
            git.GitCommandError: If commit fails
        """
        # Add files to staging area
        self.repo.index.add(files)

        # Commit changes
        commit = self.repo.index.commit(message)

        return commit.hexsha

    def rollback_to_tag(self, tag_name: str) -> bool:
        """
        Rollback repository to a specific version tag

        Args:
            tag_name: Name of the tag to rollback to

        Returns:
            True if rollback successful, False otherwise

        Raises:
            git.GitCommandError: If rollback fails
        """
        try:
            # Get the tag
            tag = self.repo.tags[tag_name]

            # Reset to tag commit
            self.repo.head.reset(tag.commit, index=True, working_tree=True)

            return True
        except (IndexError, git.GitCommandError) as e:
            print(f"Rollback failed: {e}")
            return False

    def rollback_to_commit(self, commit_sha: str) -> bool:
        """
        Rollback repository to a specific commit

        Args:
            commit_sha: SHA of the commit to rollback to

        Returns:
            True if rollback successful, False otherwise
        """
        try:
            # Get the commit
            commit = self.repo.commit(commit_sha)

            # Reset to commit
            self.repo.head.reset(commit, index=True, working_tree=True)

            return True
        except (git.BadName, git.GitCommandError) as e:
            print(f"Rollback failed: {e}")
            return False

    def get_version_history(self, limit: int = 10) -> List[VersionInfo]:
        """
        Get version history from tags

        Args:
            limit: Maximum number of versions to return

        Returns:
            List of version information, sorted by timestamp (newest first)
        """
        versions = []

        for tag in self.repo.tags:
            try:
                commit = tag.commit
                version_info = VersionInfo(
                    tag_name=tag.name,
                    commit_sha=commit.hexsha,
                    message=tag.tag.message if tag.tag else "",
                    timestamp=int(commit.committed_date),
                    author=str(commit.author),
                )
                versions.append(version_info)
            except Exception:
                # Skip tags that can't be processed
                continue

        # Sort by timestamp (newest first)
        versions.sort(key=lambda v: v.timestamp, reverse=True)

        return versions[:limit]

    def get_commit_history(self, limit: int = 10) -> List[Dict[str, Any]]:
        """
        Get commit history

        Args:
            limit: Maximum number of commits to return

        Returns:
            List of commit information dictionaries
        """
        commits = []

        for commit in self.repo.iter_commits(max_count=limit):
            commit_info = {
                "sha": commit.hexsha,
                "short_sha": commit.hexsha[:7],
                "message": commit.message.strip(),
                "author": str(commit.author),
                "timestamp": int(commit.committed_date),
                "date": datetime.fromtimestamp(commit.committed_date).isoformat(),
            }
            commits.append(commit_info)

        return commits

    def get_current_commit(self) -> str:
        """
        Get current commit SHA

        Returns:
            Current commit SHA
        """
        return self.repo.head.commit.hexsha

    def get_current_branch(self) -> str:
        """
        Get current branch name

        Returns:
            Current branch name
        """
        return self.repo.active_branch.name

    def has_uncommitted_changes(self) -> bool:
        """
        Check if there are uncommitted changes

        Returns:
            True if there are uncommitted changes, False otherwise
        """
        return self.repo.is_dirty()

    def get_changed_files(self) -> List[str]:
        """
        Get list of changed files (staged and unstaged)

        Returns:
            List of changed file paths
        """
        changed_files = []

        # Get staged changes
        for item in self.repo.index.diff("HEAD"):
            changed_files.append(item.a_path)

        # Get unstaged changes
        for item in self.repo.index.diff(None):
            changed_files.append(item.a_path)

        # Get untracked files
        changed_files.extend(self.repo.untracked_files)

        return list(set(changed_files))

    def create_branch(self, branch_name: str) -> bool:
        """
        Create a new branch

        Args:
            branch_name: Name for the new branch

        Returns:
            True if branch created successfully, False otherwise
        """
        try:
            self.repo.create_head(branch_name)
            return True
        except git.GitCommandError as e:
            print(f"Branch creation failed: {e}")
            return False

    def checkout_branch(self, branch_name: str) -> bool:
        """
        Checkout a branch

        Args:
            branch_name: Name of the branch to checkout

        Returns:
            True if checkout successful, False otherwise
        """
        try:
            self.repo.heads[branch_name].checkout()
            return True
        except (IndexError, git.GitCommandError) as e:
            print(f"Checkout failed: {e}")
            return False

    def get_file_at_commit(self, file_path: str, commit_sha: str) -> Optional[str]:
        """
        Get file content at a specific commit

        Args:
            file_path: Path to file (relative to repo root)
            commit_sha: Commit SHA

        Returns:
            File content as string, or None if file doesn't exist at that commit
        """
        try:
            commit = self.repo.commit(commit_sha)
            blob = commit.tree / file_path
            return blob.data_stream.read().decode("utf-8")
        except (KeyError, git.BadName):
            return None

    def diff_commits(self, commit_sha1: str, commit_sha2: str) -> str:
        """
        Get diff between two commits

        Args:
            commit_sha1: First commit SHA
            commit_sha2: Second commit SHA

        Returns:
            Diff as string
        """
        try:
            commit1 = self.repo.commit(commit_sha1)
            commit2 = self.repo.commit(commit_sha2)
            diff = commit1.diff(commit2, create_patch=True)
            return "\n".join([str(d) for d in diff])
        except git.BadName:
            return ""
