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

Valida física y estrictamente las 15 condiciones y pruebas negativas reales requeridas por el usuario:
1. TEST_AR88_HASH_MISMATCH_BLOCK: Archivo de laboratorio con tamaño correcto pero SHA-256 alterado -> BLOQUEO OBLIGATORIO en streaming.
2. TEST_STAGED_PATH_OUTSIDE_KINGSTON_BLOCK: staged_path dirigido fuera de staging / fuera de Kingston -> BLOQUEO antes de leer.
3. TEST_CORE_ZIP_HASH_MISMATCH_BLOCK: teitan_core.zip con hash alterado -> BLOQUEO en preflight.
4. TEST_UNAUTHORIZED_EXTRA_ENTRY_BLOCK: Entrada adicional no autorizada en ZIP o SQLite -> BLOQUEO en preflight.
5. TEST_STAGED_COLLISION_DIFFERENT_HASHES_BLOCK: Dos registros con mismo staged_path y hashes diferentes -> BLOQUEO en preflight.
6. TEST_EXACT_FAKE_RECORD_RESOLUTION: Detección y resolución exacta de 'c:\\disco_roto_no_existe_fake.dat'.
7. TEST_PARTIALLY_SIMILAR_FAKE_NOT_EXCLUDED: Registro parecido (ej. 'c:\\disco_roto_no_existe_fake2.dat') NO se excluye y provoca fallo si no existe.
8. TEST_EXISTING_MASTER_ZIP_PROTECTED: teitan.zip preexistente bloquea la finalización sin modificarse ni renombrarse.
9. TEST_EXISTING_FINAL_REPORT_PROTECTED: FINAL_MASTER_REPORT.json preexistente bloquea la finalización sin sobrescribirse.
10. TEST_ZIP_DATE_SANITIZATION: Fechas <1980, >2107 o incompatibles se sustituyen de forma segura exclusivamente en ZIP.
11. TEST_LARGE_VHDX_STREAMING: Streaming ZIP64 de archivos en bloques de 4MB sin cargar todo en memoria.
12. TEST_FAILURE_DURING_PACKAGING_NO_PUBLISH: Fallo durante el empaquetado no publica teitan.zip y limpia el temporal.
13. TEST_E2E_SANDBOX_RUN: Ejecución E2E completa en sandbox aislada: preflight, empaquetado, streaming hash, CRC y publicación.
14. TEST_PREFLIGHT_READONLY_GUARANTEE: El modo --preflight no ejecuta transferencias ni modifica fuentes.
15. TEST_FINALIZE_NO_CODE_FROM_TEAMGROUP: El código de V5.2 no invoca inventario de C: ni transferencias desde C:.
"""

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

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

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

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")
    ar88_f1_sha = hashlib.sha256(b"AR88 VHDX DUMMY CONTENT").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"), 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,
        "ar88_f1": ar88_f1,
        "ar88_f1_sha": ar88_f1_sha
    }

def test_1_ar88_hash_mismatch_block():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        with open(env["ar88_f1"], "wb") as f: f.write(b"AR88 VHDX DUMMY CORRUPT")
        
        blocked = 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 (RuntimeError, SystemExit):
            blocked = True
        assert blocked is True, "No se bloqueó un archivo AR88 con hash alterado"
        assert not os.path.exists(env["master_zip_path"]), "El ZIP maestro fue publicado a pesar del hash alterado"
    return "PASS"

def test_2_staged_path_outside_kingston_block():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        stage_root = os.path.join(tmpdir, "STAGED")
        os.makedirs(stage_root, exist_ok=True)

        bad_path_c = r"C:\Windows\System32\drivers\etc\hosts"
        ok_c, _ = finalizer.validate_staged_path_isolation(bad_path_c, allowed_stage_root=stage_root)
        assert ok_c is False, "No se rechazó una ruta apuntando a C:"

        bad_traversal = os.path.join(stage_root, "..", "outside.txt")
        ok_trav, _ = finalizer.validate_staged_path_isolation(bad_traversal, allowed_stage_root=stage_root)
        assert ok_trav is False, "No se rechazó una ruta con directory traversal"
    return "PASS"

def test_3_core_zip_hash_mismatch_block():
    actual = finalizer.EXPECTED_CORE_SHA256
    try:
        finalizer.EXPECTED_CORE_SHA256 = "FAKE_HASH_00000000000000000000000000000000000000000000000000000000"
        res = finalizer.run_preflight()
        assert res is False, "No se bloqueó preflight ante un hash de núcleo alterado"
    finally:
        finalizer.EXPECTED_CORE_SHA256 = actual
    return "PASS"

def test_4_unauthorized_extra_entry_block():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        conn = sqlite3.connect(env["db_path"])
        cur = conn.cursor()
        cur.execute("INSERT INTO file_checkpoints VALUES (?, ?, ?, ?, ?, ?, ?)",
                    ("c:\\extra_malicious.txt", "C:\\extra_malicious.txt", env["ar88_f1"], 10, "HASH", "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, "No se bloqueó ante una entrada no autorizada en SQLite"
    return "PASS"

def test_5_staged_collision_different_hashes_block():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        conn = sqlite3.connect(env["db_path"])
        cur = conn.cursor()
        cur.execute("UPDATE file_checkpoints SET sha256 = 'HASH_DIFFERENT' 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, "No se bloqueó una colisión con hashes diferentes"
    return "PASS"

def test_6_exact_fake_record_resolution():
    assert finalizer.EXACT_FAKE_NORM_SOURCE == "c:\\disco_roto_no_existe_fake.dat"
    assert finalizer.EXACT_COLLIDING_NORM_SOURCE == "c:\\xampp\\htdocs\\digital optimus\\.gitignore"
    return "PASS"

def test_7_partially_similar_fake_not_excluded():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        conn = sqlite3.connect(env["db_path"])
        cur = conn.cursor()
        cur.execute("INSERT INTO file_checkpoints VALUES (?, ?, ?, ?, ?, ?, ?)",
                    ("c:\\disco_roto_no_existe_fake2.dat", "C:\\disco_roto_no_existe_fake2.dat", env["ar88_f1"], 10, "HASH", "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, "Un registro parcialmente parecido fue indebidamente excluido o no detectado"
    return "PASS"

def test_8_existing_master_zip_protected():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        original_content = b"ORIGINAL PRE-EXISTING MASTER ZIP CONTENT"
        with open(env["master_zip_path"], "wb") as f: f.write(original_content)
        orig_hash = hashlib.sha256(original_content).hexdigest()

        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 bloqueó ante un teitan.zip preexistente"
        assert open(env["master_zip_path"], "rb").read() == original_content
        assert hashlib.sha256(open(env["master_zip_path"], "rb").read()).hexdigest() == orig_hash
    return "PASS"

def test_9_existing_final_report_protected():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        report_content = b'{"REPORT": "PRE-EXISTING"}'
        with open(env["final_report_path"], "wb") as f: f.write(report_content)

        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 bloqueó ante un reporte final preexistente"
        assert open(env["final_report_path"], "rb").read() == report_content
    return "PASS"

def test_10_zip_date_sanitization():
    # 1. < 1980
    dt_pre, r_pre = finalizer.get_sanitized_zip_date_time(1000)
    assert r_pre is not None
    assert dt_pre == finalizer.SAFE_FALLBACK_DATE_TIME

    # 2. > 2107
    dt_post, r_post = finalizer.get_sanitized_zip_date_time(5000000000)
    assert r_post is not None
    assert dt_post == finalizer.SAFE_FALLBACK_DATE_TIME

    # 3. Timestamp negativo incompatible con Windows localtime
    dt_neg, r_neg = finalizer.get_sanitized_zip_date_time(-10000000000)
    assert r_neg is not None
    assert dt_neg == finalizer.SAFE_FALLBACK_DATE_TIME
    return "PASS"

def test_11_large_vhdx_streaming():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        large_file = os.path.join(tmpdir, "large.dat")
        with open(large_file, "wb") as f:
            for _ in range(10): f.write(b"X" * (1024 * 1024))
        
        orig_sha = hashlib.sha256(open(large_file, "rb").read()).hexdigest().upper()
        out_zip = os.path.join(tmpdir, "out.zip")

        zinfo = zipfile.ZipInfo(filename="large.dat", date_time=(2026, 10, 29, 12, 0, 0))
        zinfo.compress_type = zipfile.ZIP_DEFLATED

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

        assert file_sha.hexdigest().upper() == orig_sha
        assert zipfile.ZipFile(out_zip, "r").testzip() is None
    return "PASS"

def test_12_failure_during_packaging_no_publish():
    ensure_sandbox_dir()
    with tempfile.TemporaryDirectory(dir=SANDBOX_BASE) as tmpdir:
        env = setup_mock_environment(tmpdir)
        os.remove(env["ar88_f1"])

        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 (RuntimeError, SystemExit, FileNotFoundError):
            failed = True
        assert failed is True
        assert not os.path.exists(env["master_zip_path"]), "teitan.zip fue publicado a pesar del fallo durante empaquetado"
    return "PASS"

def test_13_e2e_sandbox_run():
    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 falló"

        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 retornó código 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
    return "PASS"

def test_14_preflight_readonly_guarantee():
    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 modificó datos del núcleo"
    return "PASS"

def test_15_finalize_no_code_from_teamgroup():
    src_code = open(finalizer.__file__, "r", encoding="utf-8").read()
    assert "build_prioritized_inventory" not in src_code
    assert "safe_walk" not in src_code
    assert "MANDATORY_ROOTS" not in src_code
    return "PASS"

def run_suite():
    ensure_sandbox_dir()
    tests = [
        ("1. Bloqueo ante archivo AR88 con SHA-256 alterado en streaming", test_1_ar88_hash_mismatch_block),
        ("2. Bloqueo ante staged_path dirigido fuera de Kingston / hacia C:", test_2_staged_path_outside_kingston_block),
        ("3. Bloqueo ante teitan_core.zip con SHA-256 alterado", test_3_core_zip_hash_mismatch_block),
        ("4. Bloqueo ante entrada adicional no autorizada en SQLite", test_4_unauthorized_extra_entry_block),
        ("5. Bloqueo ante colisión en staged_path con hashes diferentes", test_5_staged_collision_different_hashes_block),
        ("6. Resolución documentada del registro ficticio exacto", test_6_exact_fake_record_resolution),
        ("7. Registro parcialmente parecido al fake NO es excluido", test_7_partially_similar_fake_not_excluded),
        ("8. Protección estricta: teitan.zip preexistente bloquea sin modificarse", test_8_existing_master_zip_protected),
        ("9. Protección estricta: FINAL_MASTER_REPORT.json preexistente bloquea", test_9_existing_final_report_protected),
        ("10. Sanitización de fechas ZIP (<1980, >2107 o error de localtime)", test_10_zip_date_sanitization),
        ("11. Streaming ZIP64 de archivos grandes en bloques sin cargar en RAM", test_11_large_vhdx_streaming),
        ("12. Fallo durante empaquetado no publica y limpia temporal", test_12_failure_during_packaging_no_publish),
        ("13. Sandbox End-to-End: preflight, empaquetado, CRC y publicación", test_13_e2e_sandbox_run),
        ("14. Garantía de solo lectura estricta en modo --preflight", test_14_preflight_readonly_guarantee),
        ("15. Código de V5.2 no invoca transferencias ni inventario de C:", test_15_finalize_no_code_from_teamgroup)
    ]

    print("=" * 70)
    print("  EJECUTANDO BANCO DE PRUEBAS AISLADAS V5.2 — HOTFIX DE SEGURIDAD")
    print("=" * 70)

    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("=" * 70)
    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)
