import os
import re
import subprocess
import sys
import argparse
import importlib.metadata
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
import glob

class RequirementsGenerator:
    def __init__(
            self,
            source_directory,
            output_directory,
            env_name="env",
            project_name="auto_generated_project",
            project_description="A project generated by RequirementsGenerator",
            project_author="Your Name <you@example.com>",
            use_virtual_env=True,
            pinned_versions=None,
            verbose=False,
            log_file=None,
            package_manager="pip",
            python_env_version=["python3.12"],
            ):
        self.source_directory = source_directory  # Source directory to scan for Python files
        self.output_directory = output_directory  # Output directory for generated files
        self.python_files = []
        self.imports = set()
        self.env_name = env_name  # Name for the virtual environment
        self.env_path = os.path.join(output_directory, env_name) if use_virtual_env and output_directory else None  # Full path to the virtual environment
        self.project_name = project_name  # Project name for pyproject.toml
        self.project_description = project_description  # Project description for pyproject.toml
        self.project_author = project_author  # Project author for pyproject.toml
        self.use_virtual_env = use_virtual_env  # Flag to determine if virtual environment should be used
        self.pinned_versions = pinned_versions if pinned_versions else {}  # Dictionary of pinned versions
        self.verbose = verbose  # Verbose logging
        self.package_manager = package_manager  # Package manager to use
        self.python_env_version = python_env_version if use_virtual_env else None  # Python versions for virtual environments
        current_version = sys.version_info
        self.python_version = self.python_env_version if use_virtual_env else f"{current_version.major}.{current_version.minor}"  # Python version for global environment
        self.setup_logging(verbose, log_file)

    # Setup logging with file support
    def setup_logging(self, verbose, log_file=None):
        level = logging.DEBUG if verbose else logging.INFO
        logging.basicConfig(filename=log_file, level=level, format='%(levelname)s: %(message)s')

    # Recursively find all Python files
    def find_python_files(self):
        for root, _, files in os.walk(self.source_directory):  # Scan the source directory
            for file in files:
                if file.endswith(".py"):
                    self.python_files.append(os.path.join(root, file))

    # Extract imports from a Python file
    def extract_imports_from_file(self, filepath):
        import_pattern = re.compile(r'^\s*(?:import|from)\s+([\w\.]+)')
        file_imports = set()

        try:
            with open(filepath, 'r', encoding='utf-8') as file:
                for line in file:
                    match = import_pattern.match(line)
                    if match:
                        file_imports.add(match.group(1).split('.')[0])
        except Exception as e:
            logging.error(f"Error processing {filepath}: {e}")

        return file_imports

    # Method to ensure uv is installed
    def ensure_uv_installed(self):
        if self.use_virtual_env:
            pip_path = os.path.join(self.env_path, 'bin', 'pip') if os.name != 'nt' else os.path.join(self.env_path, 'Scripts', 'pip.exe')
            try:
                subprocess.run([pip_path, "show", "uv"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            except subprocess.CalledProcessError:
                logging.info("uv is not installed in the virtual environment. Installing uv...")
                subprocess.run([pip_path, "install", "uv"], check=True)
                logging.info("uv installed successfully.")
        else:
            try:
                importlib.metadata.version("uv")
            except importlib.metadata.PackageNotFoundError:
                logging.info("uv is not installed. Installing uv...")
                subprocess.check_call([sys.executable, "-m", "pip", "install", "uv"])
                logging.info("uv installed successfully.")

    # Method to ensure matplotlib is installed
    def ensure_matplotlib_installed(self):
        if self.use_virtual_env:
            pip_path = os.path.join(self.env_path, 'bin', 'pip') if os.name != 'nt' else os.path.join(self.env_path, 'Scripts', 'pip.exe')
            try:
                subprocess.run([pip_path, "show", "matplotlib"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            except subprocess.CalledProcessError:
                logging.info("matplotlib is not installed in the virtual environment. Installing matplotlib...")
                subprocess.run([pip_path, "install", "matplotlib"], check=True)
                logging.info("matplotlib installed successfully.")
        else:
            try:
                importlib.metadata.version("matplotlib")
            except importlib.metadata.PackageNotFoundError:
                logging.info("matplotlib is not installed. Installing matplotlib...")
                subprocess.check_call([sys.executable, "-m", "pip", "install", "matplotlib"])
                logging.info("matplotlib installed successfully.")

    # Method to ensure networkx is installed
    def ensure_networkx_installed(self):
        if self.use_virtual_env:
            pip_path = os.path.join(self.env_path, 'bin', 'pip') if os.name != 'nt' else os.path.join(self.env_path, 'Scripts', 'pip.exe')
            try:
                subprocess.run([pip_path, "show", "networkx"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            except subprocess.CalledProcessError:
                logging.info("networkx is not installed in the virtual environment. Installing networkx...")
                subprocess.run([pip_path, "install", "networkx"], check=True)
                logging.info("networkx installed successfully.")
        else:
            try:
                importlib.metadata.version("networkx")
            except importlib.metadata.PackageNotFoundError:
                logging.info("networkx is not installed. Installing networkx...")
                subprocess.check_call([sys.executable, "-m", "pip", "install", "networkx"])
                logging.info("networkx installed successfully.")

    # Method to ensure poetry is installed
    def ensure_poetry_installed(self):

        if self.use_virtual_env:
            try:
                subprocess.run(["poetry", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                logging.info("poetry is installed on the host system")
            except subprocess.CalledProcessError:
                subprocess.run(["curl", "-sSL", "https://install.python-poetry.org", " | ", "python3 -"], shell=True)
                logging.info("Poetry installed successfully.")
            except FileNotFoundError:
                subprocess.run(["curl", "-sSL", "https://install.python-poetry.org", " | ", "python3 -"], shell=True)
                logging.info("Poetry installed successfully.")
        else:
            try:
                subprocess.run(["poetry", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            except subprocess.CalledProcessError:
                logging.info("poetry is not installed. Installing poetry...")
                subprocess.run(["curl", "-sSL", "https://install.python-poetry.org", " | ", "python3 -"], shell=True)
                logging.info("poetry installed successfully.")

    #method to ensure pyenv is installed
    def ensure_pyenv_installed(self):
        # Check if pyenv is installed
        try:
            subprocess.run(["pyenv", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        except subprocess.CalledProcessError:
            logging.info("pyenv is not installed. Installing pyenv...")
            subprocess.run(["curl", "-L", "https://pyenv.run", " | ", "bash"], check=True)
            logging.info("pyenv installed successfully.")
            # Automatically update the user's shell configuration
            bashrc_path = os.path.expanduser("~/.bashrc")
            with open(bashrc_path, "a") as bashrc:
                bashrc.write('\n# Pyenv initialization\n')
                bashrc.write('export PYENV_ROOT="$HOME/.pyenv"\n')
                bashrc.write('[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"\n')
                bashrc.write('eval "$(pyenv init -)"\n')
            logging.info("Updated .bashrc with pyenv initialization. Please restart your terminal.")

    # Get installed version of a package
    def get_installed_version(self, package_name):
        try:
            return importlib.metadata.version(package_name)
        except importlib.metadata.PackageNotFoundError:
            return None

    # Merge detected and existing requirements with conflict resolution
    def merge_requirements(self, new_requirements, existing_file='requirements.txt'):
        existing_file = os.path.join(self.output_directory, existing_file)  # Ensure existing file is in the output directory
        if os.path.exists(existing_file):
            with open(existing_file, 'r') as f:
                existing_requirements = set(f.read().splitlines())
            existing_requirements.discard("# This file was auto-generated by RequirementsGenerator")
            merged_requirements = existing_requirements.union(new_requirements)

            # Conflict resolution: keep the latest version
            versioned_requirements = {}
            for req in merged_requirements:
                pkg, _, version = req.partition('==')
                if pkg in versioned_requirements:
                    existing_version = versioned_requirements[pkg].split('==')[1]
                    if version and (not existing_version or self.compare_versions(version, existing_version) > 0):
                        versioned_requirements[pkg] = req
                else:
                    versioned_requirements[pkg] = req

            return sorted(versioned_requirements.values())
        else:
            return sorted(new_requirements)

    # Compare version strings
    def compare_versions(self, v1, v2):
        return tuple(map(int, v1.split('.'))) > tuple(map(int, v2.split('.')))

    # Generate requirements.txt from the imports and handle version pinning
    def generate_requirements_txt(self, output_file='requirements.txt'):
        output_file = os.path.join(self.output_directory, output_file)  # Ensure output file is in the project directory
        logging.info("Generating requirements.txt")
        requirements = []
        for package in sorted(self.imports):
            if package in self.pinned_versions:
                # Use the pinned version from the CLI
                requirements.append(f"{package}=={self.pinned_versions[package]}")
            else:
                # Use detected installed version, if available
                installed_version = self.get_installed_version(package)
                if installed_version:
                    requirements.append(f"{package}=={installed_version}")
                else:
                    # Just use the package name without version pinning
                    requirements.append(package)

        # Merge with existing requirements, if any
        requirements = self.merge_requirements(requirements)

        # Write to requirements.txt with a comment that it was generated
        with open(output_file, 'w') as f:
            f.write("# This file was auto-generated by RequirementsGenerator\n")
            f.write('\n'.join(requirements))
        logging.info(f"Requirements written to {output_file}")

    # Create pyproject.toml file with a custom project name and optional dev dependencies
    def create_pyproject(self, output_file='pyproject.toml', include_dev=False):
        output_file = os.path.join(self.output_directory, output_file)  # Ensure output file is in the project directory
        logging.info("Generating pyproject.toml")
        if self.package_manager == "poetry":
            pyproject_content = f"""
# This file was auto-generated by RequirementsGenerator
[tool.poetry]
name = "{self.project_name}"
version = "0.1.0"
description = "{self.project_description}"
authors = ["{self.project_author}"]

[tool.poetry.dependencies]
python = "^{self.python_version.strip('python')}"
"""
            for pkg in sorted(self.imports):
                if pkg in self.pinned_versions:
                    # Use the pinned version if available
                    logging.info(f"using pinned version for {pkg}")
                    pyproject_content += f'    {pkg} = {{ version = "^{self.pinned_versions[pkg]}", python = "{self.python_version}" }}\n'
                else:
                    # Use the installed version if available
                    installed_version = self.get_installed_version(pkg)
                    if installed_version:
                        logging.info(f"using installed version for {pkg}")
                        pyproject_content += f'    {pkg} = {{ version = "^{installed_version}", python = "{self.python_version}" }}\n'
                    else:
                        # Fallback to wildcard if no version is available
                        pyproject_content += f'    {pkg} = {{ version = "*", python = "{self.python_version}" }}\n'

            if include_dev:
                pyproject_content += """
[tool.poetry.dev-dependencies]
pytest = "^6.0"
black = "^21.7b0"
"""
            if self.use_virtual_env:
                pyproject_content += f"""
[virtualenvs]
path = "{self.env_path}"
create = "false --local"
prefer-active-python = "true --local"
"""
        elif self.package_manager == "uv":
            pyproject_content = f"""
# This file was auto-generated by RequirementsGenerator
[project]
name = "{self.project_name}"
version = "0.1.0"
description = "{self.project_description}"
authors = ["{self.project_author}"]

dependencies = [
"""
            for pkg in sorted(self.imports):
                if pkg in self.pinned_versions:
                    # Use the pinned version if available
                    pyproject_content += f'    "{pkg} == {self.pinned_versions[pkg]}",\n'
                else:
                    # Use the installed version if available
                    installed_version = self.get_installed_version(pkg)
                    if installed_version:
                        pyproject_content += f'    "{pkg} == {installed_version}",\n'
                    else:
                        # Fallback to wildcard if no version is available
                        pyproject_content += f'    "{pkg} == *",\n'
            pyproject_content += "]\n"  # Close the dependencies list

            if include_dev:
                pyproject_content += """
[tool.uv]
dev-dependencies = [
"pytest >= 6.0",
"black >= 21.7b0",
]
"""
        with open(output_file, 'w') as f:
            f.write(pyproject_content)
        logging.info(f"Pyproject.toml written to {output_file}")

    # Create Pipfile for pipenv users
    def create_pipfile(self, output_file='Pipfile'):
        output_file = os.path.join(self.output_directory, output_file)  # Ensure output file is in the project directory
        logging.info("Generating Pipfile")
        pipfile_content = """
# This file was auto-generated by RequirementsGenerator
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[packages]
"""
        for pkg in sorted(self.imports):
            if pkg in self.pinned_versions:
                # Use the pinned version if available
                pipfile_content += f'"{pkg}" = "{self.pinned_versions[pkg]}"\n'
            else:
                # Use the installed version if available
                installed_version = self.get_installed_version(pkg)
                if installed_version:
                    pipfile_content += f'"{pkg}" = "{installed_version}"\n'
                else:
                    # Fallback to wildcard if no version is available
                    pipfile_content += f'"{pkg}" = "*"\n'

        with open(output_file, 'w') as f:
            f.write(pipfile_content)
        logging.info(f"Pipfile written to {output_file}")

    # Create virtual environment
    def create_virtual_env(self, python_version="python3"):
        self.python_version = python_version.strip("python")
        if self.use_virtual_env:
            # Check if the specified Python version is available
            try:
                #subprocess.run([python_version, "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                result = subprocess.run(["pyenv", "versions"], capture_output=True, text=True)
                if self.python_version in result.stdout:
                    logging.info(f"Python version '{python_version}' is already installed.")
                else:
                    logging.error(f"Python version '{python_version}' is not installed.")
            except subprocess.CalledProcessError:
                logging.error(f"Python version '{python_version}' is not installed.")
            except FileNotFoundError:
                logging.error(f"Python version '{python_version}' is not installed.")
                # Check if pyenv is installed
                self.ensure_pyenv_installed()

                # Install the required Python version using pyenv
                logging.info(f"Installing Python version '{python_version}' using pyenv...")
                subprocess.run(["pyenv", "install", self.python_version], check=True)

                logging.info(f"Python version '{python_version}' installed successfully.")

            # Create the virtual environment
            #env_path = os.path.join(self.output_directory, f"{self.env_name}_{python_version.replace('.', '_')}")  # Unique env name
            #env_path = os.path.join(os.path.abspath(os.path.expanduser(f"~/.pyenv/versions/{self.python_version}*/envs")), f"{self.env_name}_{python_version.replace('.', '_')}")  # Unique env name
            env_pattern = os.path.expanduser(f"~/.pyenv/versions/{self.python_version}*/envs")  # Pattern to match Python version directories
            env_dirs = glob.glob(env_pattern)  # Get all matching directories
            env_path = os.path.join(os.path.abspath(env_dirs[0]), f"{self.env_name}_{python_version.replace('.', '_')}")  # Unique env name

            self.env_name = f"{self.env_name}_{python_version.replace('.', '_')}"
            self.env_path = env_path  # Update the env path
            logging.info(f"Creating virtual environment at {env_path} with Python {python_version}...")
            logging.info(f"installing {python_version}-venv via apt, will prompt for sudo password unless already entered")
            subprocess.run(['sudo', 'apt', 'install', f"{python_version}-venv"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            if not os.path.exists(env_path):
                subprocess.run(["pyenv", "virtualenv", self.python_version, self.env_name], check=True)
                logging.info(f"Virtual environment created at {env_path}")
            else:
                logging.info(f"Virtual environment {self.env_name} already exists")

    # Install the requirements in the virtual environment (or globally if no virtual environment)
    def install_requirements(self, requirements_file='requirements.txt'):
        self.requirements_file = requirements_file
        requirements_file = os.path.join(self.output_directory, requirements_file)  # Ensure requirements file is in the output directory

        if self.use_virtual_env:
            # Check which package manager to use
            if self.package_manager == "pip":
                pip_path = os.path.join(self.env_path, 'bin', 'pip') if os.name != 'nt' else os.path.join(self.env_path, 'Scripts', 'pip.exe')
                #python_path = os.path.join(self.env_path, 'bin', 'python') if os.name != 'nt' else os.path.join(self.env_path, 'Scripts', 'python.exe')
                if not os.path.exists(pip_path):
                    raise FileNotFoundError("pip not found in virtual environment. Ensure the environment was created correctly.")
                if requirements_file == 'requirements.txt':
                    logging.info(f"Installing requirements in the virtual environment at {self.env_path} using pip...")
                    subprocess.run([pip_path, "install", "-r", requirements_file], check=True)
                else:
                    logging.info(f"Installing requirements in the virtual environment at {self.env_path} using pip...")
                    subprocess.run([pip_path, "install", "."], check=True)

            elif self.package_manager == "poetry":
                # Check if Poetry is installed in the virtual environment
                self.ensure_poetry_installed()
                # Save the current directory
                current_dir = os.getcwd()
                try:
                    # Change to the output directory
                    logging.info("using poetry in virtual environment")
                    os.chdir(self.output_directory)
                    logging.info(f"Using Poetry to install requirements in {self.env_name}...at {self.env_path}")
                    #subprocess.run(["poetry", "env", "use", self.env_path], check=True, env=env)
                    command = f"""
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
export VIRTUAL_ENV="{self.env_path}"
eval "$(pyenv init --path)"
eval "$(pyenv init -)"
pyenv activate {self.env_name} && poetry install
"""
                    subprocess.run(command, shell=True, executable="/bin/bash")  # Install using Poetry
                finally:
                    # Change back to the original directory
                    os.chdir(current_dir)

            elif self.package_manager == "uv":
                # Check if uv is installed in the virtual environment
                self.ensure_uv_installed()
                logging.info("Using uv to install requirements...")
                subprocess.run([sys.executable, "-m", "uv", "pip", "install", "-r", requirements_file], check=True)

            else:
                logging.error(f"Unknown package manager: {self.package_manager}. Please specify 'pip', 'poetry', or 'uv'.")
                sys.exit(1)  # Exit if the package manager is unknown

        else:
            if self.package_manager == "pip":
                if requirements_file == 'requirements.txt':
                    # Install in the global/default Python environment
                    logging.info(f"Installing requirements in the global Python environment via pip from {requirements_file}...")
                    subprocess.run([sys.executable, "-m", "pip", "install", "-r", requirements_file], check=True)
                else:
                    logging.info(f"Installing requirements in the global Python environment via pip from {requirements_file}...")
                    subprocess.run([sys.executable, "-m", "pip", "install", "."], check=True)

            elif self.package_manager == "poetry":
                self.ensure_poetry_installed()
                logging.info("Installing requirements in the global Python environment using Poetry...")
                subprocess.run(["poetry", "install"], check=True)
            elif self.package_manager == "uv":
                self.ensure_uv_installed()
                logging.info("Installing requirements in the global Python environment using uv...")
                subprocess.run([sys.executable, "-m", "uv", "pip", "install", "-r", requirements_file], check=True)
            else:
                logging.error(f"Unknown package manager: {self.package_manager}. Please specify 'pip', 'poetry', or 'uv'.")
                sys.exit(1)  # Exit if the package manager is unknown

        logging.info("Requirements installed successfully.")

    # Visualize the dependency graph
    def visualize_dependency_graph(self, output_file='dependency_graph.png'):
        self.ensure_networkx_installed()
        self.ensure_matplotlib_installed()
        import networkx as nx
        import matplotlib.pyplot as plt
        G = nx.Graph()
        for pkg in self.imports:
            G.add_node(pkg)
            # Here you can add edges based on your logic, e.g., if you have a way to determine dependencies
            # G.add_edge(pkg, dependency)

        plt.figure(figsize=(10, 8))
        nx.draw(G, with_labels=True, node_color='lightblue', node_size=2000, font_size=10)
        plt.title("Dependency Graph")
        plt.savefig(os.path.join(self.output_directory, output_file))  # Save to the output directory
        plt.close()  # Close the plot to free memory
        logging.info(f"Dependency graph saved to {os.path.join(self.output_directory, output_file)}")

    # Create a Dockerfile for the project
    def create_dockerfile(self, output_file='Dockerfile', main_script='your_script.py'):
        output_file = os.path.join(self.output_directory, output_file)  # Ensure output file is in the output directory
        logging.info("Generating Dockerfile")
        dockerfile_content = f"""
# This file was auto-generated by RequirementsGenerator
FROM python:{self.python_version}-slim

WORKDIR /app

COPY . .
"""
        try:
            if self.requirements_file == "requirements.txt":
                logging.info("creating Dockerfile with requirements.txt as requirements file")
                dockerfile_content += f"""

RUN pip install --no-cache-dir -r requirements.txt

CMD ["python", "{main_script}"]
"""
            elif self.requirements_file == "pyproject.toml":
                logging.info(f"creating Dockerfile with pyproject.toml as requirements file")
                dockerfile_content += f"""

RUN pip install uv
RUN uv pip install -r pyproject.toml
CMD ["python", "{main_script}"]
"""
        finally:
            with open(output_file, 'w') as f:
                f.write(dockerfile_content)
        logging.info(f"Dockerfile written to {output_file}")

    # Main function to execute the process
    def generate_requirements(
            self,
            skip_install=False,
            include_dev=False,
            create_pipfile=False,
            visualize_graph=False,
            create_reqs_file=False,
            create_pyproject=False
            ):
        self.find_python_files()
        self.include_dev = include_dev
        # Parallelize import extraction using ThreadPoolExecutor
        with ThreadPoolExecutor() as executor:
            futures = {executor.submit(self.extract_imports_from_file, file): file for file in self.python_files}
            for future in as_completed(futures):
                file_imports = future.result()
                self.imports.update(file_imports)

        if create_reqs_file:
            self.generate_requirements_txt()

        if create_pipfile:
            self.create_pipfile()

        if self.use_virtual_env:
            # Create virtual environment and install requirements
            for python_version in self.python_env_version:
                self.create_virtual_env(python_version=python_version)

        if create_pyproject:
            self.create_pyproject(include_dev=include_dev)


        if not skip_install and create_reqs_file:
            self.install_requirements()

        elif not skip_install and create_pyproject:
            self.install_requirements(requirements_file='pyproject.toml')

        if visualize_graph:
            self.visualize_dependency_graph()

    # Method to increment version
    def increment_version(self, level):
        """
        Increment the version in pyproject.toml file.

        Parameters:
        level (str): The level of version to increment ('major', 'minor', 'patch')
        """
        pyproject_file = os.path.join(self.output_directory, 'pyproject.toml')  # Ensure pyproject file is in the output directory

        # Read pyproject.toml and get the current version
        with open(pyproject_file, 'r') as f:
            pyproject_content = f.read()

        # Extract the current version using regex
        version_match = re.search(r'(?<=^version = ")([0-9]+\.[0-9]+\.[0-9]+)', pyproject_content, re.MULTILINE)
        if not version_match:
            logging.error("Error: Version not found in pyproject.toml")
            return

        current_version = version_match.group(0)
        logging.info(f"Current version: {current_version}")

        # Split the version into major, minor, and patch
        major, minor, patch = map(int, current_version.split('.'))

        # Increment the correct version part
        if level == 'major':
            major += 1
            minor = 0  # Reset minor and patch when major is incremented
            patch = 0
        elif level == 'minor':
            minor += 1
            patch = 0  # Reset patch when minor is incremented
        elif level == 'patch':
            patch += 1
        else:
            logging.error("Error: Invalid version level. Choose 'major', 'minor', or 'patch'.")
            return

        # Construct the new version
        new_version = f"{major}.{minor}.{patch}"
        logging.info(f"New version: {new_version}")

        # Replace the old version in pyproject.toml with the new version
        new_pyproject_content = re.sub(r'(?<=^version = ")[0-9]+\.[0-9]+\.[0-9]+', new_version, pyproject_content, flags=re.MULTILINE)

        # Write the updated pyproject.toml back to the file
        with open(pyproject_file, 'w') as f:
            f.write(new_pyproject_content)

        logging.info(f"Version updated to {new_version} in pyproject.toml")

    # Add a new method to activate a specific virtual environment
    def activate_virtual_env(self, env_name=None):
        if env_name is None:
            env_name = self.env_name
        #if os.path.exists(env_path):
            logging.info(f"Activating virtual environment: {self.env_name}...")
            if os.name == 'nt':  # Windows
                #activate_command = f"{env_path}\\Scripts\\activate.bat"
                activate_command = f"pyenv activate {self.env_name}"
            else:  # Unix-based
                #activate_command = f". {env_path}/bin/activate"
                activate_command = f"pyenv activate {self.env_name}"

            # Execute the activation command in a new shell
            #subprocess.run(activate_command, shell=True)
            subprocess.run([activate_command], shell=True)
            logging.info(f"Virtual environment {self.env_name} activated")
            #os.environ["VIRTUAL_ENV"] = os.path.abspath(env_path)
            logging.info(f"$VIRTUAL_ENV is set at {os.environ.get('VIRTUAL_ENV')}")
        else:
            logging.error(f"Virtual environment for Python {self.python_version} does not exist.")

    def is_venv(self):
        return (hasattr(sys, 'real_prefix') or
                (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))

    def deactivate_virtual_env(self):
        logging.info("Deactivating virtual environment...")
        subprocess.run(["pyenv", "deactivate"], check=True)
        logging.info("Virtual environment deactivated.")

    @staticmethod
    def list_envs():
        result = subprocess.run(["pyenv", "virtualenvs"], check=True, capture_output=True, text=True)
        envs = result.stdout.splitlines()  # Split the output into lines
        env_names = set()  # Use a set to store unique environment names

        for line in envs:
            # Remove leading '* ' if present and strip any surrounding whitespace
            line = line.lstrip('* ').strip()
            
            # Ignore empty lines
            if not line:
                continue

            # Split the line at the first space to remove details like "(created from ...)"
            parts = line.split(' ')
            env_path = parts[0]

            # Check if the line contains '/envs/' and extract the environment name accordingly
            if '/envs/' in env_path:
                env_name = env_path.split('/envs/')[-1]
            else:
                # For environments without '/envs/', use the entire path (e.g., 'system')
                env_name = env_path

            env_names.add(env_name)

        env_list = sorted(env_names)  # Sort the list for consistency
        if not env_list:
            logging.info("No virtual environments found.")
            return env_list

        # Determine the maximum length of environment names for formatting
        max_env_length = max(len(name) for name in env_list)
        padding = 2
        total_width = max_env_length + padding + 3  # numbering and pipe

        separator = "_" * total_width

        header = "INSTALLED VIRTUAL ENVIRONMENTS:"
        logging.info(header)
        logging.info(separator)

        for idx, env_name in enumerate(env_list, 1):
            line = f"{idx}. {env_name.ljust(max_env_length)} |"
            logging.info(line)

        logging.info(separator)

        return list(env_list)  # Return the list of environment names


# Modify the CLI function to handle multiple Python versions
def main():
    parser = argparse.ArgumentParser(
        description="Requirements Generator Tool: Manage requirements, versioning, and virtual environments for Python projects.",
        formatter_class=argparse.RawTextHelpFormatter
    )

    parser.add_argument(
        "source_directory",
        type=str,
        help="Directory to recursively search for Python files and generate requirements from imports."
    )

    parser.add_argument(
        "--output_directory",
        type=str,
        help="Directory to save generated files."
    )

    parser.add_argument(
        "--env-name",
        type=str,
        default="env",
        help="Specify the name of the virtual environment to create (default: 'env')."
    )

    parser.add_argument(
        "--list-envs",
        action="store_true",
        help="List all virtual environments."
    )

    parser.add_argument(
        "--no-env",
        action="store_true",
        help="Skip creating a virtual environment and install dependencies in the global Python environment."
    )

    parser.add_argument(
        "--project-name",
        type=str,
        default="auto_generated_project",
        help="Set the project name for pyproject.toml (default: 'auto_generated_project')."
    )

    parser.add_argument(
        "--project-description",
        type=str,
        default="A project generated by RequirementsGenerator",
        help="Set the project description for pyproject.toml (default: 'A project generated by RequirementsGenerator')."
    )

    parser.add_argument(
        "--project-author",
        type=str,
        default="Your Name <you@example.com>",
        help="Set the project author for pyproject.toml (default: 'Your Name <you@example.com>')."
    )

    parser.add_argument(
        "--pin",
        action="append",
        help="Pin a dependency to a specific version. Usage: --pin package==version. Can be used multiple times."
    )

    parser.add_argument(
        "--skip-install",
        action="store_true",
        help="Skip the installation of dependencies after generating requirements."
    )

    parser.add_argument(
        "--merge-requirements",
        action="store_true",
        help="Merge newly detected dependencies into an existing requirements.txt file, instead of overwriting it."
    )

    parser.add_argument(
        "--dev",
        action="store_true",
        help="Include development dependencies (like pytest, black) in pyproject.toml."
    )

    parser.add_argument(
        "--pipfile",
        action="store_true",
        help="Generate a Pipfile for pipenv users (in addition to requirements.txt and pyproject.toml)."
    )

    parser.add_argument(
        "--verbose",
        action="store_true",
        help="Enable verbose logging for detailed output during the execution."
    )

    parser.add_argument(
        "--bump",
        choices=["major", "minor", "patch"],
        help=(
            "Bump the version in pyproject.toml:\n"
            "  major: Increment the major version (e.g., 1.0.0 -> 2.0.0)\n"
            "  minor: Increment the minor version (e.g., 1.1.0 -> 1.2.0)\n"
            "  patch: Increment the patch version (e.g., 1.1.1 -> 1.1.2)"
        )
    )

    parser.add_argument(
        "--log-file",
        type=str,
        help="Specify a log file to save logs."
    )

    parser.add_argument(
        "--dockerfile",
        action="store_true",
        help="Generate a Dockerfile for the project."
    )

    parser.add_argument(
        "--visualize-graph",
        action="store_true",
        help="Visualize the dependency graph."
    )

    parser.add_argument(
        "--main-script",
        type=str,
        default='your_script.py',
        help="Specify the main script to run in the Docker container (default: 'your_script.py')."
    )

    parser.add_argument(
        "--package-manager",
        choices=["uv", "poetry", "pip"],
        default="pip",
        help="Specify the package manager to use (default: 'pip')."
    )

    parser.add_argument(
        "--python-env-version",
        nargs='+',  # Accept multiple Python versions
        default=["python3"],  # Default to python3 if no versions are provided
        help="Specify the Python versions to use for the virtual environments (default: 'python3')."
    )

    parser.add_argument(
        "--activate-env",
        type=str,
        help="Activate a specific virtual environment by specifying the env path."
    )

    parser.add_argument(
        "--deactivate-env",
        type=str,
        help="Deactivate virtual environment."
    )

    parser.add_argument(
        "--create-reqs-file",
        action="store_true",
        help="Create a requirements.txt file."
    )

    parser.add_argument(
        "--create-pyproject",
        action="store_true",
        help="Create a pyproject.toml file."
    )

    args = parser.parse_args()

    # Initialize RequirementsGenerator with CLI arguments
    use_virtual_env = not args.no_env
    generator = RequirementsGenerator(
        source_directory=args.source_directory,
        output_directory=args.output_directory,
        env_name=args.env_name,
        project_name=args.project_name,
        project_description=args.project_description,
        project_author=args.project_author,
        use_virtual_env=use_virtual_env,
        log_file=args.log_file,
        package_manager=args.package_manager,
        python_env_version=args.python_env_version
    )

    # If bump flag is present, increment the version and exit
    if args.bump:
        generator.increment_version(args.bump)
        return  # Exit after bumping the version

    if args.list_envs:
        generator.list_envs()
        return  # Exit after listing the environments

    # If activate-env is specified, activate the corresponding virtual environment
    if args.activate_env:
        generator.activate_virtual_env(args.activate_env)
        return  # Exit after activating the environment

    if args.deactivate_env:
        generator.deactivate_virtual_env()
        return  # Exit after deactivating the environment

    # Otherwise, run the normal generation process
    generator.generate_requirements(
        skip_install=args.skip_install,
        include_dev=args.dev,
        create_pipfile=args.pipfile,
        create_reqs_file=args.create_reqs_file,
        create_pyproject=args.create_pyproject,
        visualize_graph=args.visualize_graph
    )

    if args.dockerfile:
        generator.create_dockerfile(main_script=args.main_script)  # Use the main_script argument


if __name__ == "__main__":
    main()
