import os
import sys
import subprocess
import time
from pathlib import Path
from rich.console import Console
from glob import glob
# Import menus do TUI
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from tui import menu_python, menu_iso, menu_libs # pylint: disable=wrong-import-position, import-error

console = Console()

def corrigir_kernel_headers(linux_dir, env):
    """
    Corrige headers incompatíveis com C23 em vários caminhos.
    Adiciona guards para typedef bool / enum { false, true }.
    """
    console.print("[yellow]→ Patching kernel headers for C23 compatibility...[/]")

    header_candidates = [
        "include/linux/stddef.h",
        "include/linux/types.h",
        "include/uapi/linux/stddef.h",
        "include/uapi/linux/types.h",
        "arch/x86/include/asm/stddef.h",
        "arch/x86/include/asm/types.h",
    ]

    # Antes de aplicar os patches:
    safe_run(["make", "prepare"], env=env)
    safe_run(["make", "scripts"], env=env)

    for relpath in header_candidates:
        path = linux_dir / relpath
        if not path.exists():
            continue

        content = path.read_text()
        patched = False

        # Corrige enum false/true
        if "false" in content and "__bool_true_false_are_defined" not in content:
            content = content.replace(
                "enum {\n        false   = 0,\n        true    = 1\n};",
                "#ifndef __bool_true_false_are_defined\n"
                "enum {\n        false   = 0,\n        true    = 1\n};\n"
                "#define __bool_true_false_are_defined 1\n#endif"
            )
            patched = True

        # Corrige typedef _Bool
        if "typedef _Bool" in content and "__STDC_VERSION__" not in content:
            content = content.replace(
                "typedef _Bool                   bool;",
                "#ifndef __cplusplus\n"
                "# if __STDC_VERSION__ < 202000L\n"
                "typedef _Bool bool;\n"
                "# endif\n"
                "#endif"
            )
            patched = True

        if patched:
            path.write_text(content)
            console.print(f"[green]✔ Patched:[/] {relpath}")

    # Corrige Makefile do boot
    boot_makefile = linux_dir / "arch/x86/boot/Makefile"
    if boot_makefile.exists():
        with open(boot_makefile, "a") as f:
            f.write("\n# C23 fix\nccflags-y += -std=gnu11 -Wno-error\n")
        console.print("[cyan]→ Applied local Makefile override for boot code (C23 fix)[/]")

    # Corrige Makefile do boot/compressed
    boot_compr_makefile = linux_dir / "arch/x86/boot/compressed/Makefile"
    if boot_compr_makefile.exists():
        with open(boot_compr_makefile, "a") as f:
            f.write("\n# C23 fix\nccflags-y += -std=gnu11 -Wno-error\n")
        console.print("[cyan]→ Applied local Makefile override for boot/compressed code (C23 fix)[/]")

    # Corrige Makefile do EFI stub
    efi_makefile = linux_dir / "drivers/firmware/efi/libstub/Makefile"
    if efi_makefile.exists():
        with open(efi_makefile, "a") as f:
            f.write("\n# C23 fix\nccflags-y += -std=gnu11 -Wno-error\n")
        console.print("[cyan]→ Applied local Makefile override for EFI stub (C23 fix)[/]")

def corrigir_kernel_overrides(cross_compile):
    env = {
        **os.environ,
        "ARCH": "x86_64",
        "CROSS_COMPILE": cross_compile,
        "LOCALVERSION": "-nfdos",
    }

    console.print("[yellow]→ Aplicando overrides de configuração...[/yellow]")

    # Essenciais do sistema base
    essentials = [
        "CONFIG_PRINTK",
        "CONFIG_TTY",
        "CONFIG_SERIAL_8250",
        "CONFIG_SERIAL_8250_CONSOLE",
        "CONFIG_SERIAL_EARLYCON",
        "CONFIG_DEVTMPFS",
        "CONFIG_DEVTMPFS_MOUNT",
        "CONFIG_BLK_DEV_INITRD",
        "CONFIG_TMPFS",
        "CONFIG_PROC_FS",
        "CONFIG_SYSFS",
        # EXT4 base + alias para ext2
        "CONFIG_EXT4_FS",
        "CONFIG_EXT4_USE_FOR_EXT2",
        "CONFIG_MCORE2",
        #"CONFIG_EXT4_FS_POSIX_ACL",
        #"CONFIG_EXT4_FS_SECURITY",
    ]

    # VirtIO (para bloco, rede, PCI)
    virtio = [
        "CONFIG_PCI",
        "CONFIG_VIRTIO",
        "CONFIG_VIRTIO_MENU",
        "CONFIG_VIRTIO_PCI",
        "CONFIG_VIRTIO_PCI_LEGACY",
        "CONFIG_VIRTIO_BLK",
        "CONFIG_VIRTIO_NET",
        "CONFIG_VIRTIO_CONSOLE",
        "CONFIG_VIRTIO_INPUT",
        "CONFIG_VIRTIO_MMIO",
        "CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES",
        "CONFIG_BLK_MQ_VIRTIO",
        "CONFIG_BLOCK",
        "CONFIG_BLK_DEV",
        "CONFIG_BLOCK_LEGACY_AUTOLOAD",
        "CONFIG_EXPORTFS_BLOCK_OPS",
        "CONFIG_MSDOS_PARTITION",  # assegura parsing de tabela de partições
    ]

    # Debug e early printk
    debug = [
        "CONFIG_DEBUG_KERNEL",
        "CONFIG_EARLY_PRINTK",
        "CONFIG_SERIAL_8250_PNP",
        "CONFIG_DEBUG_INFO_NONE",  # evita symbols extras
    ]

    # Desativa gráficos e Garante compatibilidade máxima de CPU (para manter o kernel leve, limpo e evitar CR4 panics)
    disable_graphics = [
        "CONFIG_VT",
        "CONFIG_VT_CONSOLE",
        "CONFIG_VGA_CONSOLE",
        "CONFIG_FRAMEBUFFER_CONSOLE",
        "CONFIG_DUMMY_CONSOLE",
        "CONFIG_FB",
        "CONFIG_DRM",
        "CONFIG_DRM_I915",
        "CONFIG_LOGO",
        "CONFIG_X86_PAE",
        "CONFIG_X86_5LEVEL",
        "CONFIG_RANDOMIZE_BASE",
        "CONFIG_RETPOLINE",
        "CONFIG_SMAP",
        "CONFIG_SMEP",
        "CONFIG_PAGE_TABLE_ISOLATION",
    ]

    # Habilita todos os conjuntos
    for opt in essentials + virtio + debug:
        safe_run(["scripts/config", "--enable", opt], env=env)

    # garante que o driver não é módulo
    for opt in [
        "CONFIG_VIRTIO_BLK=m",
        "CONFIG_VIRTIO_PCI=m",
        "CONFIG_VIRTIO_PCI_LEGACY=m",
    ]:
        safe_run(["scripts/config", "--disable", opt], env=env)

    # Desativa o que não precisamos
    for opt in disable_graphics:
        safe_run(["scripts/config", "--disable", opt], env=env)

    # Formatos binários básicos
    for opt in [
        "CONFIG_BINFMT_ELF",
        "CONFIG_BINFMT_SCRIPT",
    ]:
        safe_run(["scripts/config", "--enable", opt], env=env)

    # Regera configuração coerente
    safe_run(["make", "olddefconfig"], env=env)

    console.print("[green]✔ Overrides aplicados com sucesso.[/green]")


def safe_run(cmd, **kwargs):
    """Executa um comando e mostra logs legíveis no TUI."""
    console.print(f"[dim]{' '.join(cmd) if isinstance(cmd, list) else cmd}[/dim]")
    subprocess.run(cmd, check=True, **kwargs)

def limpar_rootfs(nfdos_dir):
    rootfs = nfdos_dir / "rootfs"
    initramfs = nfdos_dir / "initramfs.cpio.gz"
    if rootfs.exists():
        console.print("[yellow]🧹 Limpando RootFS anterior...[/yellow]")
        safe_run(f"rm -rf {rootfs}", shell=True)
    if initramfs.exists():
        console.print("[yellow]🧹 Removendo initramfs anterior...[/yellow]")
        safe_run(f"rm -f {initramfs}", shell=True)

def run():
    while True:
        console.clear()
        console.rule("[bold yellow]Kernel / BusyBox / Python[/bold yellow]")
        console.print("1. Obter código-fonte do Kernel Linux")
        console.print("2. Compilar Kernel com Toolchain NFDOS")
        console.print("3. Obter e compilar BusyBox")
        console.print("4. Compilar Python estatico com Toolchain NFDOS")
        console.print("5. Obter e compilar Bibliotecas do Neurotron")
        console.print("6. Montar RootFS e gerar imagem bootável")
        console.print("7. Gerar imagem ISO e iniciar o NFDOS no QEMU")
        console.print("0. Voltar")

        choice = console.input("\n[cyan]nfdos> [/cyan]")

        base_dir = Path(__file__).resolve().parents[1]
        kernel_dir = base_dir / "_nfdos" / "kernel"
        linux_dir = kernel_dir / "linux"
        nfdos_dir = base_dir / "_nfdos"
        busybox_dir = nfdos_dir / "busybox"
        rootfs_dir = nfdos_dir / "rootfs"
        toolchain_prefix = Path.home() / "x-tools" / "x86_64-nfdos-linux-musl"
        cross_compile = f"{toolchain_prefix}/bin/x86_64-nfdos-linux-musl-"

        linux_repo = "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git"
        busybox_repo = "git://busybox.net/busybox.git"

        env = {
            **os.environ,		
            "ARCH": "x86_64",
            "CROSS_COMPILE": cross_compile,
            "CFLAGS": "-std=gnu11 -fcommon -Wno-error",
            "LOCALVERSION": "-nfdos",
            "KCFLAGS": "-std=gnu11 -fcommon -Wno-error",
            "HOSTCFLAGS": "-std=gnu11 -fcommon -Wno-error",
            "KBUILD_CFLAGS": "-std=gnu11 -fcommon -Wno-error",
            "KBUILD_AFLAGS": "-std=gnu11 -fcommon",
            "KBUILD_CPPFLAGS": "-std=gnu11 -fcommon",
            "KCFLAGS_FOR_HOST": "-std=gnu11 -Wno-error",
        }

        if choice == "1":
            kernel_dir.mkdir(parents=True, exist_ok=True)
            os.chdir(kernel_dir)
            console.print("[yellow]Baixando código-fonte do Kernel Linux...[/yellow]")
            if not linux_dir.exists():
                safe_run(["git", "clone", "--depth=1", "--branch", "v6.12", linux_repo, "linux"])
            else:
                os.chdir(linux_dir)
                safe_run(["git", "pull"])
            console.print("[green]✔ Kernel atualizado![/green]")

        elif choice == "2":
            os.chdir(linux_dir)
            console.print("[cyan]Compilando Kernel Linux...[/cyan]")

            # Verificação do compilador
            if not Path(cross_compile + "gcc").exists():
                console.print("[red]✗ Toolchain não encontrada![/red]")
                console.print(f"Esperado: {cross_compile}gcc")
                time.sleep(2)
                continue

            # Passo 1 — Limpar tudo antes de começar
            safe_run(["make", "mrproper"], env=env)

            # Passo 2 — Baixar configuração base mínima
            safe_run(["make", "allnoconfig"], env=env)

            # Passo 3 — Aplicar patches C23 aos headers
            corrigir_kernel_headers(linux_dir, env)

            # Passo 4 — Aplicar overrides minimalistas (serial-only, sem VGA)
            corrigir_kernel_overrides(cross_compile)

            # Passo 5 — Regerar dependências e .config
            safe_run(["make", "olddefconfig"], env=env)

            # Passo 6 — Compilar
            safe_run(["make", "-j", str(os.cpu_count())], env=env)

            console.print("[green]✔ Kernel compilado com sucesso![/green]")

        elif choice == "3":
            busybox_dir.mkdir(parents=True, exist_ok=True)
            os.chdir(busybox_dir)
            console.print("[yellow]📦 Baixando e compilando BusyBox...[/yellow]")

            if not (busybox_dir / ".git").exists():
                safe_run(["git", "clone", "--depth=1", busybox_repo, "-b", "master", "."], env=env)

            # 🧹 Limpeza preventiva se houver restos de builds anteriores
            if (busybox_dir / ".config").exists():
                console.print("[cyan]🧼 Limpando build antigo (make mrproper)...[/cyan]")
                safe_run(["make", "mrproper"], env=env)

            console.print("[cyan]🧩 Gerando configuração base (defconfig)...[/cyan]")
            safe_run(["make", "defconfig"], env=env)

            console.print("[cyan]🧠 Aplicando overrides de configuração (persistência + EXT4)...[/cyan]")

            config_updates = {
                "CONFIG_STATIC": "y",
                "CONFIG_MKFS": "y",
                "CONFIG_FEATURE_MKFS_EXT2": "y",
                "CONFIG_FEATURE_MKFS_EXT3": "y",
                "CONFIG_FEATURE_MKFS_EXT4": "y",
                "CONFIG_MOUNT": "y",
                "CONFIG_UMOUNT": "y",
                "CONFIG_BLKID": "y",
                "CONFIG_LSBLK": "y",
                "CONFIG_FSCK": "y",
                "CONFIG_FEATURE_MOUNT_HELPERS": "y",
                "CONFIG_FEATURE_MOUNT_FSTAB": "y",
                "CONFIG_FEATURE_VOLUMEID": "y",
                "CONFIG_FEATURE_VOLUMEID_EXT": "y",
                "CONFIG_FEATURE_VOLUMEID_FAT": "y",
                "CONFIG_FEATURE_VOLUMEID_EXFAT": "y",
                "CONFIG_FEATURE_VOLUMEID_NTFS": "y",
                "CONFIG_FEATURE_VOLUMEID_LINUXSWAP": "y",
                "CONFIG_FEATURE_VOLUMEID_BTRFS": "y",
                "CONFIG_FEATURE_VOLUMEID_XFS": "y",
                "CONFIG_FEATURE_VOLUMEID_ISO9660": "y",
            }

            for key, value in config_updates.items():
                safe_run([
                    "sed", "-i",
                    f"s/^# {key} is not set/{key}={value}/; t; s/^{key}=.*/{key}={value}/; t; $a\\{key}={value}",
                    ".config"
                ], env=env)

            # 🧠 Shell e utilitários básicos
            safe_run(["sed", "-i", "s/^CONFIG_TC=.*/# CONFIG_TC is not set/", ".config"], env=env)
            safe_run(["sed", "-i", "s/^# CONFIG_ASH is not set/CONFIG_ASH=y/", ".config"], env=env)
            safe_run(["sed", "-i", "s/^# CONFIG_ASH_OPTIMIZE_FOR_SIZE is not set/CONFIG_ASH_OPTIMIZE_FOR_SIZE=y/", ".config"], env=env)
            safe_run(["sed", "-i", "s/^# CONFIG_FEATURE_SH_STANDALONE is not set/CONFIG_FEATURE_SH_STANDALONE=y/", ".config"], env=env)

            console.print("[yellow]🔧 Regerando configuração coerente (make oldconfig)...[/yellow]")
            safe_run(["make", "oldconfig"], env=env)

            console.print("[yellow]🔨 Compilando BusyBox (estático, com EXT4 e ferramentas de disco)...[/yellow]")
            safe_run(["make", "-j", str(os.cpu_count())], env=env)
            safe_run(["make", "install"], env=env)

            console.print("[green]✔ BusyBox compilado e instalado com sucesso![/green]")


        elif choice == "4":
            menu_python.run()

        elif choice == "5":
            menu_libs.run()

        elif choice == "6":
            os.chdir(nfdos_dir)
            console.print("[cyan]Montando RootFS e gerando imagem bootável...[/cyan]")

            limpar_rootfs(nfdos_dir)

            rootfs_dir = nfdos_dir / "rootfs"
            rootfs_dir.mkdir(exist_ok=True)

            # Estrutura de diretórios básica
            for d in ["bin", "sbin", "etc", "proc", "sys", "usr/bin", "usr/sbin", "opt/kernel"]:
                (rootfs_dir / d).mkdir(parents=True, exist_ok=True)

            # Garante os device nodes no initramfs
            safe_run(f"mkdir -p {rootfs_dir}/dev", shell=True)
            safe_run(f"sudo mknod -m 600 {rootfs_dir}/dev/console c 5 1", shell=True)
            safe_run(f"sudo mknod -m 666 {rootfs_dir}/dev/null c 1 3", shell=True)
            safe_run(f"sudo mknod -m 600 {rootfs_dir}/dev/ttyS0 c 4 64", shell=True)
            safe_run(f"sudo mknod -m 666 {rootfs_dir}/dev/random c 1 8", shell=True)
            safe_run(f"sudo mknod -m 666 {rootfs_dir}/dev/urandom c 1 9", shell=True)

            # Copiar o BusyBox instalado para o rootfs
            console.print("[blue]🔗 Copiando BusyBox para o rootfs...[/blue]")
            safe_run(f"cp -a {busybox_dir}/_install/* {rootfs_dir}/", shell=True)
            safe_run(["chmod", "-R", "755", str(rootfs_dir / "bin")])
            console.print("[green]✔ BusyBox incluído no rootfs![/green]")

            # Inserir a Lib Python (TODO: No NFDOS V0.2 build “fully frozen”)
            safe_run(f"mkdir -p {rootfs_dir}/usr/lib/python3.13", shell=True)
            safe_run(f"rsync -av --exclude='test' --exclude='idlelib' --exclude='tkinter' \
                {nfdos_dir}/cpython/Lib/ {rootfs_dir}/usr/lib/python3.13/", shell=True)
            
            # --- Copiar binário Python estático para o RootFS ---
            python_bin = nfdos_dir / "python"
            usr_bin = rootfs_dir / "usr" / "bin"
            usr_bin.mkdir(parents=True, exist_ok=True)

            if python_bin.exists():
                console.print("[yellow]→ Adicionando Python estático ao RootFS...[/yellow]")
                safe_run(f"cp {python_bin} {usr_bin}/python3", shell=True)
            else:
                console.print("[red]⚠ Python estático não encontrado em nfdos/python — compilar antes.[/red]")

            # Inserir Neurotron (se existir)
            opt_kernel = rootfs_dir / "opt" / "kernel"
            opt_kernel.mkdir(parents=True, exist_ok=True)

            # Integrar o Neurotron completo (núcleo + main)
            neurotron_dir = kernel_dir / "neurotron"

            if neurotron_dir.exists():
                console.print("[yellow]→ Integrando Neurotron no sistema...[/yellow]")
                safe_run(f"cp -r {neurotron_dir} {opt_kernel}/", shell=True)
            else:
                console.print("[red]⚠ Neurotron não encontrado em kernel/ — prosseguindo sem mente digital.[/red]")


            # Instalar libs externas do Neurotron (se existirem)
            libs_dir = nfdos_dir / "libs"
            site_packages = rootfs_dir / "usr/lib/python3.13/site-packages"
            site_packages.mkdir(parents=True, exist_ok=True)

            lib_files = []
            for ext in ("*.whl", "*.tar.gz", "*.zip", "*.tgz"):
                lib_files.extend(glob(f"{libs_dir}/{ext}"))

            if lib_files:
                libs_str = " ".join(lib_files)
                console.print("[cyan]→ Instalando bibliotecas externas do Neurotron...[/cyan]")
                safe_run(
                    f"pip install --no-deps --target={site_packages} {libs_str}",
                    shell=True
                )
            else:
                console.print("[yellow]⚠️ Nenhuma biblioteca externa encontrada em libs/.[/yellow]")


            # Script init com chamada ao Neurotron
            init_file = rootfs_dir / "init"
            initramfs_file = nfdos_dir / "initramfs.cpio.gz"

            safe_run(f"cd {rootfs_dir} && cp ../init .", shell=True) # TODO: usar f.write para automatizar o init
            safe_run(["chmod", "+x", str(init_file)])

            # Gerar initramfs
            console.print("[blue]Gerando initramfs.cpio.gz...[/blue]")
            if initramfs_file.exists():
                safe_run(f"rm {initramfs_file}", shell=True)
            safe_run(f"cd {rootfs_dir} && find . | cpio -H newc -o | gzip > {initramfs_file}", shell=True)
            console.print("[green]✔ RootFS criado e compactado![/green]")

            # 🧠 Validação do init antes de gerar o initramfs
            console.print("[blue]🔍 Verificando integridade do init...[/blue]")

            # Garante que o init existe
            if not init_file.exists():
                console.print(f"[red]✗ Ficheiro init não encontrado em {init_file}[/red]")
                raise SystemExit(1)

            # Mostra onde ele está dentro do initramfs
            safe_run(f'cd {nfdos_dir} && lsinitramfs initramfs.cpio.gz | grep -E "(^|/)init$"', shell=True)

            # Mostra tipo e encoding
            console.print("[cyan]→ Verificando tipo de ficheiro...[/cyan]")
            safe_run(f'file {init_file}', shell=True)

            # Mostra primeiras linhas (para confirmar shebang)
            console.print("[cyan]→ Conteúdo inicial do init (máx 10 linhas)...[/cyan]")
            safe_run(f'head -n 10 {init_file}', shell=True)

            # Sanitização: remove BOM e CRLF
            console.print("[blue]→ Removendo BOM e CRLF (normalização universal)...[/blue]")
            safe_run(f"sed -i '1s/^\\xEF\\xBB\\xBF//' {init_file}", shell=True)
            safe_run(f"sed -i 's/\\r$//' {init_file}", shell=True)

            # Confirma permissões
            console.print("[cyan]→ Ajustando permissões de execução...[/cyan]")
            safe_run(f'chmod 0755 {init_file}', shell=True)

            # Segunda verificação do tipo
            console.print("[green]✔ Ficheiro final após normalização:[/green]")
            safe_run(f'file {init_file}', shell=True)

            # Validação do shebang e encoding
            console.print("[blue]→ Testando assinatura e line endings...[/blue]")
            safe_run(f"head -n1 {init_file} | od -c", shell=True)
            safe_run(f"grep -Ua 'bin/sh' {init_file} | head -n1", shell=True)

            console.print("[green]✔ init validado e pronto para inclusão no initramfs[/green]")

            # Criar disco (se não existir)
            data_disk = nfdos_dir / "nfdos_data.img"
            if not data_disk.exists():
                console.print("[cyan]💽 Criando disco persistente (hipocampo físico)...[/cyan]")
                safe_run(f"dd if=/dev/zero of={data_disk} bs=1M count=512", shell=True)
            else:
                console.print("[green]✔ Disco persistente já existe.[/green]")


            time.sleep(5)


            # Testar no QEMU
            bz_image = linux_dir / "arch" / "x86" / "boot" / "bzImage"
            data_disk = nfdos_dir / "nfdos_data.img"

            if bz_image.exists():
                console.print("\n[bold blue]Iniciando QEMU (modo kernel direto)...[/bold blue]")

                # 🔒 Pergunta ao utilizador se deve permitir formatação automática do disco
                console.print(
                    "\n[yellow]O Neurotron detetará o disco físico e poderá formatá-lo se estiver vazio.[/yellow]"
                )
                console.print(
                    "[bright_yellow]Deseja permitir formatação automática do disco?[/bright_yellow] "
                    "([green]y[/green]/[red]N[/red]): ",
                    end=""
                )
                choice = input().strip().lower()

                allow_format = choice in ("y", "yes", "sim", "s")

                # 🧠 Monta a linha base do QEMU
                kernel_params = (
                    "console=ttyS0 earlyprintk=serial,ttyS0,115200 "
                    "keep_bootcon loglevel=8"
                )

                # 🧩 Injeta flag de formatação se permitido
                if allow_format:
                    kernel_params += " nfdos_force_format=1"
                    console.print("[green]✔ Formatação automática permitida neste boot.[/green]")
                else:
                    console.print("[cyan]ℹ Formatação automática desativada (modo seguro).[/cyan]")

                qemu_cmd = (
                    f"qemu-system-x86_64 "
                    f"-machine q35,accel=kvm "
                    f"-cpu qemu64 "
                    f"-kernel {bz_image} "
                    f"-initrd {nfdos_dir}/initramfs.cpio.gz "
                    f"-append '{kernel_params}' "
                    f"-drive file={data_disk},if=virtio,format=raw "
                    f"-m 1024 "
                    f"-nographic "
                    f"-no-reboot"
                )

                # 🚀 Executa o QEMU
                safe_run(qemu_cmd, shell=True)

            else:
                console.print("[red]✗ bzImage não encontrado! Compile o kernel primeiro.[/red]")


        elif choice == "7":
            menu_iso.run()

        elif choice == "0":
            break
        else:
            console.print("[red]Opção inválida![/red]")

        time.sleep(1)

