# -*- coding: utf-8 -*-
"""
BANCO DE PRUEBAS AISLADAS V5.2.1 — TEITAN RESCUE FINALIZER
Ubicacion: E:\\TEITAN_RESCUE_OUTPUT\\TOOLS\\test_finalizer_suite_v521.py

Valida física y estrictamente las garantías de seguridad y pruebas negativas de V5.2.1:
1. TEST_FAKE_RECORD_OUTSIDE_STAGING_BLOCKS_BEFORE_READ:
   - staged_path del registro ficticio apunta fuera de staging / hacia C: -> preflight BLOQUEA
     inmediatamente ANTES de inspeccionar tamaño, existencia o SHA-256.
2. TEST_AR88_DISAPPEARANCE_DURING_STREAMING_ABORT_AND_CLEANUP:
   - Archivo AR88 desaparece o sufre error I/O durante el streaming en run_finalize() ->
     ABORTA limpiamente, CIERRA descriptores y ELIMINA el ZIP temporal único de esa ejecución.
3. TEST_HISTORICAL_ZIP_TEMPS_PRESERVED_ON_FAILURE:
   - Temporales históricos preexistentes no son borrados ni afectados ante fallos en una ejecución.
4. TEST_CONCURRENT_MASTER_ZIP_BLOCKS_WITHOUT_OVERWRITE:
   - Aparición concurrente de teitan.zip antes de la publicación -> BLOQUEA y NO sobrescribe.
5. TEST_EXCLUSIVE_REPORT_CREATION_PROTECTS_EXISTING:
   - Creación de FINAL_MASTER_REPORT.json usa open 'x'; no sobrescribe reporte preexistente.
6. TEST_CORE_MEMBER_SIZE_MISMATCH_BLOCKS_PREFLIGHT:
   - Discrepancia entre ZipInfo.file_size del núcleo y la columna 'bytes' de SQLite -> BLOQUEA preflight.
7. TEST_AR88_DUPLICATE_OR_CORE_COLLISION_BLOCKS_PREFLIGHT:
   - Arcnames duplicados en AR88 o en colisión con el núcleo -> BLOQUEA preflight.
8. TEST_ZIP64_STREAMING_INTEGRITY_AND_BUFFERING:
   - Verificación de streaming con bloques de 4MB y estructuras ZIP64 válidas.
9. TEST_PREFLIGHT_READONLY_GUARANTEE:
   - Modo preflight no modifica archivos ni base de datos.
10. TEST_E2E_SANDBOX_RUN:
    - Ejecución integral en sandbox de preflight, construcción, verificación CRC y publicación.
"""

import os
import sys
import shutil
import zipfile
import hashlib
import tempfile
import time
import uuid
import sqlite3

# Importamos directamente la herramienta V5.2.1 desde Kingston E:\
sys.path.insert(0, r"E:\TEITAN_RESCUE_OUTPUT\TOOLS")
import finalize_master_v521 as finalizer

SANDBOX_BASE = r"E:\TEITAN_RESCUE_OUTPUT\TOOLS\SANDBOX_TEST_TMP_V521"

def ensure_sandbox_dir():
    os.makedirs(SANDBOX_BASE, exist_ok=True)

def setup_mock_environment(tmpdir):
    """Crea un entorno mock con SQLite, core.zip y archivos staged."""
    stage_root = os.path.join(tmpdir, "STAGED_COPIES")
    os.makedirs(stage_root, exist_ok=True)
    db_path = os.path.join(tmpdir, "test_checkpoint.sqlite")
    core_zip_path = os.path.join(tmpdir, "teitan_core.zip")
    master_zip_path = os.path.join(tmpdir, "teitan.zip")
    final_report_path = os.path.join(tmpdir, "FINAL_MASTER_REPORT.json")

    # 1. Crear 1 archivo de core en staging (que actuará como gitignore legítimo con exactamente 733 bytes)
    git_file = os.path.join(stage_root, "xampp", "htdocs", "Digital Optimus", ".gitignore")
    os.makedirs(os.path.dirname(git_file), exist_ok=True)
    git_payload = (b"A" * 733)
    assert len(git_payload) == 733
    with open(git_file, "wb") as f: f.write(git_payload)
    git_sha = hashlib.sha256(git_payload).hexdigest().upper()

    # 2. Crear teitan_core.zip con ese archivo
    with zipfile.ZipFile(core_zip_path, "w", allowZip64=True) as zf:
        zf.writestr("xampp/htdocs/Digital Optimus/.gitignore", git_payload)

    # 3. Crear 1 archivo de AR88 en staging
    ar88_f1 = os.path.join(stage_root, "Users", "intel", "AR88_Sandbox", "lab.vhdx")
    os.makedirs(os.path.dirname(ar88_f1), exist_ok=True)
    with open(ar88_f1, "wb") as f: f.write(b"AR88 VHDX DUMMY CONTENT FOR TESTING V521")
    ar88_f1_sha = hashlib.sha256(b"AR88 VHDX DUMMY CONTENT FOR TESTING V521").hexdigest().upper()

    # 4. Crear DB de checkpoint
    conn = sqlite3.connect(db_path)
    cur = conn.cursor()
    cur.execute("""
        CREATE TABLE file_checkpoints (
            norm_source TEXT PRIMARY KEY,
            original_source TEXT NOT NULL,
            staged_path TEXT NOT NULL,
            bytes INTEGER NOT NULL,
            sha256 TEXT NOT NULL,
            status TEXT NOT NULL,
            timestamp REAL NOT NULL
        )
    """)
    # Insertar el gitignore legítimo
    cur.execute("INSERT INTO file_checkpoints VALUES (?, ?, ?, ?, ?, ?, ?)",
                (finalizer.EXACT_COLLIDING_NORM_SOURCE, "C:\\xampp\\htdocs\\Digital Optimus\\.gitignore", git_file, 733, git_sha, "VERIFIED", time.time()))
    # Insertar el fake colisionando exactamente sobre git_file
    cur.execute("INSERT INTO file_checkpoints VALUES (?, ?, ?, ?, ?, ?, ?)",
                (finalizer.EXACT_FAKE_NORM_SOURCE, "C:\\DISCO_ROTO_NO_EXISTE_FAKE.dat", git_file, 733, git_sha, "VERIFIED", time.time()))
    # Insertar AR88
    cur.execute("INSERT INTO file_checkpoints VALUES (?, ?, ?, ?, ?, ?, ?)",
                ("c:\\users\\intel\\ar88_sandbox\\lab.vhdx", "C:\\Users\\intel\\AR88_Sandbox\\lab.vhdx", ar88_f1, len(b"AR88 VHDX DUMMY CONTENT FOR TESTING V521"), ar88_f1_sha, "VERIFIED", time.time()))
    conn.commit()
    conn.close()

    return {
        "tmpdir": tmpdir,
        "stage_root": stage_root,
        "db_path": db_path,
        "core_zip_path": core_zip_path,
        "master_zip_path": master_zip_path,
        "final_report_path": final_report_path,
        "git_file": git_file,
        "git_sha": git_sha,
        "ar88_f1": ar88_f1,
        "ar88_f1_sha": ar88_f1_sha
    }

def test_1_fake_record_outside_staging_blocks_before_read():
    """Garantia 1: Fake record apuntando fuera de staging falla en aislamiento antes de cualquier lectura."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        # Modificar fake_record para que staged_path apunte a C:\Windows\notepad.exe (que existe)
        conn = sqlite3.connect(env["db_path"])
        cur = conn.cursor()
        cur.execute("UPDATE file_checkpoints SET staged_path = 'C:\\Windows\\notepad.exe' WHERE norm_source = ?", (finalizer.EXACT_FAKE_NORM_SOURCE,))
        conn.commit()
        conn.close()

        res = finalizer.run_preflight(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                      target_drive=tmpdir, stage_root=env["stage_root"],
                                      master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        assert res is False, "Preflight no bloqueo un staged_path del fake record que apuntaba fuera de staging"
    return "PASS"

def test_2_ar88_streaming_io_failure_abort_and_cleanup():
    """Garantia 2: Error durante el streaming de AR88 aborta y limpia el temporal generado."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        
        # Eliminar el archivo AR88 después de que setup lo creó pero antes de run_finalize
        # Para simular fallo directo durante run_finalize (con preflight inyectado o mock)
        # En este test, corrompemos el hash en disco para que falle durante la lectura en vuelo
        with open(env["ar88_f1"], "wb") as f:
            f.write(b"CORRUPTED BYTES IN AR88 STREAM")
        
        # Debe fallar en finalize y no dejar temporales
        failed = False
        try:
            finalizer.run_finalize(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                   target_drive=tmpdir, stage_root=env["stage_root"],
                                   master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        except SystemExit as se:
            if se.code != 0:
                failed = True
        except Exception:
            failed = True

        assert failed is True, "run_finalize debio abortar ante discrepancia de hash en streaming"
        assert not os.path.exists(env["master_zip_path"]), "teitan.zip no debio ser publicado"

        # Verificar que no quedo ningun temporal activo con prefijo finalizing_
        leftover_temps = [f for f in os.listdir(tmpdir) if "teitan.zip.finalizing_" in f]
        assert len(leftover_temps) == 0, f"Quedaron temporales no limpiados: {leftover_temps}"
    return "PASS"

def test_3_historical_zip_temps_preserved_on_failure():
    """Garantia 2: Temporales historicos preexistentes se preservan intactos ante fallos."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        
        # Crear un temporal historico preexistente
        historical_temp = os.path.join(tmpdir, "teitan.zip.tmp_historical_fake.zip")
        with open(historical_temp, "wb") as f:
            f.write(b"HISTORICAL TEMP CONTENT TO PRESERVE")

        # Forzar un error en finalize
        with open(env["ar88_f1"], "wb") as f:
            f.write(b"INVALID DATA")

        try:
            finalizer.run_finalize(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                   target_drive=tmpdir, stage_root=env["stage_root"],
                                   master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        except SystemExit:
            pass

        assert os.path.exists(historical_temp), "El temporal historico fue eliminado indebidamente"
        assert open(historical_temp, "rb").read() == b"HISTORICAL TEMP CONTENT TO PRESERVE"
    return "PASS"

def test_4_concurrent_master_zip_blocks_without_overwrite():
    """Garantia 3: Preflight y finalize rechazan sobrescribir teitan.zip preexistente."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        with open(env["master_zip_path"], "wb") as f:
            f.write(b"EXISTING TEITAN ZIP PREVENT OVERWRITE")

        res = finalizer.run_preflight(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                      target_drive=tmpdir, stage_root=env["stage_root"],
                                      master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        assert res is False, "Preflight no bloqueo ante teitan.zip preexistente"
        assert open(env["master_zip_path"], "rb").read() == b"EXISTING TEITAN ZIP PREVENT OVERWRITE"
    return "PASS"

def test_5_exclusive_report_creation_protects_existing():
    """Garantia 3: FINAL_MASTER_REPORT.json preexistente bloquea preflight."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        with open(env["final_report_path"], "wb") as f:
            f.write(b'{"EXISTING": true}')

        res = finalizer.run_preflight(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                      target_drive=tmpdir, stage_root=env["stage_root"],
                                      master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        assert res is False, "Preflight no bloqueo ante reporte preexistente"
    return "PASS"

def test_6_core_member_size_mismatch_blocks_preflight():
    """Garantia 4: Discrepancia entre ZipInfo.file_size y SQLite bloquea preflight."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        # Modificar tamano en SQLite para simular discrepancia de miembro del nucleo
        conn = sqlite3.connect(env["db_path"])
        cur = conn.cursor()
        cur.execute("UPDATE file_checkpoints SET bytes = 9999 WHERE norm_source = ?", (finalizer.EXACT_COLLIDING_NORM_SOURCE,))
        conn.commit()
        conn.close()

        res = finalizer.run_preflight(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                      target_drive=tmpdir, stage_root=env["stage_root"],
                                      master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        assert res is False, "Preflight no detecto discrepancia en tamano descomprimido del nucleo"
    return "PASS"

def test_7_ar88_duplicate_or_core_collision_blocks_preflight():
    """Garantia 4: Arcnames duplicados en AR88 o en colision con el nucleo bloquean preflight."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        
        # Insertar un registro en AR88 cuyo arcname colisione con el nucleo
        # El archivo en nucleo es xampp/htdocs/Digital Optimus/.gitignore
        conn = sqlite3.connect(env["db_path"])
        cur = conn.cursor()
        cur.execute("INSERT INTO file_checkpoints VALUES (?, ?, ?, ?, ?, ?, ?)",
                    ("c:\\users\\intel\\ar88_sandbox\\colliding.txt", "C:\\xampp\\htdocs\\Digital Optimus\\.gitignore", env["ar88_f1"], len(b"AR88 VHDX DUMMY CONTENT FOR TESTING V521"), env["ar88_f1_sha"], "VERIFIED", time.time()))
        conn.commit()
        conn.close()

        res = finalizer.run_preflight(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                      target_drive=tmpdir, stage_root=env["stage_root"],
                                      master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        assert res is False, "Preflight no detecto colision entre AR88 y el nucleo"
    return "PASS"

def test_8_zip64_streaming_integrity():
    """Garantia 4: Validacion de estructura de streaming ZIP64 en bloques de 4MB."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        sample_file = os.path.join(tmpdir, "stream_test.dat")
        chunk_pattern = b"A" * (4 * 1024 * 1024)
        with open(sample_file, "wb") as f:
            for _ in range(3):
                f.write(chunk_pattern)
        
        expected_sha = hashlib.sha256(open(sample_file, "rb").read()).hexdigest().upper()
        out_zip = os.path.join(tmpdir, "out_zip64.zip")
        zinfo = zipfile.ZipInfo(filename="stream_test.dat", date_time=(2026, 10, 29, 12, 0, 0))
        zinfo.compress_type = zipfile.ZIP_DEFLATED

        calc_sha = hashlib.sha256()
        with zipfile.ZipFile(out_zip, "w", allowZip64=True) as zf:
            with open(sample_file, "rb") as fi, zf.open(zinfo, "w", force_zip64=True) as fo:
                while chunk := fi.read(4 * 1024 * 1024):
                    calc_sha.update(chunk)
                    fo.write(chunk)

        assert calc_sha.hexdigest().upper() == expected_sha
        with zipfile.ZipFile(out_zip, "r") as zf_read:
            assert zf_read.testzip() is None
            info = zf_read.getinfo("stream_test.dat")
            assert info.file_size == 12 * 1024 * 1024
    return "PASS"

def test_9_preflight_readonly_guarantee():
    """Garantia de solo lectura en produccion."""
    core_sha_before = hashlib.sha256(open(finalizer.CORE_ZIP_PATH, "rb").read(1024*1024)).hexdigest()
    finalizer.run_preflight()
    core_sha_after = hashlib.sha256(open(finalizer.CORE_ZIP_PATH, "rb").read(1024*1024)).hexdigest()
    assert core_sha_before == core_sha_after, "Preflight modifico el nucleo"
    return "PASS"

def test_10_e2e_sandbox_run():
    """Ejecucion completa E2E en sandbox aislada."""
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        ok_pref = finalizer.run_preflight(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                          target_drive=tmpdir, stage_root=env["stage_root"],
                                          master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        assert ok_pref is True, "Preflight en sandbox debio ser PASS"

        try:
            finalizer.run_finalize(db_path=env["db_path"], core_zip_path=env["core_zip_path"],
                                   target_drive=tmpdir, stage_root=env["stage_root"],
                                   master_zip_path=env["master_zip_path"], final_report_path=env["final_report_path"])
        except SystemExit as e:
            assert e.code == 0, f"Finalize retorno codigo de error: {e.code}"

        assert os.path.exists(env["master_zip_path"]), "teitan.zip no fue generado en sandbox"
        assert zipfile.ZipFile(env["master_zip_path"], "r").testzip() is None
        assert os.path.exists(env["final_report_path"]), "Reporte final no fue generado"
    return "PASS"

def run_suite():
    ensure_sandbox_dir()
    tests = [
        ("1. Aislamiento estricto: Fake record fuera de staging bloquea ANTES de leer", test_1_fake_record_outside_staging_blocks_before_read),
        ("2. Limpieza segura: Error en streaming de AR88 aborta y elimina temporal único", test_2_ar88_streaming_io_failure_abort_and_cleanup),
        ("3. Limpieza segura: Temporales históricos preexistentes se preservan intactos", test_3_historical_zip_temps_preserved_on_failure),
        ("4. No-sobrescritura: Bloqueo ante aparición concurrente de teitan.zip", test_4_concurrent_master_zip_blocks_without_overwrite),
        ("5. Reporte seguro: Creación exclusiva (open 'x') previene sobrescritura", test_5_exclusive_report_creation_protects_existing),
        ("6. Cobertura: Discrepancia en tamaño descomprimido de miembro del núcleo bloquea", test_6_core_member_size_mismatch_blocks_preflight),
        ("7. Cobertura: Arcnames duplicados en AR88 o colisión con el núcleo bloquean", test_7_ar88_duplicate_or_core_collision_blocks_preflight),
        ("8. Streaming ZIP64: Integridad de chunks de 4MB y estructuras ZIP64", test_8_zip64_streaming_integrity),
        ("9. Solo lectura: Garantía de no-modificación de fuentes durante --preflight", test_9_preflight_readonly_guarantee),
        ("10. Sandbox E2E: Ejecución integral aislada de preflight, finalize, CRC y reporte", test_10_e2e_sandbox_run)
    ]

    print("=" * 72)
    print("  EJECUTANDO BANCO DE PRUEBAS AISLADAS V5.2.1 — CIERRE DE GARANTIAS")
    print("=" * 72)

    all_pass = True
    for desc, func in tests:
        try:
            res = func()
            print(f"  [{res}] {desc}")
        except Exception as e:
            print(f"  [FAIL] {desc} -> {e}")
            all_pass = False

    print("=" * 72)
    print(f"RESULTADO: {'TODAS LAS PRUEBAS SUPERADAS (PASS)' if all_pass else 'FALLAS DETECTADAS'}")
    
    shutil.rmtree(SANDBOX_BASE, ignore_errors=True)
    return all_pass

if __name__ == "__main__":
    ok = run_suite()
    sys.exit(0 if ok else 1)

