import subprocess
import os
import threading
import time
import signal
import sys
import platform
import shutil

class Cores:
    """Classe para definir cores para saída no terminal."""
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

def mostrar_spinner(mensagem, evento_concluido):
    """
    Mostra um spinner de carregamento na linha de comando.

    Args:
        mensagem (str): A mensagem a ser exibida junto com o spinner.
        evento_concluido (threading.Event): Evento que sinaliza a conclusão do comando.
    """
    spinner = ['|', '/', '-', '\\']
    idx = 0
    while not evento_concluido.is_set():
        print(f"\r{Cores.OKBLUE}{mensagem} {spinner[idx]}{Cores.ENDC}", end='', flush=True)
        idx = (idx + 1) % len(spinner)
        time.sleep(0.1)
    print("\r" + " " * (len(mensagem) + 2), end='\r')

# Variável global para o comando Python
python_cmd = "python"

def check_python_version():
    """
    Verifica a versão do Python instalada no sistema.
    Tenta primeiro o comando 'python3'. Se não encontrado, tenta o comando 'python'.
    Se nenhum dos comandos for encontrado, o script é finalizado.
    """
    global python_cmd
    try:
        subprocess.run("python3 --version", shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        python_cmd = "python3"
        print(f"{Cores.OKGREEN}Comando 'python3' encontrado. A utilizar 'python3' para execução do script.{Cores.ENDC}")
    except subprocess.CalledProcessError:
        print(f"{Cores.WARNING}Comando 'python3' não encontrado. A verificar 'python'...{Cores.ENDC}")
        try:
            subprocess.run("python --version", shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            python_cmd = "python"
            print(f"{Cores.OKGREEN}Comando 'python' encontrado. A utilizar 'python' para execução do script.{Cores.ENDC}")
        except subprocess.CalledProcessError:
            print(f"{Cores.FAIL}Nenhum dos comandos 'python3' ou 'python' encontrados. Instale o Python 3.x para continuar.{Cores.ENDC}")
            sys.exit(1)

def verificar_e_copiar_env():
    """
    Verifica se os ficheiros .env existem nas pastas correspondentes e copia .dev.env para .env se necessário.
    """
    diretorios = [
        os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'Backoffice_Marketplace'),
        os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'BI'),
        os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'docker')
    ]

    for diretorio in diretorios:
        dev_env_path = os.path.join(diretorio, '.dev.env')
        env_path = os.path.join(diretorio, '.env')
        if os.path.exists(dev_env_path) and not os.path.exists(env_path):
            shutil.copyfile(dev_env_path, env_path)
            print(f"{Cores.OKGREEN}Ficheiro {env_path} criado a partir de {dev_env_path}.{Cores.ENDC}")
        else:
            print(f"{Cores.OKBLUE}Ficheiro {env_path} já existe ou {dev_env_path} não encontrado.{Cores.ENDC}")

def run_command_with_progress(command, workdir=None, show_output=True, message="A trabalhar...", color=Cores.OKBLUE):
    """
    Executa um comando com um indicador de progresso.

    Args:
        command (str): O comando a ser executado.
        workdir (str): Diretório de trabalho para o comando.
        show_output (bool): Indica se a saída do comando deve ser exibida.
        message (str): Mensagem a ser exibida junto com o spinner.
        color (str): Cor da mensagem.
    """
    evento_concluido = threading.Event()
    progress_thread = threading.Thread(target=mostrar_spinner, args=(message, evento_concluido))
    progress_thread.start()

    def signal_handler(sig, frame):
        evento_concluido.set()
        print(f"\n{Cores.FAIL}Execução interrompida a pedido do utilizador. A finalizar...{Cores.ENDC}")
        sys.exit(1)

    signal.signal(signal.SIGINT, signal_handler)

    original_workdir = os.getcwd()
    if workdir:
        os.chdir(workdir)

    print(f"\n{Cores.BOLD}A executar: {command}{Cores.ENDC}")
    try:
        result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
        evento_concluido.set()
        progress_thread.join()

        if show_output and result.stdout:
            print(color + result.stdout + Cores.ENDC)
        if result.stderr:
            print(Cores.WARNING + result.stderr + Cores.ENDC)
    except subprocess.CalledProcessError as e:
        evento_concluido.set()
        progress_thread.join()
        print(f"{Cores.FAIL}Erro ao executar o comando: {e.cmd}{Cores.ENDC}")
        print(f"{Cores.WARNING}Saída de erro: {e.stderr}{Cores.ENDC}")
        if "error during connect: this error may indicate that the docker daemon is not running" in e.stderr:
            print(f"{Cores.WARNING}Erro de conexão com o Docker. A execução do script continuará, mas pode haver problemas na operação com contêineres.{Cores.ENDC}")
        else:
            sys.exit(1)
    finally:
        if workdir:
            os.chdir(original_workdir)
        evento_concluido.set()
        progress_thread.join()

def update_hosts():
    """Atualiza o ficheiro de hosts."""
    print(f"\n{Cores.OKGREEN}{Cores.BOLD}[1/5] A atualizar o ficheiro de hosts...{Cores.ENDC}")
    run_command_with_progress(f"{python_cmd} update_hosts.py", show_output=False, message="A atualizar hosts...", color=Cores.OKBLUE)
    print(f"{Cores.OKGREEN}Ficheiro de hosts atualizado com sucesso.{Cores.ENDC}")

def docker_compose_build(docker_path):
    """Constrói os contêineres com Docker Compose sem usar cache."""
    print(f"\n{Cores.OKGREEN}{Cores.BOLD}[3/5] A construir contêineres com Docker Compose sem usar cache...{Cores.ENDC}")
    run_command_with_progress("docker compose build --no-cache", workdir=docker_path, message="A construir contêineres...", color=Cores.OKBLUE)
    print(f"{Cores.OKGREEN}Contêineres construídos com sucesso.{Cores.ENDC}")

def docker_compose_up(docker_path):
    """Inicia os contêineres com Docker Compose."""
    print(f"\n{Cores.OKGREEN}{Cores.BOLD}[4/5] A iniciar contêineres com Docker Compose...{Cores.ENDC}")
    run_command_with_progress("docker compose up -d", workdir=docker_path, message="A iniciar contêineres...", color=Cores.OKBLUE)
    print(f"{Cores.OKGREEN}Contêineres iniciados com sucesso.{Cores.ENDC}")

def run_migrations():
    """Executa as migrações da base de dados."""
    print(f"\n{Cores.OKGREEN}{Cores.BOLD}[5/5] A executar migrações...{Cores.ENDC}")
    run_command_with_progress(f"{python_cmd} run_migrations.py", show_output=True, message="A executar migrações...", color=Cores.OKBLUE)
    print(f"{Cores.OKGREEN}Migrações executadas com sucesso.{Cores.ENDC}")
    
def run_seeders():
    """Executa os seeders da base de dados."""
    print(f"\n{Cores.OKGREEN}{Cores.BOLD}[5/5] A executar seeders...{Cores.ENDC}")
    run_command_with_progress(f"{python_cmd} run_seeders.py", show_output=True, message="A executar seeders...", color=Cores.OKBLUE)
    print(f"{Cores.OKGREEN}Seeders executados com sucesso.{Cores.ENDC}")

def main():
    """Função principal que coordena a execução do script de instalação."""
    check_python_version()
    verificar_e_copiar_env()
    print(f"{Cores.HEADER}Bem-vindo ao script de instalação do Projecto Bairros!{Cores.ENDC}\n")
    docker_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'docker')
    try:
        update_hosts()
        docker_compose_build(docker_path)
        docker_compose_up(docker_path)
        
        # Chamando os scripts update_autoload.py e update_project.py
        run_command_with_progress(f"{python_cmd} update_project.py", show_output=True, message="A atualizar projecto...", color=Cores.OKBLUE)
        run_command_with_progress(f"{python_cmd} update_autoload.py", show_output=True, message="A atualizar autoload...", color=Cores.OKBLUE)
        
        # Executando as migrações
        run_migrations()
        run_seeders()

        print(f"\n{Cores.OKGREEN}Projecto Bairros configurado e iniciado com sucesso!{Cores.ENDC}")
        print("Os ambientes estão disponíveis nos seguintes endereços URL:")
        print(f"{Cores.OKBLUE}- backoffice.coimbra.local{Cores.ENDC}")
        print(f"{Cores.OKBLUE}- bi.coimbra.local{Cores.ENDC}")
        print(f"{Cores.WARNING}Para quaisquer dúvidas, consulte a documentação do projecto.{Cores.ENDC}")
    except Exception as e:
        print(f"{Cores.FAIL}Erro durante a execução do script: {e}{Cores.ENDC}")
        sys.exit(1)

if __name__ == "__main__":
    main()
