"""Git tool utilities for common Git operations.

Provides convenient classes and functions for common Git workflow operations
including repository initialization, file staging, cleaning, and pushing.

Classes:
    GitInitCommand: Initialize a new Git repository with initial commit
    GitAddCommand: Stage and commit changed files
    GitCleanCommand: Clean untracked files and reset working directory
    GitPushCommand: Push commits to remote repository

Functions:
    check_git_status: Check for uncommitted changes in repository
"""

from __future__ import annotations

import argparse
import logging
import os
import platform
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from functools import cached_property
from pathlib import Path
from typing import ClassVar

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
is_windows = platform.system() == "Windows"


@dataclass
class ParallelConfig:
    """Configuration for parallel execution.

    Attributes
    ----------
        max_workers: Maximum number of concurrent workers
        batch_size: Size of file batches for processing
        show_progress: Whether to show progress information
    """

    max_workers: int = 4
    batch_size: int = 10
    show_progress: bool = True

    @classmethod
    def auto(cls, cpu_count: int | None = None) -> ParallelConfig:
        """Create configuration based on CPU count.

        Args:
            cpu_count: Number of CPU cores, defaults to os.cpu_count()

        Returns
        -------
            ParallelConfig with optimal settings
        """
        if cpu_count is None:
            cpu_count = os.cpu_count() or 1

        # For I/O intensive Git operations, we can use more workers
        max_workers = min(8, max(1, cpu_count * 2))
        batch_size = min(20, max(5, cpu_count * 2))

        return cls(max_workers=max_workers, batch_size=batch_size)


def run_parallel_commands(
    commands: list[list[str]],
    config: ParallelConfig | None = None,
    description: str = "Executing commands",
) -> list[tuple[list[str], bool, str]]:
    """Execute multiple commands in parallel.

    Args:
        commands: List of command arguments to execute
        config: Parallel execution configuration
        description: Description for progress reporting

    Returns
    -------
        List of tuples containing (command, success, output/error message)
    """
    if config is None:
        config = ParallelConfig.auto()

    if len(commands) <= 1:
        # Use sequential execution for single command
        return run_sequential_commands(commands)

    results: list[tuple[list[str], bool, str]] = []
    total_commands = len(commands)

    if config.show_progress:
        logger.info(
            f"{description}: {total_commands} commands with {config.max_workers} workers",
        )

    with ThreadPoolExecutor(max_workers=config.max_workers) as executor:
        # Submit all commands
        future_to_cmd = {executor.submit(_execute_single_command, cmd): cmd for cmd in commands}

        # Process completed commands
        completed = 0
        for future in as_completed(future_to_cmd):
            cmd = future_to_cmd[future]
            try:
                success, message = future.result()
                results.append((cmd, success, message))
                completed += 1

                if config.show_progress:
                    status = "✓" if success else "✗"
                    logger.info(
                        f"[{completed}/{total_commands}] {status} {' '.join(cmd)}",
                    )

            except Exception as e:
                results.append((cmd, False, f"Execution error: {e}"))
                completed += 1
                if config.show_progress:
                    logger.exception(
                        f"[{completed}/{total_commands}] ✗ {' '.join(cmd)} - {e}",
                    )

    if config.show_progress:
        successful = sum(1 for _, success, _ in results if success)
        logger.info(f"Completed: {successful}/{total_commands} successful")

    return results


def run_sequential_commands(
    commands: list[list[str]],
) -> list[tuple[list[str], bool, str]]:
    """Execute commands sequentially (fallback for single commands).

    Args:
        commands: List of command arguments to execute

    Returns
    -------
        List of tuples containing (command, success, output/error message)
    """
    results: list[tuple[list[str], bool, str]] = []

    for cmd in commands:
        success, message = _execute_single_command(cmd)
        results.append((cmd, success, message))
        status = "✓" if success else "✗"
        logger.info(f"{status} {' '.join(cmd)}")

    return results


def _execute_single_command(command: list[str]) -> tuple[bool, str]:
    """Execute a single command and return result.

    Args:
        command: Command arguments to execute

    Returns
    -------
        Tuple of (success, output/error message)
    """
    try:
        result = subprocess.run(
            command,
            capture_output=True,
            text=True,
            check=True,
        )
        return True, result.stdout.strip() or "Command executed successfully"
    except subprocess.CalledProcessError as e:
        error_msg = e.stderr.strip() if e.stderr else str(e)
        return False, f"Command failed: {error_msg}"
    except Exception as e:
        return False, f"Execution error: {e}"


def check_git_status() -> bool:
    """Check if there are uncommitted changes.

    Returns
    -------
        bool: Whether there are uncommitted changes
    """
    try:
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            capture_output=True,
            text=True,
            check=False,
        )
    except subprocess.CalledProcessError as e:
        logger.exception(f"Git status failed: {e}")
        return False

    if result.stdout.strip():
        logger.error(
            f"Uncommitted changes exist, please commit changes first: {result.stdout}",
        )
        return False
    return True


@dataclass
class GitInitCommand:
    """Git init command with parallel execution support."""

    SUBCOMMANDS: ClassVar[list[list[str]]] = [
        ["git", "init"],
        ["git", "add", "."],
        ["git", "commit", "-m", "initial commit"],
    ]

    def run(self, parallel: bool = False, max_workers: int = 3) -> None:
        """Run git init command.

        Args:
            parallel: Enable parallel execution for better performance
            max_workers: Maximum number of concurrent workers when parallel=True
        """
        if parallel:
            self._run_parallel(max_workers=max_workers)
        else:
            self._run_sequential()

    def _run_sequential(self) -> None:
        """Run git init command sequentially (traditional approach)."""
        for subcommand in self.SUBCOMMANDS:
            try:
                subprocess.run(subcommand, check=True)
                logger.info(f"Executed: {' '.join(subcommand)}")
            except subprocess.CalledProcessError as e:
                logger.exception(f"Git init failed: {e}")
                return
        logger.info("Git initialization completed successfully")

    def _run_parallel(self, max_workers: int = 3) -> None:
        """Run git init command with partial parallelization.

        Note: Some commands must run sequentially due to dependencies
        (git init -> git add -> git commit)
        """
        try:
            # First, run git init sequentially (required for repo setup)
            init_result = _execute_single_command(["git", "init"])
            if not init_result[0]:
                logger.error(f"Git init failed: {init_result[1]}")
                return
            logger.info("✓ git init")

            # Then run add and commit in parallel
            parallel_commands = [
                ["git", "add", "."],
                ["git", "commit", "-m", "initial commit"],
            ]

            config = ParallelConfig(max_workers=min(max_workers, 2), show_progress=True)
            results = run_parallel_commands(
                parallel_commands,
                config=config,
                description="Git init parallel operations",
            )

            # Check if all parallel operations succeeded
            all_success = all(success for _, success, _ in results)
            if all_success:
                logger.info(
                    "Git initialization completed successfully with parallel execution",
                )
            else:
                failed_commands = [cmd for cmd, success, _ in results if not success]
                logger.error(
                    f"Some parallel init operations failed: {[str(cmd) for cmd in failed_commands]}",
                )
                return

        except Exception as e:
            logger.exception(f"Parallel git init failed: {e}")
            return


@dataclass(frozen=True)
class GitAddCommand:
    """Git add command data class."""

    def run(self) -> None:
        """Run git add command."""
        change_files_status = {
            "Added": self.added_filenames,
            "Modified": self.modified_filenames,
        }

        for change_type, files in change_files_status.items():
            if files:
                file_str = ", ".join(files)
                logger.info(f"{change_type} files: {file_str}")
                try:
                    subprocess.run(
                        ["git", "commit", "-m", f"{change_type} files: {file_str}"],
                        check=True,
                    )
                except subprocess.CalledProcessError as e:
                    logger.exception(f"Git commit failed: {e}")
                    return
            else:
                logger.warning(f"No {change_type} files")

    @cached_property
    def modified_filenames(self) -> set[str]:
        """Get list of modified files from git status."""
        return {f.filepath.stem for f in self.added_filestatus if f.status == "M"}

    @cached_property
    def added_filenames(self) -> set[str]:
        """Get list of added files from git status."""
        return {f.filepath.stem for f in self.added_filestatus if f.status == "A"}

    @cached_property
    def added_filestatus(self) -> set[GitAddFileStatus]:
        """Get list of added files from git status."""
        before = self.changed_files_info

        try:
            subprocess.run(["git", "add", "."], check=True)
        except subprocess.CalledProcessError as e:
            logger.exception(f"Git add failed: {e}")
            return set()

        after = self.changed_files_info
        return {f for f in after - before if f.status == "A"}

    @property
    def changed_files_info(self) -> set[GitAddFileStatus]:
        """Get list of changed files from git status."""
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            capture_output=True,
            text=True,
            check=False,
        )
        files: set[GitAddFileStatus] = set()
        if result.returncode == 0:
            for line in result.stdout.splitlines():
                if line.strip():
                    status = line[:2].strip()
                    filename = line[3:].strip()
                    files.add(GitAddFileStatus(status, Path(filename)))
        return files


@dataclass
class GitAddFileStatus:
    """Git file status data class.

    Properties:
        status: File status, A: Added, M: Modified
        filepath: File path
    """

    status: str
    filepath: Path

    def __hash__(self) -> int:
        """Calculate hash value for unique identification in sets.

        Returns
        -------
            int: Hash value
        """
        return hash((self.status, str(self.filepath)))


@dataclass
class GitCleanCommand:
    """Git clean command with parallel execution support."""

    EXCLUDE_DIRS: ClassVar[list[str]] = [
        ".venv",
        "node_modules",
        ".git",
        ".idea",
        ".vscode",
    ]

    def run(
        self,
        force: bool = False,
        parallel: bool = False,
        max_workers: int = 4,
    ) -> None:
        """Run git clean command.

        Args:
            force: Force clean without confirmation
            parallel: Enable parallel execution for better performance
            max_workers: Maximum number of concurrent workers when parallel=True
        """
        if parallel:
            self._run_parallel(force=force, max_workers=max_workers)
        else:
            self._run_sequential(force=force)

    def _run_sequential(self, force: bool = False) -> None:
        """Run clean command sequentially (traditional approach)."""
        try:
            cmd = ["git", "clean", "-xd"]
            for exclude_dir in self.EXCLUDE_DIRS:
                cmd.extend(["-e", exclude_dir])

            if force:
                cmd.append("-f")

            subprocess.run(cmd, check=True)
            subprocess.run(["git", "checkout", "."], check=True)
            logger.info("Clean completed successfully")
        except subprocess.CalledProcessError as e:
            logger.exception(f"Git clean failed: {e}")
            return

    def _run_parallel(self, force: bool = False, max_workers: int = 4) -> None:
        """Run clean command with parallel execution for better performance."""
        try:
            # Prepare commands for parallel execution
            commands = []

            # Main clean command
            clean_cmd = ["git", "clean", "-xd"]
            for exclude_dir in self.EXCLUDE_DIRS:
                clean_cmd.extend(["-e", exclude_dir])
            if force:
                clean_cmd.append("-f")
            commands.append(clean_cmd)

            # Checkout command
            commands.append(["git", "checkout", "."])

            # Execute commands in parallel
            config = ParallelConfig(max_workers=max_workers, show_progress=True)
            results = run_parallel_commands(
                commands,
                config=config,
                description="Git clean operations",
            )

            # Check results
            all_success = all(success for _, success, _ in results)
            if all_success:
                logger.info("Parallel clean completed successfully")
            else:
                failed_commands = [cmd for cmd, success, _ in results if not success]
                logger.error(
                    f"Some parallel clean operations failed: {[str(cmd) for cmd in failed_commands]}",
                )
                return

        except Exception as e:
            logger.exception(f"Parallel git clean failed: {e}")
            return


@dataclass
class GitPushCommand:
    """Git push command."""

    def run(self, all_branches: bool = False) -> None:
        """Run git push command.

        Args:
            all_branches: Push all branches
        """
        try:
            if all_branches:
                subprocess.run(["git", "push", "--all"], check=True)
                logger.info("Push all branches completed successfully")
            else:
                subprocess.run(["git", "push"], check=True)
                logger.info("Push completed successfully")
        except subprocess.CalledProcessError as e:
            logger.exception(f"Git push failed: {e}")
            return


@dataclass
class GitPullCommand:
    """Git pull command."""

    def run(self, rebase: bool = False) -> None:
        """Run git pull command.

        Args:
            rebase: Use rebase when pulling
        """
        try:
            if rebase:
                subprocess.run(["git", "pull", "--rebase"], check=True)
                logger.info("Pull with rebase completed successfully")
            else:
                subprocess.run(["git", "pull"], check=True)
                logger.info("Pull completed successfully")
        except subprocess.CalledProcessError as e:
            logger.exception(f"Git pull failed: {e}")
            return


@dataclass
class GitCheckoutCommand:
    """Git checkout command."""

    def run(self, branch: str, create: bool = False) -> None:
        """Run git checkout command.

        Args:
            branch: Branch name to checkout
            create: Create new branch if True
        """
        try:
            if create:
                subprocess.run(["git", "checkout", "-b", branch], check=True)
                logger.info(f"Created and checked out branch: {branch}")
            else:
                subprocess.run(["git", "checkout", branch], check=True)
                logger.info(f"Checked out branch: {branch}")
        except subprocess.CalledProcessError as e:
            logger.exception(f"Git checkout failed: {e}")
            return


@dataclass
class GitBranchCommand:
    """Git branch command."""

    def run(self, list_all: bool = False, delete: str | None = None) -> None:
        """Run git branch command.

        Args:
            list_all: List all branches (remote and local)
            delete: Branch name to delete
        """
        try:
            if delete:
                subprocess.run(["git", "branch", "-d", delete], check=True)
                logger.info(f"Deleted branch: {delete}")
            elif list_all:
                result = subprocess.run(
                    ["git", "branch", "-a"],
                    capture_output=True,
                    text=True,
                    check=True,
                )
                logger.info("All branches:")
                logger.info(result.stdout)
            else:
                result = subprocess.run(
                    ["git", "branch"],
                    capture_output=True,
                    text=True,
                    check=True,
                )
                logger.info("Local branches:")
                logger.info(result.stdout)
        except subprocess.CalledProcessError as e:
            logger.exception(f"Git branch command failed: {e}")
            return


@dataclass
class GitRestartTGitCacheCommand:
    """Git restart command."""

    def run(self) -> None:
        """Run git restart command."""
        logger.info("Restarting tgitcache...")
        cmds = ["taskkill", "/f", "/t", "/im", "tgitcache.exe"] if is_windows else ["killall", "tgitcache"]

        try:
            subprocess.run(cmds, check=True)
            logger.info("Restart completed successfully")
        except subprocess.CalledProcessError as e:
            logger.exception(f"Git restart failed: {e}")
            return


_COMMAND_MAP = {
    "i": GitInitCommand,
    "a": GitAddCommand,
    "c": GitCleanCommand,
    "p": GitPushCommand,
    "pl": GitPullCommand,
    "co": GitCheckoutCommand,
    "b": GitBranchCommand,
}


def parse_args() -> argparse.Namespace:
    """Parse command line arguments.

    Returns
    -------
        argparse.Namespace: Parsed arguments
    """
    parser = argparse.ArgumentParser(
        description="Gittool - Git command wrapper with parallel execution support",
    )
    subparsers = parser.add_subparsers(
        dest="command",
        help="Available commands",
        required=True,
    )

    # Global parallel execution options
    parser.add_argument(
        "--parallel",
        "-P",
        action="store_true",
        help="Enable parallel execution for supported commands",
    )
    parser.add_argument(
        "--workers",
        "-w",
        type=int,
        default=4,
        help="Number of parallel workers (default: 4)",
    )

    # Init subcommand
    parser_i = subparsers.add_parser("i", help="Initialize git repository")
    parser_i.add_argument(
        "--parallel",
        "-P",
        action="store_true",
        help="Enable parallel execution for git init",
    )
    parser_i.add_argument(
        "--workers",
        "-w",
        type=int,
        default=3,
        help="Number of parallel workers for init (default: 3)",
    )

    # Add subcommand
    subparsers.add_parser("a", help="Stage and commit changes")

    # Clean subcommand
    parser_c = subparsers.add_parser("c", help="Clean untracked files")
    parser_c.add_argument(
        "-f",
        "--force",
        required=False,
        action="store_true",
        help="Force clean",
    )
    parser_c.add_argument(
        "--parallel",
        "-P",
        action="store_true",
        help="Enable parallel execution for git clean",
    )
    parser_c.add_argument(
        "--workers",
        "-w",
        type=int,
        default=4,
        help="Number of parallel workers for clean (default: 4)",
    )

    # Push subcommand
    parser_p = subparsers.add_parser("p", help="Push to remote")
    parser_p.add_argument("--all", "-a", action="store_true", help="Push all branches")

    # Pull subcommand
    parser_pl = subparsers.add_parser("pl", help="Pull from remote")
    parser_pl.add_argument(
        "--rebase",
        "-r",
        action="store_true",
        help="Pull with rebase",
    )

    # Checkout subcommand
    parser_co = subparsers.add_parser("co", help="Checkout branch")
    parser_co.add_argument("branch", help="Branch name")
    parser_co.add_argument(
        "-b",
        "--create",
        action="store_true",
        help="Create new branch",
    )

    # Branch subcommand
    parser_b = subparsers.add_parser("b", help="List or manage branches")
    parser_b.add_argument("-a", "--all", action="store_true", help="List all branches")
    parser_b.add_argument("-d", "--delete", metavar="BRANCH", help="Delete branch")

    return parser.parse_args()


def main() -> None:
    """Run main entry point for gittool CLI."""
    args = parse_args()
    command = _COMMAND_MAP.get(args.command)

    if not command:
        msg = f"Invalid command: {args.command}"
        raise ValueError(msg)

    cmd_instance = command()

    # Determine if parallel execution should be enabled
    use_parallel = getattr(args, "parallel", False)
    max_workers = getattr(args, "workers", 4)

    if args.command == "i":
        # Git init command
        init_workers = getattr(args, "workers", 3)  # Different default for init
        cmd_instance.run(parallel=use_parallel, max_workers=init_workers)
    elif args.command == "a":
        # Git add command (no parallel support yet)
        cmd_instance.run()
    elif args.command == "c":
        # Git clean command
        cmd_instance.run(
            force=getattr(args, "force", False),
            parallel=use_parallel,
            max_workers=max_workers,
        )
    elif args.command == "p":
        # Git push command (no parallel support yet)
        cmd_instance.run(all_branches=getattr(args, "all", False))
    elif args.command == "pl":
        # Git pull command (no parallel support yet)
        cmd_instance.run(rebase=getattr(args, "rebase", False))
    elif args.command == "co":
        # Git checkout command (no parallel support yet)
        cmd_instance.run(branch=args.branch, create=getattr(args, "create", False))
    elif args.command == "b":
        # Git branch command (no parallel support yet)
        cmd_instance.run(
            list_all=getattr(args, "all", False),
            delete=getattr(args, "delete", None),
        )


if __name__ == "__main__":
    main()
