import os
import importlib.resources
import subprocess
import sys
import pickle

class Core(object):
    """
    NOTE
    For future developers, almost everything inherits from Core and therefore the best
    way to use the classes that do inherit from Core (most of them) is to use a context
    manager when interacting with these classes as the __enter__ contains necessary setup
    steps for the build object. 
    You will see this type of interaction in almost every module within /commands. This 
    is a design choice to keep the underlying repository structure rigid and allow for 
    a formal build process as changes to the code base get caught by exceptions if the
    project is not rebuilt. 
    XXX
    This isn't done until BLKMRLN can build itself into one file with MinfiedBuild.
    """
    def __init__(self,project_name,base_dir=os.getcwd(),env_dir=os.getcwd()):
        self.base_dir = base_dir
        self.project_name = project_name
        self.env_dir = os.path.join(env_dir,f'.venvmanager/{project_name}')
        

    def __enter__(self):
        # Check if the pickle file exists
        try:
            # Load the object from the pickle file
            with open(f'build/build_object.pkl', 'rb') as f:
                loaded_obj = pickle.load(f)
                self.base_dir = loaded_obj.base_dir
                self.project_name = loaded_obj.project_name
                self.project_dir = loaded_obj.project_dir
                self.env_dir = loaded_obj.env_dir
                self.resources_dir = loaded_obj.resources_dir
                self._requirements_file = loaded_obj._requirements_file
                self.build_object = loaded_obj.build_object
                self.rebuild = True
            print("Pickle found. Loading state from build object.")
        except FileNotFoundError:
            # Initialize or modify class variables if no pickle exists
            self.resources_dir = self.get_resources_directory()
            self.project_dir = os.path.join(self.base_dir,self.project_name)
            self._requirements_file= os.path.join(self.resources_dir,'dep/common/requirements.txt')
            self.build_object = os.path.join(self.project_dir,'build/build_object.pkl')
            self.rebuild=False
            print("No pickle found. Initialized new state.")
        return self  # Return the object itself

    def __exit__(self, exc_type, exc_val, exc_tb):
        
        # Persist the class object by pickling
        
        with open(self.build_object, 'wb') as f:
            pickle.dump(self, f)
        print("Exiting context and pickling build object.")
        return False
    
    def __str__(self):
        return f"\nBuild State:\n"+\
               f"\n\tBase Directory:\t\t{self.base_dir}\n"+\
               f"\n\tProject Name:\t\t{self.project_name}\n"+\
               f"\n\tProject Directory:\t{self.project_dir}\n"+\
               f"\n\tEnvironment Directory:\t{self.env_dir}\n"+\
               f"\n\tBuild Object:\t\t{self.build_object}\n"

    def get_resources_directory(self):
        """
        Get the path to the `resources` directory within the `blkmrln` package.
        """
        resources_path = importlib.resources.files('blkmrln').joinpath('resources')
        
        return str(resources_path)

    def validate_directories(self):
        """
        Validates that the source directories exist and are directories.
        """
        source_dirs = ['src','common','config','dep','test']
        invalid_dirs = []
        for source_dir in source_dirs:
            source_dir = os.path.join(self.project_dir,source_dir)
            if not os.path.exists(source_dir) or not os.path.isdir(source_dir):
                invalid_dirs.append(source_dir)
        return len(invalid_dirs) == 0, invalid_dirs

    def setup_project_structure(self):
        """
        Sets up the basic project structure within the base directory.
        """
        if not os.path.exists(self.project_dir):
            os.makedirs(self.project_dir)
            os.makedirs(os.path.join(self.project_dir,self.project_name))
        else:
            "Run Setup tools and create bin dist for already existing project."
            return None
        return self.project_dir

    def create_readme(self)->None:
        """
        Creates a README file in the project directory.
        """
        readme_path = os.path.join(self.project_dir, 'README.md')
        with open(readme_path, 'w') as f:
            f.write(f"# {self.project_name}\n\nGenerated by blkmrln.\n")
        print(f"README.md created at {readme_path}")

    def initialize_git_repo(self):
        """
        Initializes a Git repository in the project directory.
        """
        if not os.path.exists(os.path.join(self.project_dir, '.git')):
            os.system(f'git init {self.project_dir}')
            print(f"Initialized empty Git repository in {self.project_dir}")
        else:
            print(f"Git repository already exists in {self.project_dir}")

    def create_virtual_environment(self):
        """
        Creates a virtual environment in the specified directory.
        """
        if not os.path.exists(self.env_dir):
            print(f"Creating virtual environment at {self.env_dir}...")
            os.makedirs(self.env_dir, exist_ok=True)
            
            # Create the virtual environment
            subprocess.check_call([sys.executable, '-m', 'venv', self.env_dir])
            print(f"Virtual environment created at {self.env_dir}")
        else:
            print(f"Virtual environment already exists at {self.env_dir}")
        
        return self.env_dir

    def install_requirements(self):
        """
        Installs dependencies from a requirements file into the virtual environment.
        """
        pip_executable = os.path.join(self.env_dir, 'bin', 'pip')
        
        if not os.path.exists(pip_executable):
            raise FileNotFoundError("Pip not found in the virtual environment")
        
        # Install dependencies
        subprocess.check_call([pip_executable, 'install', '-r', self._requirements_file])
        print("Dependencies installed.")

    def activate_virtual_environment(self):
        """
        Prints instructions to activate the virtual environment.
        """
        activate_script = os.path.join(self.env_dir, 'bin', 'activate')
        script = f"source {activate_script} && cd {self.project_dir}"
        # Locate the site-packages directory within the virtual environment
        site_packages_dir = os.path.join(self.env_dir, 'lib', f'python{sys.version_info.major}.{sys.version_info.minor}', 'site-packages')

        # Create the .pth file in the site-packages directory
        pth_file_path = os.path.join(site_packages_dir, 'project_directory.pth')
        with open(pth_file_path, 'w') as pth_file:
            pth_file.write(self.project_dir + '\n')
            pth_file.write(f"{self.project_dir}/src" + '\n')
            pth_file.write(f"{self.project_dir}/test" + '\n')
        print(f"Created .pth file at: {pth_file_path}")
        print(f"Added {self.project_dir} to Python path.")
        print(f"\nTo activate the virtual environment and use the project directory, run:\n{script}")

class MinifiedBuild(Core):
    def __init__(self,project_name,base_dir=os.getcwd(),env_dir=os.getcwd()):
        super().__init__(project_name,base_dir,env_dir)
        self.import_tree = {}
        #self.minified_build = open('minified.py')
    
    def __del__(self):
        #self.minified_build.close()
        self.__delattr__('import_tree')

    def __enter__(self):
        super().__enter__()
        self.build_import_tree()
        self.parse_import_tree()
        return self

    def build_import_tree(self):
        """
        Starting at the __main__.py of the package iterate through all imports building
        a tree of filepaths where each node represents the filepath to a file required
        for the package build. 
        NOTE The underlying assumption here is that the user will build their package
        structure such that the __main__.py is the entrypoint for the package and all
        imports for the package can be resolved from __main__.py or once of the modules
        imported into it or from one of the modules imported from one of that modules 
        sub modules, etc.. you can see how a tree becomes a natural solution. 
        """
        pass

    def parse_import_tree(self):
        """
        This function will traverse the tree built from build_import_tree() and will run
        parse_node_file() at each node. The final exit case for this function should close
        the file minified.py, not the destructor. The destructor is here as a place holder
        until this is implemented. 
        """
        pass

    def parse_node_file(self):
        """
        This function will only be called by parse_import_tree() and will be used to 
        parse the file associated with the file path provided from the tree node. At 
        each function call this function will parse the tree node file, and write the
        results to self.minified_build. 
        """
        pass

    

    