
import os
import re
import subprocess
import inspect
import pip
import ast
import importlib
import sys
import shutil
import textwrap


from twine.commands import upload

def createPackageTurner( inputClass , packageName = "na" , packageHousePath = "na", specificPackageFolder="na", pythonAbove="3.6" , author_email='brianbrandonturner@gmail.com', author='Brigham Turner',description="auto generated package from packageMaker" , readme="this package was generated by packageMaker using a class as input that contains functions", publishToPypi = False, pypiApiKey = "unknown", requirementsReplacement={}, relyOnOldSetupWhenPossible = True ):
    
    # addressing the issue of if there is a predecessor
        # getSetupText_fromNothing()
        # getSetupText_fromOldText(
        
        # everything is preserved except for two things:
        # version=
        # install_requires=
        
        # if a requirement is replaced with blank, that is fine
        
    print("what about importing an entire file as a module???")
    
    if False:
        packageName = "na"
        packageHousePath = "na"
        specificPackageFolder="na"
        pythonAbove="3.6"
        author_email='brianbrandonturner@gmail.com'
        author='Brigham Turner'
        description="auto generated package from packageMaker"
        readme="this package was generated by packageMaker using a class as input that contains functions"
        
        inputClass = sayHello # importRelative
    
    baseVersion = "0.2.2"
    
    def remove_leading_whitespace(text):
        # Split the text into lines
        lines = text.split('\n')

        # Find the minimum amount of leading whitespace
        min_whitespace = float('inf')
        for line in lines:
            stripped_line = line.lstrip()
            leading_whitespace = len(line) - len(stripped_line)
            if stripped_line:  # Ignore completely empty lines
                min_whitespace = min(min_whitespace, leading_whitespace)

        # Subtract the minimum amount of leading whitespace from each line
        adjusted_lines = [line[min_whitespace:] for line in lines]

        # Join the lines back into a single string
        adjusted_text = '\n'.join(adjusted_lines)
        
        return adjusted_text
    def getCurrentFilePath():
        code_to_run = remove_leading_whitespace( '''
            import os
            current_file_path_8286433374 = os.path.abspath(__file__)
            ''' )
        
        # print("finding file path")
        def canGoBack( framee ):
            def isIPythonCore( pathh ):
                # c:\Users\brigh\.conda\envs\selenium\Lib\site-packages\IPython\core\interactiveshell.py
                # inspection: c:\Users\brigh\.conda\envs\selenium\Lib\site-packages\IPython\core\interactiveshell.py
                # c:\Users\brigh\.conda\envs\selenium\Lib\site-packages\IPython\core\interactiveshell.py
                return "IPython\core" in pathh  ### ".conda\envs" in pathh or 
        
            frameBehind = framee.f_back
            if frameBehind:
                exec(code_to_run, frameBehind.f_globals, frameBehind.f_locals)
                # print("  - ", frameBehind.f_locals['current_file_path_8286433374'] )
                if isIPythonCore(   frameBehind.f_locals['current_file_path_8286433374']   ):
                    return False
                else:
                    return True            
            else:
                print("   reached end")
                return False
            
        caller_frame = inspect.currentframe()

        while  canGoBack( caller_frame ):
            caller_frame = caller_frame.f_back
        
        exec(code_to_run, caller_frame.f_globals, caller_frame.f_locals)

        return caller_frame.f_locals['current_file_path_8286433374']
    
    #####  getCurrentFilePath()
    
    def getPackageName_and_checkNameUnused():
        def getClassName_PackageName():
            className = inputClass.__name__
                
            if packageName == "na":
                namePackage = className
            else:
                namePackage = packageName #className
            
            return className , namePackage
        className , namePackage = getClassName_PackageName()
        
        def checkifRightAuthor():
            def getPackageAuthor(package_name):
                try:
                    result = subprocess.run(
                        ["pip", "show", package_name],
                        capture_output=True,
                        text=True,
                        check=True
                    )
                    for line in result.stdout.splitlines():
                        if line.startswith("Author:"):
                            return line[len("Author: "):].strip()
                    return "Author not found"
                except subprocess.CalledProcessError:
                    return "Package not found"
            theoreticalAuthor = getPackageAuthor( namePackage )
            assert theoreticalAuthor != "Author not found"
            assert theoreticalAuthor == author or theoreticalAuthor == "Package not found"
        checkifRightAuthor()
        
        return className , namePackage
    className , namePackage = getPackageName_and_checkNameUnused()
    # p#rint("namePackage 2:", namePackage)

    def get_packageFolderSpecific():
            
        def get_packageHousePath():
            if specificPackageFolder != "na":
                houseOfPackages = os.path.dirname( specificPackageFolder )
                return houseOfPackages
            
            elif packageHousePath != "na":
                return packageHousePath
            
            else:
                current_file_path = getCurrentFilePath() # os.path.a#bspath(__file__)
                
                directory_path = os.path.dirname( current_file_path )
                houseOfPackages = directory_path + "/" + "packageHolder"
                
                if not os.path.exists(houseOfPackages):
                    os.makedirs(houseOfPackages)
                    print(f"Directory {houseOfPackages} created.")
                else:
                    print(f"Directory {houseOfPackages} already exists.")
                
                return houseOfPackages
        houseOfPackages = get_packageHousePath()

        def get_specificPackageFolder( houseOfPackages , namePackage ):
            if specificPackageFolder == "na":
                packageFolderSpecific = houseOfPackages + "/" + namePackage
                
                if not os.path.exists(packageFolderSpecific):
                    os.makedirs(packageFolderSpecific)
                    print(f"(2) Directory {packageFolderSpecific} created.")
                else:
                    print(f"(2) Directory {packageFolderSpecific} already exists.")
                
            else:
                packageFolderSpecific = specificPackageFolder
                
            return packageFolderSpecific
        packageFolderSpecific = get_specificPackageFolder( houseOfPackages , namePackage )            
        
        return packageFolderSpecific
    packageFolderSpecific = get_packageFolderSpecific()
    
    def getInfoAboutPackage():
        def getVersion(namePackage , setupLocation):
            def textVersion():
                if not os.path.exists( setupLocation ):
                    return None
    
                with open( setupLocation , 'r') as file:
                    setupText = file.read()
                
                pattern = r'version\s*=\s*([^,]+),'
                matches = re.findall( pattern , setupText )
                
                oldVersion = matches[0]
                loadedVersion = oldVersion[1:len(oldVersion)-1].strip()
                return loadedVersion
            textVersion = textVersion()
            
            def pip_getMaxVersion(namePackage):
                try:
                    # Execute the pip install command to list available versions
                    result = subprocess.run(['pip', 'install', f'{namePackage}==random'], capture_output=True, text=True)
                    
                    # Extract versions using regex
                    versions = re.findall(r'\b\d+\.\d+\.\d+\b', result.stderr)
                    
                    if not versions:
                        return None
                    
                    # Return the maximum version
                    return max(versions, key=lambda v: [int(x) for x in v.split('.')])
                except subprocess.CalledProcessError:
                    return None
            pipVersion = pip_getMaxVersion(namePackage) # "hdslk") #
            
            if textVersion == None and pipVersion == None:
                return baseVersion
            
            def getStartVersion( textVersion, pipVersion ):
                def chooseHigherVersion( textVersion, pipVersion ):
                    split_textVersion = textVersion.split(".")
                    split_pipVersion = pipVersion.split(".")
                
                    assert len(split_textVersion) == 3
                    assert len(split_pipVersion) == 3
                    for iq in range(3):
                        if int(split_textVersion[iq]) > int(split_pipVersion[iq]):
                            return split_textVersion
                        elif int(split_textVersion[iq]) < int(split_pipVersion[iq]):
                            return split_pipVersion
                    return split_textVersion
                
                if textVersion == None:
                    return pipVersion.split(".")
                elif pipVersion == None:
                    return textVersion.split(".")
                else:
                    return chooseHigherVersion( textVersion , pipVersion)
            underVersion = getStartVersion( textVersion, pipVersion )
            lastInt = str(int(underVersion[-1]) + 1)
            thisVersion = underVersion[0] + "." + underVersion[1] + "." + lastInt 
            
            return thisVersion
        setupLocation = packageFolderSpecific + "/setup.py"
        thisVersion = getVersion(namePackage , setupLocation)
        
        def getPackages( inputClass ):
            def get_class_imports( inputClass ): # cls = r#elativeImport
                def get_imports_from_file(file_path):
                    print("searching for files to require:")
                    
                    with open(file_path, "r") as file:
                        tree = ast.parse(file.read(), filename=file_path)
                    
                    allReqs         = []
                    normalImport    = []
                    fromImport      = []
                    
                    for node in ast.walk(tree):
                        if isinstance(node, ast.Import):
                            for alias in node.names:
                                # imports.add(alias.name)
                                allReqs.append( alias.name )
                                normalImport.append( alias.name )
                        elif isinstance(node, ast.ImportFrom):
                            # imports.add(node.module)
                            allReqs.append( node.module )
                            # print("  ", node.module )
                            # for alias in node.names:
                            #   # print("    alias.name:", alias.name)
                            aliasNames = [ alias.name for alias in node.names ]
                            fromImport.append( [ node.module, aliasNames ] )
                            
                    # imports = list( imports ) # return imports
                    return allReqs, normalImport, fromImport

                # Find the file where the class is defined
                class_file_path = inspect.getfile( inputClass )
                if class_file_path.startswith( "<ipython-input" ):
                    class_file_path = getCurrentFilePath() # os.path.abspath(__file__)
        
                # Get the imports from the file
                allReqs, normalImport, fromImport = get_imports_from_file(class_file_path)
                return allReqs, normalImport, fromImport
            allReqs, normalImport, fromImport = get_class_imports( inputClass )
            
            def replacesRequirements(allReqs):
                newReqs = []
                for req in allReqs:
                    for old, new in requirementsReplacement.items():
                        if req == old:
                            req = new
                    
                    if req != "":
                        newReqs.append( req )
                
                return newReqs  
            allReqs = replacesRequirements(allReqs)

            def filterForStandardLibrary( allReqs ):
                def is_standard_library(module_name):
                    print("module_name:" , module_name)
                    try:
                        spec = importlib.util.find_spec(module_name)
                    except:
                        return False
                    
                    if spec is None:
                        return False
                    return 'site-packages' not in spec.origin
                allReqsFiltered = []
                for req in allReqs:
                    if not is_standard_library( req ):
                        # allReqsFiltered.append( req )
                        if "." in req:
                            allReqsFiltered.append( req.split(".")[0] )
                        else:
                            allReqsFiltered.append( req )
                        
                return allReqsFiltered
            allReqsFiltered = filterForStandardLibrary( allReqs )
            
             
            return allReqsFiltered, normalImport, fromImport
        allReqsFiltered, normalImport, fromImport = getPackages( inputClass )
        
        return setupLocation, thisVersion, allReqsFiltered, normalImport, fromImport
    setupLocation, thisVersion, allReqsFiltered, normalImport, fromImport = getInfoAboutPackage()
    

    def dealWithUpperLayerOfPackage():
        def dealWithReadme( packageFolderSpecific ):
            readmeLocation = packageFolderSpecific + "/README.md"
            
            if not os.path.exists( readmeLocation ):
                with open( readmeLocation , 'w') as file:
                    file.write( readme )
        dealWithReadme( packageFolderSpecific )
        
        def saveSetup( namePackage, setupLocation, thisVersion, allReqsFiltered ): # reqs      
            # p#rint("namePackage 3:", namePackage)

            #reqs = [ "sympy" ]
            reqsText = '["' + '","'.join( allReqsFiltered ) + '"]' #reqs
            
            def getSetupText_fromNothing():
                modifiedDescription = "'''" + description + "'''"
                setupText_indented = f'''
                    from setuptools import setup, find_packages
                    with open("README.md", "r") as fh: 
                        long_description = fh.read() 

                    setup(
                        name='{namePackage}',
                        version='{thisVersion}',
                        author='{author}',
                        author_email='{author_email}',
                        description={modifiedDescription},
                        long_description=long_description, 
                        long_description_content_type="text/markdown", 
                        packages=find_packages(),
                        install_requires={reqsText}, 
                        license="MIT",
                        classifiers=[
                        'Programming Language :: Python :: 3',
                        'License :: OSI Approved :: MIT License',
                        'Operating System :: OS Independent',
                        ],
                        python_requires='>={pythonAbove}',
                    )'''
                
                print("should be backspacing setup text please")
                print("before:", setupText_indented)
                setupText = textwrap.dedent(setupText_indented)
                print("after:", setupText)

                return setupText
            
            def getSetupText_fromOldText( setupLocation ):
                with open( setupLocation , 'r') as file:
                    setupText_old = file.read()
                
                pattern = r'version\s*=\s*[^,]+,'
                replacementt = "version='" + thisVersion + "',"
                setupText = re.sub(pattern, replacementt, setupText_old )
                
                # pattern = r'install_requires\s*=\s*[^,]+]\s*,'
                pattern = r'install_requires\s*=\s*\[[^\]]*\]\s*,'
                replacementt = f"install_requires={reqsText},"
                setupText = re.sub(pattern, replacementt, setupText     )
                
                return setupText
            

            if relyOnOldSetupWhenPossible and os.path.exists( setupLocation ):
                setupText = getSetupText_fromOldText( setupLocation )
            else:
                setupText = getSetupText_fromNothing()
            
            # if not os.path.exists( setupLocation ):
            #     setupText = getSetupText_fromNothing()
            # else:
            #     setupText = getSetupText_fromOldText( setupLocation )
                
            with open( setupLocation , 'w') as file:
                file.write( remove_leading_whitespace(setupText) )
        saveSetup( namePackage, setupLocation, thisVersion, allReqsFiltered ) # reqs
        
        def createInnerFolder( packageFolderSpecific , namePackage ):
            innerFolder = packageFolderSpecific + "/" + namePackage
            if not os.path.exists( innerFolder ):
                os.makedirs( innerFolder )
            innerFolder = packageFolderSpecific + "/" + namePackage
            return innerFolder
        innerFolder = createInnerFolder( packageFolderSpecific , namePackage )
        
        return innerFolder
    innerFolder = dealWithUpperLayerOfPackage()
    
    def withLowerLayerOfPackage():
        def createInit( inputClass, innerFolder ):
            initPath = innerFolder + "/__init__.py"
            
            if inspect.ismodule( inputClass ):
                initText = f'''
                    from .{className} import *'''
            else:
                initText = f'''
                    from .{namePackage} import {className}
                    import sys
                    sys.modules[__name__] = {className}'''
            
            with open( initPath , 'w') as file:
                file.write( remove_leading_whitespace(initText) )
        createInit( inputClass, innerFolder )
        
        def saveClass( inputClass, namePackage, normalImport, fromImport ):
            class_code = inspect.getsource( inputClass )
            
            # p#rint("fromImport:", fromImport )
            def getNormalImportText():
                # if len(packagesToImport) > 0:
                #     source_code = "import " + "\nimport ".join( packagesToImport ) + "\n" + class_code
                # else:
                #     source_code = class_code
                
                if len( normalImport ) > 0:
                    normalImportText = "\n" + "import " + "\nimport ".join( normalImport ) + "\n"
                else:
                    normalImportText = ""
                return normalImportText
            normalImportText = getNormalImportText()
            # p#rint( "normalImportText:", normalImportText )
            
            def getFromImportText():
                # fromImport.append( [ node.module, aliasNames ] )
                if len( fromImport ) > 0: # was normalImport (in error I think)
                    listOfLines = []
                    for outerFrom in fromImport: #normalImport:
                        listOfLines.append("from " + outerFrom[0] + " import " + ", ".join( outerFrom[1] ))
                    normalImportText = "\n" + "\n".join( listOfLines ) + "\n"
                    
                else:
                    normalImportText = ""
                return normalImportText
            fromImportText = getFromImportText()
            # p#rint( "fromImportText:",fromImportText )
            
            # if len(packagesToImport) > 0:
            #     source_code = "import " + "\nimport ".join( packagesToImport ) + "\n" + class_code
            # else:
            #     source_code = class_code
            source_code = normalImportText+"\n"+fromImportText+"\n"+class_code
            
            ##################################################
            
            # Save the source code to a file
            file_path = innerFolder + "/" + namePackage + '.py'
            with open(file_path, 'w') as file:
                file.write(source_code)
        saveClass( inputClass, namePackage, normalImport, fromImport )
    withLowerLayerOfPackage()
    
    print("about to install")
    pip.main(['install', packageFolderSpecific ])
    print("just installed")
    
    if publishToPypi == True:
        print("publishing to pypi...")
        def publishToPypi():
            assert pypiApiKey != "unknown"
            
            ###################
            
            os.chdir( packageFolderSpecific ) # Change to the package directory
            dist_dir = os.path.join( packageFolderSpecific , 'dist') # Path to the dist directory where distribution files are stored
            print("about to try to remove dist dir")
            if os.path.exists( dist_dir ):
                print("the line before")
                print( dist_dir )
                shutil.rmtree( dist_dir )
            print("perhaps actually removed it")


            print("building stuff")
            subprocess.check_call([sys.executable, 'setup.py', 'sdist', 'bdist_wheel'])            
            dists = [os.path.join(dist_dir, file) for file in os.listdir(dist_dir) if file.endswith(('.tar.gz', '.whl'))]


            print( "dists:" )
            print( dists )
            print("uploading stuff")
            # upload.main([
            #     '--apikey', pypiApiKey
            # ] + dists)
            upload.main([
                    '--username', '__token__',
                    '--password', pypiApiKey
                ] + dists)
        publishToPypi()
        print("just published to pypi")
        
        predictedURL = "https://pypi.org/project/"+namePackage+"/"+thisVersion+"/"
        
        return predictedURL
        
    ####
