import time

from .settings_scene import tryGetMainSceneSettings
from .utils_debug import debug_log
from .utils import getCommand, getOpenDirectoryCommand, getWebProjectPath, isWindows, showMessage
from . import external_process
import os
import sys


__isCompressing = False
__compressionProgress = 0

def drawCompressionProgress(layout, force=False, text=None):
    """
    Draw the compression progress in the layout
    Returns True if the compression is in progress
    """
    if not __isCompressing and not force:
        return False
    progress = getCompressionProgress()
    if text is None:
        text = "Compressing"
    layout.progress(text=text, text_ctxt='', factor=progress)
    return True

def getCompressionProgress():
    return __compressionProgress

def isCompressing():
    return __isCompressing

def runAutoCompress():
    settings = tryGetMainSceneSettings()
    if not settings.compressOnSave:
        debug_log("Auto compress is disabled")
        return False
    projectPath = getWebProjectPath()
    if projectPath is None:
        debug_log("Web project path not found")
        return False
    assetsDir = getAssetsDirectory(projectPath)
    if assetsDir is None:
        debug_log("Assets directory not found")
        return False
    # buildPipelinePath = projectPath + "/node_modules/@needle-tools/gltf-build-pipeline"
    # if not os.path.exists(buildPipelinePath):
    # return False
    cmd = "npx --yes @needle-tools/gltf-build-pipeline@latest transform \"" + assetsDir + "\""
    cmd += " " + external_process.getLogOutputToFileCommand()
    # HACK to prevent server reload during compression...
    lockfile = projectPath + "/needle.lock";
    global __isCompressing, __compressionProgress
    __isCompressing = True
    __compressionProgress = 0
    def __onAutoCompressFinished():
        global __isCompressing
        __isCompressing = False
        if os.path.exists(lockfile):
            os.remove(lockfile)
    if not os.path.exists(lockfile):
        with open(lockfile, "w") as f:
            f.write("lock")
            
    def __onAutoCompressMessage(message):
        global __compressionProgress
        __compressionProgress += (1-__compressionProgress) * 0.05
    external_process.exec_command(cmd, cwd=projectPath, debug=True, onClose=__onAutoCompressFinished, onMessage=__onAutoCompressMessage)


def tryStopOtherServerProcesses():
    webProjectPath = getWebProjectPath()
    return runToolHelperCommand("npm run tool:kill-other-vite-processes -- --directory \"" + webProjectPath + "\"")

def stopProcesses(pattern:str):
    # TODO: this doesnt work if there's no web project
    if runToolHelperCommand("npm run tool:kill-processes -- --pattern \"" + pattern + "\""):
        return False
    # find all processes with the name "npm-cli.js" and "vite" and kill them
    # import psutil
    # for process in psutil.process_iter():
    #     print(process.name)
    # return True

def runToolHelperCommand(cmd:str):
    webProjectPath = getWebProjectPath()
    if webProjectPath is None:
        return False
    pathToPackage = webProjectPath + "/node_modules/@needle-tools/helper"
    if not os.path.exists(pathToPackage):
        debug_log("Can not run tool helper command, package does not exist:", pathToPackage)
        return False
    while webProjectPath.endswith("/") or webProjectPath.endswith("\\"):
        webProjectPath = webProjectPath[:-1]
    debug_log("RUN TOOL HELPER COMMAND", cmd, "at", pathToPackage)
    external_process.exec_command(cmd, cwd=pathToPackage, onMessage=True, onClose=True)


server_process = None
def stopLocalServers():
    global last_local_server_ping_time
    last_local_server_ping_time = 0
    stopProcesses("npm-cli\.js.+start$|[\/\\]vite[\s\.].*--host$") 
    global server_process, server_start_id
    server_start_id = 0
    if server_process is not None:
        print("CLOSE SERVER PROCESS")
        if sys.platform == "win32" or sys.platform == "darwin":
            if getattr(server_process, "kill"):
                server_process.kill()
            elif getattr(server_process, "close"):
                server_process.close()
        else:
            # TODO check if this works on linux
            if getattr(server_process, "kill"):
                server_process.kill()
            elif getattr(server_process, "close"):
                server_process.close()
            elif getattr(server_process, "terminate"):
                server_process.terminate()

        server_process = None
    pass

def serverIsRunning():
    if server_process is not None: return True
    if localServerIsRunning(getWebProjectPath()): return True
    return False

def serverIsStarting():
    global server_is_starting
    return server_is_starting == True and (time.time() - last_start_server_time) < 60

last_start_server_time = 0
last_server_install_time = 0
server_is_starting = False
server_start_id = 0

def getLastStartServerTime():
    global last_start_server_time
    return last_start_server_time

def getLastServerInstallTime():
    global last_server_install_time
    return last_server_install_time

def startLocalServer(projectDirectory):
    global server_process, last_start_server_time, server_is_starting, server_start_id, last_server_install_time


    if not os.path.exists(projectDirectory):
        showMessage("Can not start server, project directory does not exist: " + projectDirectory, "Starting local server", "ERROR")
        print("Can not start server, project directory does not exist: " + projectDirectory)
        return
    
    stopLocalServers()
    
    start_id = server_start_id + 1
    server_start_id = start_id

    npmCmd = getCommand("npm")
    last_start_server_time = time.time()
    last_server_install_time = last_start_server_time
    server_is_starting = True

    # add a slight timeout because of the stop local servers call above
    cmd = f"{getTimeout(2)} && {npmCmd} set registry https://registry.npmjs.org && {npmCmd} install --no-audit --no-fund && {npmCmd} start"
    # the server is now started by a vite plugin
    # openUrlCmd = getOpenUrlCommand("https://localhost:3000")
    # cmd += f" && {openUrlCmd} && {npmCmd} start"
    debug_log("Update & Install: \"" + cmd + "\" at " + projectDirectory)

    # Handles platform-specific differences internally    
    server_process = external_process.exec_command_standalone(cmd, cwd=projectDirectory)
    server_is_starting = False

    # async doesnt work yet:

    # cmd = f"{npmCmd} update && {npmCmd} install"
    # debug_log("Update & Install: \"" + cmd + "\" at " + projectDirectory)
    # server_process = external_process.exec_command(cmd, cwd=projectDirectory, onMessage=__onRunningLocalServerMessage, onClose=True)

    # if server_start_id != start_id and server_start_id != None:
    #     return
    
    # openUrlCmd = getOpenUrlCommand("https://localhost:3000")
    # cmd = f"{openUrlCmd} && {npmCmd} start"
    # debug_log("Start: \"" + cmd + "\" at " + projectDirectory)
    # server_is_starting = False
    # server_process = external_process.exec_command(cmd, cwd=projectDirectory, onMessage=__onRunningLocalServerMessage, onClose=True)
    pass

def __onRunningLocalServerMessage(message):
    debug_log("> " + message)
    global server_is_starting
    # check if message contains localhost or ip address
    if "localhost" in message or "Local: " in message:
        server_is_starting = False
    elif "Error:" in message:
        server_is_starting = False

def getOpenUrlCommand(url):
    if sys.platform == "win32":
        return "start " + url
    elif sys.platform == "darwin":
        return "open " + url
    else:
        return "xdg-open " + url
    
def getTimeout(seconds):
    if sys.platform == "win32":
        return "timeout " + str(seconds)
    else:
        return "sleep " + str(seconds)
    

last_local_server_ping_time = time.time()
last_local_server_ping_result = False

def localServerIsRunning(projectDirectory, force=False):
    if projectDirectory is None:
        return False
    
    global last_local_server_ping_time, last_local_server_ping_result
    if force == False:
        if last_local_server_ping_time != 0 and (time.time() - last_local_server_ping_time) < 4:
            return last_local_server_ping_result

    if not os.path.exists(projectDirectory):
        return False
    
    last_local_server_ping_time = time.time()
    # check if localhost:3000 is reachable
    import socket
    try:
        # TODO: check vite config / configs for port
        # TODO: can we do this check on a background thread to prevent blocking?
        socket.create_connection(("localhost", 3000), timeout=.01)
        last_local_server_ping_result = True
        return True
    except:
        pass
    last_local_server_ping_result = False
    return False
    
def openInBrowser(url):
    openUrlCmd = getOpenUrlCommand(url)
    external_process.exec_command(openUrlCmd)

def runInstall(projectDirectory):
    if not os.path.exists(projectDirectory):
        debug_log("Can not run install, project directory does not exist: " + projectDirectory)
        return
    global last_server_install_time
    last_server_install_time = time.time()
    npmCmd = getCommand("npm")
    cmd = f"{npmCmd} set registry https://registry.npmjs.org && {npmCmd} update && {npmCmd} install --no-audit --no-fund & {getTimeout(5)} & exit 0"
    debug_log("Running command: \"" + cmd + "\" at " + projectDirectory)
    external_process.exec_command_standalone(cmd, cwd=projectDirectory)
    pass

def runBuild(projectDirectory, production:bool, postbuildcmd:str = None, openDirectory:bool = True):
    buildCommand = "build:production" if production else "build:dev"
    log_command = external_process.getLogOutputToFileCommand()
    outputPath = os.path.join(projectDirectory, "dist")
    npmCmd = getCommand("npm")
    
    cmd = f'echo Preparding project... & {npmCmd} install --no-audit --no-fund && echo Building project for the web: Please wait - this can take a moment && npm run {buildCommand} {log_command}'
    
    if postbuildcmd:
        cmd += f" && {postbuildcmd}"
    if openDirectory:
        cmd += f" && {getOpenDirectoryCommand(outputPath)}"
    
    cmd += f" & {getTimeout(2)}"
    
    debug_log(f"Running command: {cmd} at {projectDirectory}")
    proc = external_process.exec_command_standalone(cmd, projectDirectory)    
    return proc

def tryGetConfig(projectDirectory):
    if projectDirectory is None:
        return None
    if not os.path.exists(projectDirectory):
        return None
    configPath = projectDirectory + "/needle.config.json"
    if not os.path.exists(configPath):
        __generateConfig(configPath)
    with open(configPath, "r") as f:
        content = f.read()
        import json
        try:
            return json.loads(content)
        except:
            pass
    return None

def __generateConfig(path:str):
    json_data = {
        "buildDirectory": "dist",
        "assetsDirectory": "assets",
        "scriptsDirectory": "src/scripts",
        "codegenDirectory": "src/codegen"
    }
    import json
    with open(path, "w") as f:
        write = json.dumps(json_data, indent=4)
        f.write(write)
    pass

def getAssetsDirectory(projectDirectory):
    config = tryGetConfig(projectDirectory)
    if config is None:
        return projectDirectory + "/" + "assets"
    if "assetsDirectory" in config:
        return projectDirectory + "/" + config["assetsDirectory"]
    return projectDirectory + "/" + "assets"

def getScriptsDirectory(projectDirectory):
    config = tryGetConfig(projectDirectory)
    if config is None:
        return None
    if "scriptsDirectory" in config:
        return projectDirectory + "/" + config["scriptsDirectory"]
    return None

def getGeneratedDirectory(projectDirectory):
    config = tryGetConfig(projectDirectory)
    if config is None:
        return None
    if "codegenDirectory" in config:
        return projectDirectory + "/" + config["codegenDirectory"]
    return None

def getBuildDirectory(projectDirectory):
    config = tryGetConfig(projectDirectory)
    if config is None:
        return None
    if "buildDirectory" in config:
        return projectDirectory + "/" + config["buildDirectory"]
    return "dist"

def tryOpenWorkspace(projectDirectory, maxUp:int = 3, currentUp:int = 0):
    # check if directory exists
    if not os.path.exists(projectDirectory):
        return False
    # find files with .code-workspace extension
    files = os.listdir(projectDirectory)
    for file in files:
        if file.endswith(".code-workspace"):
            # open workspace
            if isWindows():
                os.startfile(projectDirectory + "/" + file)
            else:
                os.system("code " + projectDirectory + "/" + file)
            return True
    # check if we can go up
    if currentUp >= maxUp:
        return False
    # go up
    return tryOpenWorkspace(os.path.dirname(projectDirectory), maxUp, currentUp + 1)
        
    
def tryGetPackageVersion(projectDirectory:str, packageName:str, default:str):
    path = projectDirectory + "/node_modules/" + packageName + "/package.json"
    if not os.path.exists(path):
        return default
    try:
        import json
        with open(path, "r") as f:
            content = f.read()
            data = json.loads(content)
            if "version" in data:
                return data["version"]
    except:
        pass
    return default

