import os
import sys
import time
import zipfile
import hashlib
import json

def get_target_volume():
    import string
    import ctypes
    kernel32 = ctypes.windll.kernel32
    volumeNameBuffer = ctypes.create_unicode_buffer(1024)
    fileSystemNameBuffer = ctypes.create_unicode_buffer(1024)
    serial_number = ctypes.c_ulong()
    max_component_length = ctypes.c_ulong()
    file_system_flags = ctypes.c_ulong()

    for letter in string.ascii_uppercase:
        drive = f"{letter}:\\"
        try:
            res = kernel32.GetVolumeInformationW(
                ctypes.c_wchar_p(drive),
                volumeNameBuffer,
                ctypes.sizeof(volumeNameBuffer),
                ctypes.byref(serial_number),
                ctypes.byref(max_component_length),
                ctypes.byref(file_system_flags),
                fileSystemNameBuffer,
                ctypes.sizeof(fileSystemNameBuffer)
            )
            if res and volumeNameBuffer.value == "TEITAN_RESCATE":
                return drive
        except Exception:
            pass
    return None

def calculate_sha256(filepath):
    sha = hashlib.sha256()
    with open(filepath, "rb") as f:
        while chunk := f.read(4 * 1024 * 1024):
            sha.update(chunk)
    return sha.hexdigest().upper()

def get_core_files():
    core_dirs = [
        r"C:\xampp\htdocs\Digital Optimus",
        r"C:\Users\intel\AppData\Local\OptimusRuntime",
        r"C:\Users\intel\.gemini\antigravity",
        r"C:\xampp\mysql\data"
    ]
    external_files = [
        r"C:\Users\intel\Downloads\DIGITAL_OPTIMUS_NORTE_MAESTRO.md.txt",
        r"C:\Users\intel\Downloads\LEEME_INSTRUCCIONES_MIGRACION.md",
        r"C:\Users\intel\Downloads\ORC_CIERRE_OPERATIVO_V1_1_0_2026-10-28.md",
        r"C:\Users\intel\Downloads\Digital_Optimus_Complete_Project.zip.sha256",
        r"C:\Users\intel\Desktop\AR72_CIRCUITO_AUTORREPARACION.txt",
        r"C:\Users\intel\Desktop\LICENSE"
    ]
    
    # Also collect external ZIP evidences from Desktop & Downloads
    desktop_dir = r"C:\Users\intel\Desktop"
    downloads_dir = r"C:\Users\intel\Downloads"

    collected = []
    
    for cdir in core_dirs:
        if os.path.exists(cdir):
            for root, dirs, files in os.walk(cdir):
                for f in files:
                    full = os.path.join(root, f)
                    collected.append(full)

    for ef in external_files:
        if os.path.exists(ef):
            collected.append(ef)

    if os.path.exists(desktop_dir):
        for f in os.listdir(desktop_dir):
            if f.endswith((".zip", ".txt", ".md", ".json")):
                full = os.path.join(desktop_dir, f)
                if full not in collected:
                    collected.append(full)

    if os.path.exists(downloads_dir):
        for f in os.listdir(downloads_dir):
            if f.startswith("ORC") or f.startswith("AR") or f.startswith("TEITAN_AR"):
                full = os.path.join(downloads_dir, f)
                if os.path.isfile(full) and full not in collected:
                    collected.append(full)

    return sorted(list(set(collected)))

def get_all_master_files():
    # Core files + AR88_Sandbox + any others
    files = get_core_files()
    ar88_dir = r"C:\Users\intel\AR88_Sandbox"
    if os.path.exists(ar88_dir):
        for root, dirs, f_list in os.walk(ar88_dir):
            for f in f_list:
                full = os.path.join(root, f)
                # Do not include the backup engine script itself or generated temp archives if any
                if not f.endswith(".tmp_zip"):
                    files.append(full)
    return sorted(list(set(files)))

def create_zip_archive(target_zip_final, file_list, description):
    temp_zip = target_zip_final + ".tmp_zip"
    if os.path.exists(temp_zip):
        os.remove(temp_zip)
    if os.path.exists(target_zip_final):
        os.remove(target_zip_final)

    print(f"\n--- INICIANDO {description} ---")
    print(f"Destino Temporal: {temp_zip}")
    print(f"Destino Final:    {target_zip_final}")
    print(f"Total Archivos:   {len(file_list)}")

    total_bytes = 0
    missing = []
    start_time = time.time()
    manifest = []

    with zipfile.ZipFile(temp_zip, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True, compresslevel=6) as zf:
        count = 0
        for fpath in file_list:
            if not os.path.exists(fpath):
                missing.append(fpath)
                continue
            try:
                fsize = os.path.getsize(fpath)
                # Store relative to drive or preserve full path without drive letter
                # e.g. "C:/xampp/..." -> "xampp/..."
                norm = os.path.normpath(fpath)
                drive, rel = os.path.splitdrive(norm)
                arcname = rel.lstrip("\\/")
                
                zf.write(fpath, arcname=arcname)
                total_bytes += fsize
                count += 1
                manifest.append({"path": fpath, "arcname": arcname, "bytes": fsize})

                if count % 2000 == 0 or fsize > 100 * 1024 * 1024:
                    elapsed = time.time() - start_time
                    mb_s = (total_bytes / (1024*1024)) / elapsed if elapsed > 0 else 0
                    print(f"  Progreso: {count}/{len(file_list)} archivos ({total_bytes / (1024**3):.2f} GB leidos, {mb_s:.1f} MB/s)...")
            except Exception as e:
                print(f"[ERROR LECTURA] {fpath}: {e}")
                missing.append(f"{fpath} (Error: {e})")

    elapsed = time.time() - start_time
    print(f"Compresion finalizada en {elapsed:.1f}s. Guardando manifiesto...")
    manifest_path = target_zip_final + ".manifest.json"
    with open(manifest_path, "w", encoding="utf-8") as mf:
        json.dump({"manifest": manifest, "missing": missing, "total_files": count, "uncompressed_bytes": total_bytes}, mf, indent=2)

    # Renaming temp to final
    os.rename(temp_zip, target_zip_final)
    final_size = os.path.getsize(target_zip_final)
    print(f"[OK] Archivo renombrado a: {target_zip_final}")
    print(f"Tamano final: {final_size} bytes ({final_size / (1024**3):.2f} GiB)")

    # Integrity check
    print("Verificando integridad ZIP (CRC test)...")
    with zipfile.ZipFile(target_zip_final, 'r') as zf:
        first_bad = zf.testzip()
        if first_bad is not None:
            raise RuntimeError(f"ERROR INTEGRIDAD: Archivo corrupto en ZIP: {first_bad}")
    print("[PASS] Test de integridad CRC superado con 100% de exito.")

    print("Calculando SHA-256 definitivo...")
    zip_hash = calculate_sha256(target_zip_final)
    print(f"SHA-256: {zip_hash}")

    return {
        "path": target_zip_final,
        "files_included": count,
        "uncompressed_bytes": total_bytes,
        "compressed_bytes": final_size,
        "compressed_gb": round(final_size / (1024**3), 3),
        "missing": missing,
        "sha256": zip_hash,
        "integrity": "PASS"
    }

def run_backup():
    target_drive = get_target_volume()
    if not target_drive:
        print("[BLOQUEO] No se detecto unidad TEITAN_RESCATE.")
        sys.exit(1)

    backup_dir = os.path.join(target_drive, "TEITAN_BACKUP")
    os.makedirs(backup_dir, exist_ok=True)

    # 1. Core backup first
    core_files = get_core_files()
    core_zip = os.path.join(backup_dir, "teitan_core.zip")
    core_result = create_zip_archive(core_zip, core_files, "RESPALDO NUCLEO (teitan_core.zip)")

    # 2. Master full backup
    master_files = get_all_master_files()
    master_zip = os.path.join(backup_dir, "teitan.zip")
    master_result = create_zip_archive(master_zip, master_files, "RESPALDO MAESTRO COMPLETO (teitan.zip)")

    print("\n" + "=" * 60)
    print("  RESULTADOS CONSOLIDADOS DEL RESCATE")
    print("=" * 60)
    print(json.dumps({"CORE": core_result, "MASTER": master_result}, indent=2))

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "run":
        run_backup()
    else:
        print("Motor cargado correctamente. Para ejecutar usar: python teitan_backup_engine.py run")
