import sys
import os
import requests
import tempfile
import subprocess
import shutil
import winreg
import re
from lib.echo import echo
from lib.obter_configuracoes import obter_configuracoes
import psutil    

def buscar_executaveis_usando_dll(nome):
    nome_dll = f"{nome}.dll"
    executaveis = []

    for process in psutil.process_iter():
        try:
            process_name = process.name()
            process_id = process.pid

            for dll in process.memory_maps():
                if nome_dll.lower() in dll.path.lower():
                    executaveis.append((process_id, process_name))
                    
                    
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
            pass
        except AttributeError:
            pass

    return executaveis   


def obter_paths(versao, diretorio_scripts_implantacao):

    return {
        'PATH_DLL_SRVCUSTO': r'c:\windows\srvcusto\srvcusto.dll',
        'PATH_DLL_SRVCUSTO_ZIP_FTP': f'http://download.microuniverso.com.br/TradSql/Utilitarios/SRVCUSTO/srvcusto{versao}.zip',
        '7Z_PATH': os.path.join(diretorio_scripts_implantacao,"7z.exe")
    }

def baixar_e_copiar(versao, senha, diretorio_scripts_implantacao):
    paths = obter_paths(versao, diretorio_scripts_implantacao)

    if not os.path.exists(os.path.dirname(paths['PATH_DLL_SRVCUSTO'])):
        os.makedirs(os.path.dirname(paths['PATH_DLL_SRVCUSTO']))

    echo(f"baixando {paths['PATH_DLL_SRVCUSTO_ZIP_FTP']}")
    response = requests.get(paths['PATH_DLL_SRVCUSTO_ZIP_FTP'])
    response.raise_for_status()  

    
    arquivo_zip_temporario = tempfile.NamedTemporaryFile(delete=False, suffix=".zip").name
    diretorio_temporario = tempfile.TemporaryDirectory().name
    os.makedirs(diretorio_temporario)

    with open(arquivo_zip_temporario, 'wb') as f:
        f.write(response.content)

    cmd_unzip = [paths['7Z_PATH'], "x", arquivo_zip_temporario, f"-p{senha}", f"-o{diretorio_temporario}"]

    echo(f"descompactando. comando: " + " ".join(cmd_unzip))

    subprocess.run(cmd_unzip, check=True)

    echo(f"copiando {os.path.join(diretorio_temporario, 'srvcusto.dll')} para {paths['PATH_DLL_SRVCUSTO']}")

    if os.path.exists(paths['PATH_DLL_SRVCUSTO']):
        os.remove(paths['PATH_DLL_SRVCUSTO'])

    shutil.copy(os.path.join(diretorio_temporario,  f'srvcusto{versao}.dll'), paths['PATH_DLL_SRVCUSTO'])

    echo(f"concluido")

def buscar_locais_de_registro(nome):
    comando = rf'reg query HKLM\SOFTWARE\Classes /s /f {nome}.dll'

    resultado = subprocess.run(comando, shell=True, capture_output=True)

    saida = resultado.stdout.decode('windows-1252')

    matches = re.finditer(r'[cdefghijklmnopqrstuvwxyz]:.*' + nome+'.dll', saida, re.IGNORECASE)

    enderecos_registros = []

    for match in matches:
        endereco_dll = match.group().lower()

        if not endereco_dll in enderecos_registros:
            enderecos_registros.append(endereco_dll)

    return enderecos_registros

def obter_enderecos_clsids(nome):
    comando = rf'reg query HKLM\SOFTWARE\Classes /s /f {nome}.dll'

    resultado = subprocess.run(comando, shell=True, capture_output=True)

    saida = resultado.stdout.decode('windows-1252')

    matches = re.finditer(r'HKEY.*', saida, re.IGNORECASE)

    enderecos_clsids = []

    for match in matches:
        endereco_clsid = match.group()
        endereco_clsid = endereco_clsid[0:endereco_clsid.find('}') + 1]
        enderecos_clsids.append(endereco_clsid)

    return enderecos_clsids


def desregistrar_dll_pelo_endereco(endereco_dll):
    comando = f'regsvr32 /s /u "{endereco_dll}"'

    echo(f'desregistrando {endereco_dll}')

    resultado = subprocess.run(comando, shell=True, capture_output=True)

    if resultado.returncode != 0 and resultado.returncode != 3221225477:
        echo(f'erro ao desregistrar {endereco_dll}: {resultado.stderr.decode("utf-8")}')

def remover_todos_clsid_do_registro(nome):
    enderecos_clsids = obter_enderecos_clsids(nome)

    for endereco_clsid in enderecos_clsids:
        forcar_remocao_clsid_do_registro(endereco_clsid)

def forcar_remocao_clsid_do_registro(endereco_clsid):

    def delete_sub_key(key_handle, sub_key):
        with winreg.OpenKey(key_handle, sub_key, access=winreg.KEY_ALL_ACCESS) as key:
            # Recursively delete subkeys
            while True:
                try:
                    sub_key_name = winreg.EnumKey(key, 0)
                    delete_sub_key(key, sub_key_name)
                except OSError:
                    break  # No more subkeys, break the loop

        # Now that all subkeys are deleted, delete the main key
        winreg.DeleteKey(key_handle, sub_key)


    echo(f'removendo chave do registro {endereco_clsid}')

    key_handle_informado = endereco_clsid[0:endereco_clsid.find('\\')]
    key_handle =    winreg.HKEY_LOCAL_MACHINE if key_handle_informado == 'HKEY_LOCAL_MACHINE' else \
                    winreg.HKEY_CLASSES_ROOT if key_handle_informado == 'HKEY_CLASSES_ROOT' else \
                    winreg.HKEY_CURRENT_USER if key_handle_informado == 'HKEY_CURRENT_USER' else \
                    winreg.HKEY_USERS if key_handle_informado == 'HKEY_USERS' else \
                    winreg.HKEY_CURRENT_CONFIG
    
    sub_key_informado = endereco_clsid[endereco_clsid.find('\\')+1:]

    delete_sub_key(key_handle, sub_key_informado)


def desregistrar_dll(nome):
    locais_registro = buscar_locais_de_registro(nome)

    if len(locais_registro) == 0:
        echo(f"{nome} nao registrada")
        return
    
    locais_registro_str = "\n".join(locais_registro)
    
    echo(f"locais de registro de {nome}: {locais_registro_str}")

    for local_dll in locais_registro:
        desregistrar_dll_pelo_endereco(local_dll)

    locais_registro = buscar_locais_de_registro(nome)

    if len(locais_registro) > 0:
        locais_registro_str = "\n".join(locais_registro)
        echo(f"{nome} ainda registrada em {locais_registro_str}")

        remover_todos_clsid_do_registro(nome)

        locais_registro = buscar_locais_de_registro(nome)

        if len(locais_registro) > 0:
            locais_registro_str = "\n".join(locais_registro)
            echo(f"{nome} ainda registrada em {locais_registro_str}")
            raise Exception(f"{nome} ainda registrada em {locais_registro_str}")
        else:
            echo(f"{nome} removida com sucesso")

def registrar_dll(endereco):

    

    echo(f'registrando {endereco}')

    comando = f'regsvr32 /s "{endereco}"'

    resultado = subprocess.run(comando, shell=True, capture_output=True)

    if resultado.returncode != 0 and resultado.returncode != 3221225477:
        echo(f'erro ao registrar {endereco}: {resultado.stderr.decode("utf-8")}')
    else:
        echo(f'{endereco} registrado com sucesso')


def atualizar_srvcusto(versao, senha, diretorio_scripts_implantacao):
    
    executaveis_usando_dll = buscar_executaveis_usando_dll("srvcusto")

    if len(executaveis_usando_dll) > 0:
        msg = "existem executaveis usando a dll srvcusto. impossivel continuar\n"
        msg += "(PID, NOME)\n"
        msg += ', '.join([f"({t[0]}, '{t[1]}')" for t in executaveis_usando_dll])

        echo(msg, "error")
        
        exit(1)
    

    paths = obter_paths(versao, diretorio_scripts_implantacao)

    baixar_e_copiar(versao,senha, diretorio_scripts_implantacao)

    desregistrar_dll("srvcusto")

    registrar_dll(
        endereco=paths['PATH_DLL_SRVCUSTO']
    )



if __name__ == '__main__':

    arquivo_configuracao = None if len(sys.argv) < 2 else sys.argv[1]
    diretorio_scripts_implantacao = r'c:\microuniversoWeb' if len(sys.argv) < 3 else sys.argv[2]

    configuracoes = obter_configuracoes(arquivo_configuracao)

    if not configuracoes:
        echo(f"Falha ao carregar configuracoes do arquivo {arquivo_configuracao}" , "error")
        exit(1)

    versao = configuracoes["muDllRest"]["versaoSrvcusto"]

    echo(f"atualizando srvcusto para versao {versao}")

    atualizar_srvcusto(
        versao=versao,
        senha="PADRAOMU",
        diretorio_scripts_implantacao=diretorio_scripts_implantacao
    )

    

    