import subprocess
import sys
import logging
from pathlib import Path
import asyncio
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List, Dict, Optional, Any, Union


def installs(
    pkgs: Optional[List[str]] = None, mode: str = "thread", timeout: int = 120
) -> List[Dict[str, Any]]:
    """
    Install multiple Python packages concurrently using either threading or asyncio.

    This function provides a fast, safe, and fault-tolerant way to install
    multiple Python packages in parallel. It isolates each installation process,
    captures detailed logs, and returns structured results.

    Args:
            pkgs (list[str], optional): List of package names to install.
                    If None, an empty list is used (no action).
            mode (str): Concurrency method.
                    - "threading": Uses ThreadPoolExecutor (fast, stable).
                    - "asyncio": Uses asyncio subprocess (non-blocking).
            timeout (int): Max seconds per package install before cancellation.

    Returns:
            list[dict[str, Any]]: A list of dictionaries containing results for each package.
                    Example:
                            [
                                    {"package": "requests", "success": True, "code": 0, "stderr": "", "stdout": "Successfully installed"},
                                    {"package": "numpy", "success": False, "code": 1, "stderr": "TimeoutExpired", "stdout": ""}
                            ]

    Notes:
            - Uses isolated subprocesses for safety.
            - Threaded mode is recommended for most systems.
            - Async mode requires Python 3.7+ and works best on Linux/macOS.

    Example:
            >>> installs(["requests", "pandas"], module="threading")
            [
                    {"package": "requests", "success": True, "code": 0, "stderr": "", "stdout": "Successfully installed"},
                    {"package": "pandas", "success": True, "code": 0, "stderr": "", "stdout": "Successfully installed"}
            ]
    """
    if not pkgs:
        return []

    results: List[Dict[str, Any]] = []

    def _install_package(pkg: str) -> Dict[str, Any]:
        try:
            process = subprocess.run(
                [
                    sys.executable,
                    "-m",
                    "pip",
                    "install",
                    pkg,
                    "--disable-pip-version-check",
                    "--no-input",
                ],
                capture_output=True,
                text=True,
                timeout=timeout,
            )
            return {
                "package": pkg,
                "success": process.returncode == 0,
                "code": process.returncode,
                "stdout": process.stdout.strip(),
                "stderr": process.stderr.strip(),
            }
        except subprocess.TimeoutExpired:
            return {
                "package": pkg,
                "success": False,
                "code": -1,
                "stdout": "",
                "stderr": "TimeoutExpired",
            }
        except Exception as e:
            return {
                "package": pkg,
                "success": False,
                "code": -2,
                "stdout": "",
                "stderr": f"{type(e).__name__}: {e}",
            }

    # --- Thread mode ---
    if mode == "thread":
        with ThreadPoolExecutor(max_workers=min(8, len(pkgs))) as executor:
            futures = {executor.submit(_install_package, pkg): pkg for pkg in pkgs}
            for future in as_completed(futures):
                results.append(future.result())

    # --- Async mode ---
    elif mode == "async":

        async def _async_install(pkg: str) -> Dict[str, Any]:
            try:
                process = await asyncio.create_subprocess_exec(
                    sys.executable,
                    "-m",
                    "pip",
                    "install",
                    pkg,
                    "--disable-pip-version-check",
                    "--no-input",
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE,
                )
                try:
                    stdout, stderr = await asyncio.wait_for(
                        process.communicate(), timeout=timeout
                    )
                except asyncio.TimeoutError:
                    process.kill()
                    return {
                        "package": pkg,
                        "success": False,
                        "code": -1,
                        "stdout": "",
                        "stderr": "TimeoutExpired",
                    }
                return {
                    "package": pkg,
                    "success": process.returncode == 0,
                    "code": process.returncode,
                    "stdout": stdout.decode().strip(),
                    "stderr": stderr.decode().strip(),
                }
            except Exception as e:
                return {
                    "package": pkg,
                    "success": False,
                    "code": -2,
                    "stdout": "",
                    "stderr": f"{type(e).__name__}: {e}",
                }

        async def _async_main():
            tasks = [_async_install(pkg) for pkg in pkgs]
            return await asyncio.gather(*tasks)

        results = asyncio.run(_async_main())

    else:
        raise ValueError("Invalid module. Use 'threading' or 'asyncio'.")

    return results


class __installation__:
    """
    Comprehensive package management class for Python package installation operations.

    Provides robust methods for installing, uninstalling, and updating Python packages
    with comprehensive error handling and output management.

    Attributes:
        lib (str): Package name to manage
        logger (logging.Logger): Logger instance for operation tracking
    """

    def __init__(self, lib: str, text: bool = False) -> None:
        """
        Initialize the package manager with target library and output preferences.

        Args:
            lib (str): Name of the Python package to manage
            text (bool): If True, returns detailed output; if False, returns boolean status

        Raises:
            TypeError: If lib is not a string
            ValueError: If lib is empty or contains invalid characters
        """
        if not isinstance(lib, str):
            raise TypeError(f"Library name must be a string, got {type(lib).__name__}")

        lib = lib.strip()
        if not lib:
            raise ValueError("Library name cannot be empty")

        # Basic sanitization to prevent command injection
        if any(char in lib for char in [";", "&", "|", "$", "`"]):
            raise ValueError(f"Invalid characters in library name: {lib}")

        self.lib = lib
        self.text = text
        self.logger = self._setup_logging()

    def _setup_logging(self) -> logging.Logger:
        """Configure and return logger instance for installation operations."""
        logger = logging.getLogger(f"installation_{self.lib}")
        if not logger.handlers:
            handler = logging.StreamHandler()
            formatter = logging.Formatter(
                "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
            )
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            logger.setLevel(logging.INFO)
        return logger

    def _run_pip_command(self, command: list, operation: str) -> Union[bool, str]:
        """
        Execute pip command with comprehensive error handling and output management.

        Args:
            command (list): Pip command and arguments to execute
            operation (str): Description of operation for logging and error messages

        Returns:
            Union[bool, str]: Success status or detailed output based on text setting
        """
        self.logger.info(f"Starting {operation} for package: {self.lib}")

        try:
            # Configure output based on text preference
            stdout_config = subprocess.PIPE if self.text else subprocess.DEVNULL
            stderr_config = subprocess.PIPE

            result = subprocess.run(
                command,
                stdout=stdout_config,
                stderr=stderr_config,
                text=True,
                check=True,
                timeout=300,  # 5-minute timeout
            )

            self.logger.info(f"Successfully completed {operation} for {self.lib}")

            if self.text:
                output = result.stdout or f"{operation} completed successfully"
                # Include stderr in output if present (for warnings)
                if result.stderr:
                    output += f"\nWarnings: {result.stderr}"
                return output
            else:
                return True

        except subprocess.CalledProcessError as e:
            self.logger.error(f"Failed to {operation} {self.lib}: {e}")

            if self.text:
                error_msg = f"Error during {operation}: {e}"
                if e.stderr:
                    error_msg += f"\nDetails: {e.stderr}"
                return error_msg
            else:
                return False

        except subprocess.TimeoutExpired:
            self.logger.error(f"Timeout during {operation} of {self.lib}")

            if self.text:
                return f"Timeout: {operation} took too long to complete"
            else:
                return False

        except Exception as e:
            self.logger.error(f"Unexpected error during {operation} of {self.lib}: {e}")

            if self.text:
                return f"Unexpected error during {operation}: {str(e)}"
            else:
                return False

    def install(self) -> Union[bool, str]:
        """
        Install the specified Python package using pip.

        Executes 'pip install <package>' with proper error handling and output management.
        Supports both simple package names and package specifications with version constraints.

        Returns:
            Union[bool, str]:
                - If text=False: Boolean indicating success (True) or failure (False)
                - If text=True: String with command output or error message

        Example:
            >>> installer = __installation__("requests")
            >>> result = installer.install()
            True

            >>> installer = __installation__("requests", text=True)
            >>> result = installer.install()
            'Collecting requests... Successfully installed requests-2.28.1'
        """
        command = [sys.executable, "-m", "pip", "install", self.lib]

        return self._run_pip_command(command, "installation")

    def uninstall(self) -> Union[bool, str]:
        """
        Uninstall the specified Python package using pip.

        Executes 'pip uninstall -y <package>' with automatic confirmation
        to enable non-interactive operation.

        Returns:
            Union[bool, str]:
                - If text=False: Boolean indicating success (True) or failure (False)
                - If text=True: String with command output or error message

        Example:
            >>> installer = __installation__("requests")
            >>> result = installer.uninstall()
            True

            >>> installer = __installation__("requests", text=True)
            >>> result = installer.uninstall()
            'Found existing installation: requests 2.28.1... Successfully uninstalled requests-2.28.1'
        """
        command = [
            sys.executable,
            "-m",
            "pip",
            "uninstall",
            "-y",  # Auto-confirm uninstall
            self.lib,
        ]

        return self._run_pip_command(command, "uninstallation")

    def update(self) -> Union[bool, str]:
        """
        Update the specified Python package to the latest version.

        Executes 'pip install --upgrade <package>' to update an existing
        installation to the latest available version.

        Returns:
            Union[bool, str]:
                - If text=False: Boolean indicating success (True) or failure (False)
                - If text=True: String with command output or error message

        Example:
            >>> installer = __installation__("requests")
            >>> result = installer.update()
            True

            >>> installer = __installation__("requests", text=True)
            >>> result = installer.update()
            'Collecting requests... Successfully installed requests-2.28.2'
        """
        command = [sys.executable, "-m", "pip", "install", "--upgrade", self.lib]

        return self._run_pip_command(command, "update")

    def is_installed(self) -> bool:
        """
        Check if the package is currently installed in the environment.

        Uses pip show command to verify package installation without
        attempting to install or modify anything.

        Returns:
            bool: True if package is installed, False otherwise

        Example:
            >>> installer = __installation__("requests")
            >>> installer.is_installed()
            True
        """
        try:
            result = subprocess.run(
                [sys.executable, "-m", "pip", "show", self.lib],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                check=False,
            )
            return result.returncode == 0
        except Exception:
            return False

    def get_version(self) -> Optional[str]:
        """
        Get the currently installed version of the package.

        Returns:
            Optional[str]: Version string if package is installed, None otherwise

        Example:
            >>> installer = __installation__("requests")
            >>> installer.get_version()
            '2.28.1'
        """
        try:
            result = subprocess.run(
                [sys.executable, "-m", "pip", "show", self.lib],
                stdout=subprocess.PIPE,
                stderr=subprocess.DEVNULL,
                text=True,
                check=True,
            )

            # Parse version from pip show output
            for line in result.stdout.split("\n"):
                if line.startswith("Version:"):
                    return line.split(":", 1)[1].strip()

            return None
        except Exception:
            return None

    def install_specific_version(self, version: str) -> Union[bool, str]:
        """
        Install a specific version of the package.

        Args:
            version (str): Version specification (e.g., "==2.28.1", ">=2.0,<3.0")

        Returns:
            Union[bool, str]: Success status or detailed output

        Example:
            >>> installer = __installation__("requests")
            >>> installer.install_specific_version("==2.28.1")
            True
        """
        if not isinstance(version, str):
            raise TypeError("Version must be a string")

        versioned_lib = f"{self.lib}{version}"
        temp_installer = __installation__(versioned_lib, self.text)
        return temp_installer.install()
