<?php
/**
 * Robot de Grabación Médica - Loop principal
 * Ejecución exclusiva por CLI (php robot.php)
 * Compatible con Windows y Linux
 */

// ─── CONFIGURACIÓN DEL ENTORNO ────────────────────────────────────────────────
// Tiempo ilimitado: el robot corre indefinidamente
set_time_limit(0);
ini_set('max_execution_time', 0);
ignore_user_abort(true);

// Flush inmediato en consola para ver el log en tiempo real
if (php_sapi_name() === 'cli') {
    // Desactivar buffering de salida
    while (ob_get_level()) ob_end_flush();
    ob_implicit_flush(true);
}

require_once __DIR__ . '/config.php';
require_once __DIR__ . '/dicom_reader.php';

// ─── LOG PERSISTENTE ─────────────────────────────────────────────────────────
$logFile = RUTA_RAIZ . 'backend' . DIRECTORY_SEPARATOR . 'robot.log';

function robotLog($msg) {
    global $logFile;
    $line = '[' . date('Y-m-d H:i:s') . '] ' . $msg . "\n";
    echo $line;
    // Rotar log si supera 5 MB
    if (file_exists($logFile) && filesize($logFile) > 5 * 1024 * 1024) {
        rename($logFile, $logFile . '.bak');
    }
    file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
}

// ─── INICIO ──────────────────────────────────────────────────────────────────
robotLog("=================================================");
robotLog(" Robot de Grabacion Medica - " . (ES_WINDOWS ? "Windows" : "Linux"));
robotLog("=================================================");
robotLog("Monitoreando: " . RUTA_ESTUDIOS_PENDIENTES);
robotLog("Log persistente: " . $logFile);

if (!ES_WINDOWS) {
    $herramienta = getHerramientaGrabacionLinux();
    robotLog("Herramienta grabacion Linux: " . ($herramienta ?? "NO DETECTADA - instala wodim o growisofs"));
}

$unidades = getUnidadesOpticas();
if (empty($unidades)) {
    robotLog("ADVERTENCIA: No se detectaron grabadoras opticas.");
    robotLog("  Linux: instala 'wodim' o 'growisofs' y verifica /dev/sr0");
    robotLog("  Windows: verifica que la grabadora este conectada.");
} else {
    robotLog("Unidades detectadas: " . implode(', ', $unidades));
}
robotLog("-------------------------------------------------");

// Variables para control de ritmo de hardware
$lastHardwareCheck = [];
$checkInterval = 10; // Segundos entre chequeos de hardware cuando hay trabajo (por defecto)
$emptyTrayInterval = 120; // 2 Minutos cuando está vacío (Req. Usuario)

// ─── LOOP PRINCIPAL ────────────────────────────────────────────────────────
while (true) {
    if (!DB::esLicenciaValida()) {
        robotLog("ERROR CRITICO: Licencia vencida o no activada. El robot esta detenido. Contacte soporte.");
        sleep(5); // Reducido de 30 a 5 para reanudación más rápida
        continue;
    }

    // Pulso de vida ligero (evita saturar db.json)
    @file_put_contents(RUTA_RAIZ . 'backend/robot.pulse', time());

    robotLog("Ciclo de monitoreo...");

    // PRIORIDAD 1: Procesar grabaciones (si hay disco listo + estudio esperando)
    procesaGrabacion();

    // PRIORIDAD 2: Escanear nuevas carpetas en estudios_pendientes
    escaneaCarpeta();

    // PRIORIDAD 3: Preparar estudios detectados (copiar visor + datos)
    procesaEstudios();

    sleep(2);  // 2 segundos entre ciclos para no saturar CPU/HARDWARE
}

// ─── ESCANEAR CARPETA DE ESTUDIOS PENDIENTES ─────────────────────────────────
function escaneaCarpeta() {
    if (!is_dir(RUTA_ESTUDIOS_PENDIENTES)) {
        robotLog("  WARN: Carpeta " . RUTA_ESTUDIOS_PENDIENTES . " no existe.");
        return;
    }

    $archivos = scandir(RUTA_ESTUDIOS_PENDIENTES);
    foreach ($archivos as $archivo) {
        if ($archivo === '.' || $archivo === '..') continue;

        $rutaCompleta = RUTA_ESTUDIOS_PENDIENTES . $archivo;
        if (!is_dir($rutaCompleta)) continue;

        // Evitar carpetas que aún están siendo escritas (vacías)
        $contenido = array_diff(scandir($rutaCompleta), ['.', '..']);
        if (empty($contenido)) continue;

        $existente = DB::getEstudioPorRuta($rutaCompleta);
        if ($existente) continue;

        robotLog("  Detectada carpeta PACS: $archivo");
        $metaDicom = DicomReader::getMetadataFromFolder($rutaCompleta);

        $config = DB::getConfig();
        $esAuto = ($config['modo_automatico'] ?? '1') === '1';

        if ($metaDicom) {
            $paciente    = $metaDicom['paciente'];
            $paciente_id = $metaDicom['paciente_id'] ?? '';
            $study_uid   = $metaDicom['study_uid'] ?? '';
            
            // FALLBACK: Si el DICOM no tiene UID pero la carpeta si parece uno
            if (empty($study_uid) && substr_count($archivo, '.') > 5) {
                $study_uid = $archivo;
            }

            $modalidad   = $metaDicom['modalidad'];
            $fechaRaw    = $metaDicom['fecha_estudio'];
            $fecha       = substr($fechaRaw, 0, 4) . '-' . substr($fechaRaw, 4, 2) . '-' . substr($fechaRaw, 6, 2);
            $desc        = $metaDicom['descripcion'] ?? '';
            robotLog("  >>> DICOM: $paciente [$modalidad]" . ($esAuto ? " (AUTO)" : " (MANUAL)"));
        } else {
            robotLog("  >>> CARPETA (Sin DICOM): $archivo" . ($esAuto ? " (AUTO)" : " (MANUAL)"));
            $paciente    = $archivo;
            $paciente_id = 'SN';
            $study_uid   = $archivo; // Usar nombre de carpeta como UID por defecto
            $modalidad   = 'OT';
            $fecha       = date('Y-m-d');
            $desc        = 'Estudio detectado automáticamente sin metadatos DICOM legibles';
        }

        $id = DB::insertEstudio([
            'paciente'        => $paciente,
            'paciente_id'     => $paciente_id,
            'study_uid'       => $study_uid,
            'fecha'           => $fecha,
            'hora_deteccion'  => date('H:i:s'),
            'modalidad'       => $modalidad,
            'descripcion'     => $desc,
            'ruta_archivos'   => $rutaCompleta,
            'tamaño'          => getTamañoCarpeta($rutaCompleta),
            'estado'          => $esAuto ? 'preparando' : 'pendiente',
            'unidad_asignada' => $esAuto ? ($config['unidad_optica'] ?? '') : ''
        ]);

        $msgLog = $esAuto
            ? "Deteccion Automatica: Iniciando preparacion."
            : "Detectado: Esperando accion manual.";
        DB::registrarLog($id, 'DETECCION', $msgLog . " | Paciente: $paciente | UID: $study_uid");
    }
}

// ─── PREPARAR ESTUDIOS EN ESTADO 'preparando' ────────────────────────────────
function procesaEstudios() {
    $estudios = DB::getEstudios();
    $config   = DB::getConfig();
    $esAuto   = ($config['modo_automatico'] ?? '1') === '1';

    foreach ($estudios as $estudio) {
        $estado = $estudio['estado'];

        // Si está pendiente en modo auto → promover a preparando
        if ($estado === 'pendiente' && $esAuto) {
            DB::updateEstudio($estudio['id'], [
                'estado'           => 'preparando',
                'unidad_asignada'  => $config['unidad_optica'] ?? ''
            ]);
            continue;
        }

        if ($estado !== 'preparando') continue;

        $id = $estudio['id'];
        robotLog("  [ID $id] Preparando estructura del disco...");

        try {
            $rutaDisco = RUTA_RAIZ . 'temp_disco_' . $id . DIRECTORY_SEPARATOR;
            if (!is_dir($rutaDisco)) {
                if (!mkdir($rutaDisco, 0755, true)) {
                    throw new Exception("No se pudo crear carpeta temporal: $rutaDisco");
                }
            }

            // 1. Copiar visor DICOM
            if (!is_dir(RUTA_VISOR)) {
                throw new Exception("Carpeta del visor no encontrada en: " . RUTA_VISOR);
            }
            DB::cpDir(RUTA_VISOR, $rutaDisco);

            // 2. Crear carpeta 'data' y copiar imagenes
            $rutaData = $rutaDisco . 'data' . DIRECTORY_SEPARATOR;
            if (!is_dir($rutaData)) mkdir($rutaData, 0755, true);

            if (!is_dir($estudio['ruta_archivos'])) {
                throw new Exception("Carpeta de origen no encontrada: " . $estudio['ruta_archivos']);
            }
            DB::cpDir($estudio['ruta_archivos'], $rutaData);

            // 3. Generar inventario de imágenes (RECURSIVO)
            $listaImagenes = [];
            $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($rutaData));
            foreach ($it as $file) {
                if ($file->isFile()) {
                    $name = $file->getFilename();
                    // Ignorar archivos de sistema DICOM y carpetas
                    if (in_array(strtoupper($name), ['DICOMDIR', 'SERIES.DIR', 'DESKTOP.INI', 'THUMBS.DB'])) continue;
                    
                    // Obtener ruta relativa respecto a $rutaData
                    $relPath = str_replace($rutaData, '', $file->getPathname());
                    // Normalizar separadores a '/' para web
                    $relPath = str_replace(DIRECTORY_SEPARATOR, '/', $relPath);
                    $listaImagenes[] = $relPath;
                }
            }

            // 4. Escribir metadatos JSON para el visor
            $metadatos = [
                'paciente'    => $estudio['paciente'],
                'paciente_id' => $estudio['paciente_id'] ?? '',
                'study_uid'   => $estudio['study_uid'] ?? '',
                'modalidad'   => $estudio['modalidad'],
                'descripcion' => $estudio['descripcion'] ?? '',
                'fecha'       => $estudio['fecha'],
                'imagenes'    => $listaImagenes,
            ];
            file_put_contents(
                $rutaData . 'paciente.json',
                json_encode($metadatos, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
            );

            // 5. Generar también paciente.js para compatibilidad con protocolo file:// (Offline CD)
            $jsContent = "window.ESTUDIO_DATA = " . json_encode($metadatos, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . ";";
            file_put_contents($rutaData . 'paciente.js', $jsContent);

            DB::updateEstudio($id, ['estado' => 'esperando_disco']);
            DB::registrarLog($id, 'PREPARACION', 'Preparacion completada. Esperando disco blanco. ('
                . count($listaImagenes) . ' archivos)');
            robotLog("  [ID $id] OK Preparacion lista (" . count($listaImagenes) . " archivos).");

        } catch (Exception $e) {
            $errorMsg = $e->getMessage();
            DB::updateEstudio($id, ['estado' => 'error']);
            DB::registrarLog($id, 'ERROR', "Fallo en preparacion: $errorMsg");
            robotLog("  [ID $id] ERROR en preparacion: $errorMsg");
        }
    }
}

// ─── PROCESO DE GRABACION REAL ────────────────────────────────────────────────
function procesaGrabacion() {
    $estudios = DB::getEstudios();
    $unidades = getUnidadesOpticas();
    // ─── ESCANEO DE HARDWARE (Optimizado por tiempo) ─────────────────────────
    global $lastHardwareCheck, $emptyTrayInterval;
    $ahora = time();
    $cacheUnidades = [];

    // Verificamos si hay algún estudio que necesite disco YA
    $estudiosEsperando = array_filter($estudios, function($e) { return $e['estado'] === 'esperando_disco'; });

    foreach ($unidades as $u) {
        $lastCheck = $lastHardwareCheck[$u] ?? 0;
        $diff = $ahora - $lastCheck;
        
        // Si hay estudios esperando, polleamos el hardware. 
        // Si la bandeja estaba vacía la última vez, esperamos los 2 minutos del usuario.
        $intervaloActual = (isset($lastHardwareCheck[$u . '_empty']) && $lastHardwareCheck[$u . '_empty']) ? $emptyTrayInterval : 10;

        if ($diff < $intervaloActual && !empty($estudiosEsperando)) {
            // Usamos el cache del API si está disponible y es reciente
            $tempCacheBody = @file_get_contents(RUTA_RAIZ . 'backend/unidades_cache.json');
            $tempUnidades = json_decode($tempCacheBody, true);
            if ($tempUnidades) {
                foreach ($tempUnidades as $tu) {
                    if ($tu['unidad'] === $u) { $cacheUnidades[$u] = $tu; break; }
                }
            }
        }

        if (!isset($cacheUnidades[$u])) {
            $info = getInfoDisco($u);
            $cacheUnidades[$u] = $info;
            $lastHardwareCheck[$u] = $ahora;
            $lastHardwareCheck[$u . '_empty'] = !$info['ready'];
            
            if (!$info['ready']) {
                robotLog("  [HW] Unidad $u: Bandeja vacía. Re-verificación en 2 minutos.");
            }
        }

        // REQUERIMIENTO: Si el disco tiene datos, debe expulsarse automáticamente.
        // Esto ocurre incluso si no hay estudios esperando, para mantener las bandejas limpias.
        if ($info['ready'] && $info['has_data']) {
            robotLog("  [HW] Unidad $u: DISCO CON DATOS detectado. Expulsando automáticamente...");
            DB::registrarLog(0, 'HARDWARE', "Unidad $u: Disco con datos detectado. Se ha expulsado automáticamente para mantener la unidad lista.");
            expulsarUnidad($u);
            $cacheUnidades[$u]['ready'] = false;
        } elseif ($info['ready'] && !$info['blank'] && !$info['rw']) {
            // Caso disco no blanco y no regrabable (pero sin FS detectado aún)
            robotLog("  [HW] Unidad $u: DISCO NO APTO (Cerrado/No RW). Expulsando...");
            expulsarUnidad($u);
            $cacheUnidades[$u]['ready'] = false;
        }
    }

    // COMPARTIR ESTADO CON FRONTEND
    // El Robot es la fuente de verdad. El API solo lee este archivo.
    $estadoUnidades = [];
    foreach ($cacheUnidades as $u => $infoDetalle) {
        $tipo = 'Bandeja Vacía';
        if ($infoDetalle['ready']) {
            if ($infoDetalle['blank']) {
                $tipo = 'Disco Blanco ✓' . ($infoDetalle['capacity'] !== '—' ? " ({$infoDetalle['capacity']})" : '');
            } else {
                $fsLabel = $infoDetalle['fs'] ?: ($infoDetalle['volname'] ?: 'UDF/ISO');
                if ($infoDetalle['rw']) {
                    $tipo = "Disco RW con Datos ($fsLabel)" . ($infoDetalle['volname'] ? " [{$infoDetalle['volname']}]" : '');
                } else {
                    $tipo = "Disco con Datos ($fsLabel)" . ($infoDetalle['volname'] ? " [{$infoDetalle['volname']}]" : '') . " ⚠ No Regrabable";
                }
            }
        }
        $estadoUnidades[] = array_merge(['unidad' => $u, 'tipo' => $tipo], $infoDetalle);
    }
    file_put_contents(RUTA_RAIZ . 'backend/unidades_cache.json', json_encode($estadoUnidades), LOCK_EX);

    // Verificar si hay estudios esperando (para proceder con la grabación) 
    $hayEsperando = false;
    foreach ($estudios as $e) {
        if ($e['estado'] === 'esperando_disco') { $hayEsperando = true; break; }
    }
    if (!$hayEsperando) return;

    // ─── PROCESAR ESTUDIOS ESPERANDO DISCO ───────────────────────────────────
    foreach ($estudios as $estudio) {
        if ($estudio['estado'] !== 'esperando_disco') continue;

        $id     = $estudio['id'];
        $unidad = $estudio['unidad_asignada'] ?? null;

        if (!$unidad) {
            $config = DB::getConfig();
            $unidad = $config['unidad_optica'] ?? null;
            if ($unidad) {
                DB::updateEstudio($id, ['unidad_asignada' => $unidad]);
            } else {
                robotLog("  [ID $id] WARN: No hay unidad optica configurada.");
                continue;
            }
        }

        if (!isset($cacheUnidades[$unidad])) {
            $cacheUnidades[$unidad] = getInfoDisco($unidad);
        }

        $info = $cacheUnidades[$unidad];

        if (!$info['ready']) {
            robotLog("  [ID $id] Unidad $unidad: Sin disco o no lista. Esperando...");
            continue;
        }

        if ($info['has_data'] && !$info['rw']) {
            $detalles = "has_data=" . ($info['has_data']?'Si':'No') . 
                        ", blanco="   . ($info['blank']?'Si':'No') . 
                        ", rw="       . ($info['rw']?'Si':'No') . 
                        ", fs="       . ($info['fs'] ?: 'ninguno') . 
                        ", vol="      . ($info['volname'] ?: 'ninguno');
            robotLog("  [ID $id] Unidad $unidad: Disco no apto (tiene datos y no es RW). Detalles: [$detalles]");
            continue;
        }

        // ─── ¡INICIAR GRABACION! ────────────────────────────────────────────
        $tipoMsg = $info['has_data'] && $info['rw']
            ? "Disco RW con datos (se borrara y regrabara)"
            : "Disco blanco";

        robotLog("  [ID $id] DISCO DETECTADO! ($tipoMsg | Cap: " . $info['capacity'] . ") Iniciando en $unidad...");
        DB::updateEstudio($id, ['estado' => 'grabando']);
        DB::registrarLog($id, 'GRABACION', "Iniciando grabacion en $unidad. Tipo: $tipoMsg");

        $rutaISO = RUTA_RAIZ . 'temp_disco_' . $id;
        $volName = substr(preg_replace('/[^A-Za-z0-9]/', '', $estudio['paciente']), 0, 15);
        if (empty($volName)) $volName = 'ESTUDIO' . $id;

        $output = []; $return_var = 1;

        if (ES_WINDOWS) {
            $psScript = __DIR__ . DIRECTORY_SEPARATOR . 'burn_windows.ps1';
            // Aseguramos que ExecutionPolicy esté habilitado para el proceso
            $comando = 'powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass'
                     . ' -File ' . escapeshellarg($psScript)
                     . ' -SourcePath '  . escapeshellarg($rutaISO)
                     . ' -DriveLetter ' . escapeshellarg($unidad)
                     . ' -VolumeName '  . escapeshellarg($volName);
            robotLog("  [ID $id] CMD: $comando");
            exec($comando . ' 2>&1', $output, $return_var);
        } else {
            $comando = getComandoGrabarLinux($unidad, $rutaISO, $volName);
            if ($comando) {
                robotLog("  [ID $id] CMD: $comando");
                exec($comando . ' 2>&1', $output, $return_var);
            }
        }

        $outputStr = implode(' | ', array_slice($output, -5)); // Últimas 5 líneas del log

        if ($return_var === 0) {
            DB::updateEstudio($id, ['estado' => 'finalizado']);
            DB::registrarLog($id, 'GRABACION', "Grabacion exitosa en $unidad. Volumen: $volName | " . $outputStr);
            robotLog("  [ID $id] ✓ FINALIZADO. Volumen: $volName");
            reproducirAlerta(true);

            // Expulsar disco si está configurado
            $config = DB::getConfig();
            if (($config['expulsar_al_terminar'] ?? '1') === '1') {
                expulsarUnidad($unidad);
                robotLog("  [ID $id] Disco expulsado de $unidad.");
            }

            // Eliminar carpeta temporal
            if (is_dir($estudio['ruta_archivos'])) {
                DB::rmDir($estudio['ruta_archivos']);
                robotLog("  [ID $id] Carpeta origen eliminada.");
            }
            if (is_dir($rutaISO)) DB::rmDir($rutaISO);

        } else {
            DB::updateEstudio($id, ['estado' => 'error']);
            DB::registrarLog($id, 'ERROR', "Error de grabacion en $unidad: " . $outputStr);
            robotLog("  [ID $id] ✗ ERROR en grabacion: " . $outputStr);
            reproducirAlerta(false);
        }

        // Invalidar cache de esta unidad tras uso
        unset($cacheUnidades[$unidad]);
    }
}

// La función getInfoDisco() ahora se encuentra centralizada en backend/config.php
// para ser compartida entre la API y el Robot.


// ─── UTILIDADES ──────────────────────────────────────────────────────────────
function getTamañoCarpeta($dir) {
    $size = 0;
    try {
        $it = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        foreach ($it as $file) {
            if ($file->isFile()) $size += $file->getSize();
        }
    } catch (Exception $e) {}
    return $size;
}
