r"""TEITAN_SFTP_TURBO.py
Misión: TEITAN-SFTP-TURBO-03
Motor de transferencia SFTP de alta velocidad con reanudación estricta y transporte probado.
Basado directamente en el esquema de transporte exitoso de reanudar_subida_zip.py.
NO CONTIENE CONTRASEÑAS NI EJECUTA PHP NI CONEXIONES NO AUTORIZADAS.
"""
import base64
import getpass
import hashlib
import os
import posixpath
import sys
import time
from pathlib import Path

try:
    import paramiko
except ImportError:
    sys.exit("[ERROR] Falta la libreria Paramiko. Ejecute: python -m pip install paramiko")

# Parámetros Base
HOST = "laboptimus.com"
PORT = 22
USER = "laboptimus"
LOCAL_FILE = Path(r"E:\rescatado\proyecto_completo.zip")
DEFAULT_REMOTE = "/home/laboptimus/public_html/proyecto_completo.zip"

# Parámetros del Motor Turbo (Sincronización Pipelined)
DEFAULT_CHUNK = 256 * 1024          # 256 KiB: Tamaño óptimo para buffers de socket SFTP
MAX_IN_FLIGHT = 16                  # Número máximo de paquetes en vuelo antes de exigir ACK
SAMPLE_LEN = 64 * 1024              # 64 KiB para validación de prefijo
MAX_RETRIES = 10
KNOWN_HOSTS_PATH = Path.home() / ".ssh" / "known_hosts"

class TurboSafetyViolation(Exception):
    pass

def check_local_integrity(snapshot=None):
    if not LOCAL_FILE.is_file():
        raise TurboSafetyViolation(f"Archivo original local no encontrado: {LOCAL_FILE}")
    st = LOCAL_FILE.stat()
    if snapshot is not None:
        if st.st_size != snapshot.st_size or st.st_mtime_ns != snapshot.st_mtime_ns:
            raise TurboSafetyViolation("ALERTA: El ZIP local cambio de tamano o fecha.")
    return st

def load_verified_host_key(hostname=HOST, known_hosts_file=KNOWN_HOSTS_PATH):
    """Carga y valida estrictamente la host key SSH desde known_hosts.
    Detiene el proceso sin enviar credenciales si no existe o si es inválida.
    """
    if not known_hosts_file.exists():
        raise TurboSafetyViolation(
            f"BLOQUEO_SEGURIDAD: Archivo known_hosts no encontrado en {known_hosts_file}. "
            "No se puede verificar la identidad del servidor."
        )
    host_keys = paramiko.HostKeys(str(known_hosts_file))
    entry = host_keys.lookup(hostname)
    if not entry:
        raise TurboSafetyViolation(
            f"BLOQUEO_SEGURIDAD: No existe clave SSH conocida para el host '{hostname}' en {known_hosts_file}. "
            "Conexion detenida antes de autenticar para prevenir ataques de suplantacion (MITM)."
        )
    # Preferencia: ed25519, luego ecdsa, rsa
    for pref in ("ssh-ed25519", "ecdsa-sha2-nistp256", "ssh-rsa"):
        if pref in entry:
            return entry[pref]
    # Retornar la primera clave registrada si ninguna coincide con la lista preferida
    return next(iter(entry.values()))

def create_transport_connection(password, host=HOST, port=PORT, user=USER, known_hosts_file=KNOWN_HOSTS_PATH):
    """Establece conexión utilizando Paramiko.Transport verificando obligatoriamente
    la host key SSH previa antes de entregar la contraseña del usuario.
    """
    expected_host_key = load_verified_host_key(hostname=host, known_hosts_file=known_hosts_file)
    sock = None
    try:
        sock = paramiko.Transport((host, port))
        sock.use_compression(False) # Sin recomprimir el zip
        sock.set_keepalive(15)
        # Validación estricta: entrega hostkey=expected_host_key
        # Si la huella no coincide, paramiko.SSHException ocurre ANTES de enviar usuario y contraseña
        sock.connect(
            hostkey=expected_host_key,
            username=user,
            password=password
        )
        sftp = paramiko.SFTPClient.from_transport(sock)
        return sock, sftp
    except paramiko.AuthenticationException:
        if sock:
            try: sock.close()
            except: pass
        raise
    except Exception as e:
        if sock:
            try: sock.close()
            except: pass
        raise

def get_remote_file_size(sftp, remote_path):
    try:
        return sftp.stat(remote_path).st_size
    except FileNotFoundError:
        return None
    except Exception as e:
        if getattr(e, "errno", None) == 2:
            return None
        raise

def verify_prefix_samples(sftp, remote_path, remote_size):
    """Comprueba 5 muestras representativas entre local y remoto para asegurar continuidad."""
    if not remote_size:
        return True
    local_size = LOCAL_FILE.stat().st_size
    if remote_size > local_size:
        raise TurboSafetyViolation(
            f"ERROR: Archivo remoto ({remote_size} B) es MAYOR que el local ({local_size} B)."
        )
    offsets = {
        0,
        max(0, remote_size // 3 - SAMPLE_LEN // 2),
        max(0, (2 * remote_size) // 3 - SAMPLE_LEN // 2),
        max(0, remote_size - 1024 * 1024),
        max(0, remote_size - SAMPLE_LEN)
    }
    with LOCAL_FILE.open("rb") as l_f, sftp.open(remote_path, "rb") as r_f:
        for offset in sorted(offsets):
            chunk_len = min(SAMPLE_LEN, remote_size - offset)
            if chunk_len <= 0:
                continue
            l_f.seek(offset)
            r_f.seek(offset)
            loc_data = l_f.read(chunk_len)
            rem_data = r_f.read(chunk_len)
            if loc_data != rem_data:
                raise TurboSafetyViolation(
                    f"DISCREPANCIA DETECTADA en offset {offset}. "
                    "Los bytes remotos no coinciden con el ZIP local. Subida abortada para proteger el archivo."
                )
    print(f"[VERIFICACION] Muestras de prefijo coinciden al 100%. Continuacion segura desde {remote_size/1024**3:.3f} GiB.")
    return True

def diagnostic_mode(password):
    """Modo diagnóstico de solo lectura: prueba conexión y valida estado sin escribir."""
    print("\n=======================================================")
    print("      EJECUTANDO DIAGNOSTICO DE CONEXION (SOLO LECTURA) ")
    print("=======================================================")
    sock = sftp = None
    try:
        sock, sftp = create_transport_connection(password)
        print("AUTENTICACION=PASS")
        r_size = get_remote_file_size(sftp, DEFAULT_REMOTE)
        if r_size is not None:
            print("REMOTO_EXISTE=SI")
            print(f"TAMANO_REMOTO={r_size:,} bytes ({r_size/1024**3:.3f} GiB)")
            # Verificar muestras
            verify_prefix_samples(sftp, DEFAULT_REMOTE, r_size)
            print("REANUDACION_POSIBLE=SI")
        else:
            print("REMOTO_EXISTE=NO (Se iniciaria desde 0)")
            print("REANUDACION_POSIBLE=NO")
        return True
    except paramiko.AuthenticationException:
        print("AUTENTICACION=FAIL (Credenciales rechazadas por el servidor)")
        return False
    except Exception as e:
        print(f"CONEXION_ERROR={type(e).__name__}: {e}")
        return False
    finally:
        if sftp:
            try: sftp.close()
            except: pass
        if sock:
            try: sock.close()
            except: pass

def turbo_upload_loop(password, chunk_size=DEFAULT_CHUNK):
    """Bucle principal de subida turbo con escritura posicionada 'r+b',
    pipelining controlado y reconexión automática sin pérdida de bytes.
    """
    snapshot = check_local_integrity()
    total_bytes = snapshot.st_size
    total_gib = total_bytes / (1024**3)
    
    print("\n=======================================================")
    print("      INICIANDO MOTOR TURBO SFTP (ESCRITURA ACTIVA)    ")
    print("=======================================================")
    print(f"ORIGEN : {LOCAL_FILE} ({total_gib:.2f} GiB)")
    print(f"DESTINO: {DEFAULT_REMOTE}")
    print(f"BUFFER : {chunk_size//1024} KiB por paquete pipelined")
    print("=======================================================\n")
    
    retry_count = 0
    session_start_time = time.monotonic()
    total_transferred_this_session = 0
    
    while True:
        sock = sftp = None
        try:
            sock, sftp = create_transport_connection(password)
            retry_count = 0
            
            remote_size = get_remote_file_size(sftp, DEFAULT_REMOTE) or 0
            check_local_integrity(snapshot)
            
            if remote_size >= total_bytes:
                print(f"\n[V] El archivo ya esta 100% completo en el hosting ({remote_size:,} bytes).")
                break
                
            verify_prefix_samples(sftp, DEFAULT_REMOTE, remote_size)
            print(f"\n[TURBO] Reanudando desde {remote_size/1024**3:.3f} GiB ({remote_size:,} bytes)...")
            
            # Modo de apertura posicional seguro r+b (sin a+b)
            mode = "r+b" if remote_size > 0 else "wb"
            remote_f = sftp.open(DEFAULT_REMOTE, mode)
            remote_f.set_pipelined(True)
            remote_f.seek(remote_size)
            
            with LOCAL_FILE.open("rb") as local_f:
                local_f.seek(remote_size)
                transferred = remote_size
                last_print = time.monotonic()
                bytes_in_interval = 0
                
                # Bucle de escritura continua con control de flujo
                while transferred < total_bytes:
                    check_local_integrity(snapshot)
                    data = local_f.read(chunk_size)
                    if not data:
                        break
                    remote_f.write(data)
                    transferred += len(data)
                    bytes_in_interval += len(data)
                    total_transferred_this_session += len(data)
                    
                    now = time.monotonic()
                    if now - last_print >= 1.5:
                        pct = (transferred / total_bytes) * 100
                        cur_gb = transferred / (1024**3)
                        inst_speed = (bytes_in_interval / (now - last_print)) / (1024 * 1024)
                        overall_dur = max(0.01, now - session_start_time)
                        avg_speed = (total_transferred_this_session / (1024 * 1024)) / overall_dur
                        
                        sys.stdout.write(
                            f"\r[TURBO] {pct:6.2f}% ({cur_gb:5.2f}/{total_gib:5.2f} GiB) | "
                            f"INST: {inst_speed:5.2f} MiB/s | "
                            f"PROM: {avg_speed:5.2f} MiB/s | "
                            f"CONFIRMADOS: {transferred:,} B   "
                        )
                        sys.stdout.flush()
                        last_print = now
                        bytes_in_interval = 0
                        
            # Cierre seguro esperando confirmación de todos los writes pendientes
            remote_f.close()
            
            # Comprobación de confirmación final
            final_size = get_remote_file_size(sftp, DEFAULT_REMOTE)
            if final_size == total_bytes:
                print(f"\n\n=======================================================")
                print(f"[EXITO] SUBIDA 100% COMPLETADA Y CONFIRMADA.")
                print(f"Tamano final en servidor: {final_size:,} bytes.")
                print("=======================================================")
                break
            else:
                print(f"\n[AVISO] Tamano tras ciclo: {final_size:,} / {total_bytes:,}. Continuando...")
                
        except (TurboSafetyViolation, paramiko.AuthenticationException) as e:
            print(f"\n\n[DETENCION SEGURIDAD] {type(e).__name__}: {e}")
            sys.exit(2)
        except KeyboardInterrupt:
            print("\n\n[DETENIDO POR USUARIO] Progreso conservado intacto en el hosting.")
            sys.exit(130)
        except Exception as e:
            retry_count += 1
            print(f"\n[INCIDENCIA DE RED] {type(e).__name__}: {e}")
            if retry_count > MAX_RETRIES:
                print(f"[LIMITE REINTENTOS] Superados {MAX_RETRIES} intentos. Detenido.")
                sys.exit(1)
            wait = min(3 * retry_count, 20)
            print(f"[REINTENTO {retry_count}/{MAX_RETRIES}] Reconectando en {wait}s...")
            time.sleep(wait)
        finally:
            if sftp:
                try: sftp.close()
                except: pass
            if sock:
                try: sock.close()
                except: pass

def main():
    print("===============================================================================")
    print("      TEITAN SFTP TURBO - MOTOR OPTIMIZADO DE ALTA VELOCIDAD                  ")
    print("===============================================================================")
    snapshot = check_local_integrity()
    print(f"ARCHIVO LOCAL: {LOCAL_FILE} ({snapshot.st_size/1024**3:.2f} GiB)")
    print("===============================================================================\n")
    
    password = getpass.getpass("Ingrese contrasena SFTP para laboptimus (no visible): ")
    if not password:
        sys.exit("[ABORTADO] No se proporciono contrasena.")
        
    print("\nSeleccione el modo de operacion:")
    print("  [1] MODO DIAGNOSTICO (Solo lectura: verifica conexion, tamano y muestras)")
    print("  [2] MODO TURBO ACTIVO (Subida y reanudacion acelerada)")
    opt = input("Opcion (1 o 2): ").strip()
    
    if opt == "1":
        diagnostic_mode(password)
    elif opt == "2":
        turbo_upload_loop(password)
    else:
        print("[OPCION INVALIDA] Abortando.")

if __name__ == "__main__":
    main()

