<?php
/**
 * Configuración central - Compatible Linux y Windows
 * Sistema de Robot Médico de Grabación PACS/DICOM
 */

define('DB_JSON', __DIR__ . '/db.json');

define('ES_WINDOWS', strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');

// Asegurar que las operaciones de hardware no expiren
if (isset($_SERVER['REQUEST_METHOD'])) {
    // Si es vía web, dar un tiempo generoso pero no infinito para no bloquear Apache
    set_time_limit(300); 
} else {
    // Si es vía consola (robot), tiempo infinito
    set_time_limit(0);
}

// ─── Rutas del sistema ────────────────────────────────────────────────────────
define('RUTA_RAIZ',               dirname(__DIR__) . DIRECTORY_SEPARATOR);
define('RUTA_ESTUDIOS_PENDIENTES', RUTA_RAIZ . 'estudios_pendientes' . DIRECTORY_SEPARATOR);
define('RUTA_ESTUDIOS_PROCESADOS', RUTA_RAIZ . 'estudios_procesados' . DIRECTORY_SEPARATOR);
define('RUTA_ESTUDIOS_QUEMADOS',   RUTA_RAIZ . 'estudios_quemados'   . DIRECTORY_SEPARATOR);
define('RUTA_VISOR',               RUTA_RAIZ . 'visor'               . DIRECTORY_SEPARATOR);

// ─── Ruta del helper de CD-ROM (Linux) ───────────────────────────────────────
define('CDROM_HELPER', __DIR__ . '/cdrom_helper.sh');

// Verifica que el helper está instalado y tiene permisos de sudo
function getCdromHelperCmd($accion, $unidad, ...$extras) {
    $helper = CDROM_HELPER;
    if (!file_exists($helper)) {
        error_log("WARN: cdrom_helper.sh no encontrado en $helper. Ejecuta install_linux.sh como root.");
        // Fallback directo (puede fallar por permisos)
        switch ($accion) {
            case 'eject':  return 'eject '  . escapeshellarg($unidad) . ' 2>&1';
            case 'status': return 'blkid -o value -s TYPE ' . escapeshellarg($unidad) . ' 2>/dev/null';
            default:       return null;
        }
    }
    $cmd = 'sudo ' . escapeshellarg($helper) . ' ' . escapeshellarg($accion) . ' ' . escapeshellarg($unidad);
    foreach ($extras as $extra) {
        $cmd .= ' ' . escapeshellarg($extra);
    }
    return $cmd;
}

// ─── Herramienta de grabación en Linux ───────────────────────────────────────
// wodim es el sucesor moderno de cdrecord. El sistema usa wodim si está instalado,
// de lo contrario usa growisofs (para DVD) o cdrecord como último recurso.
function getHerramientaGrabacionLinux() {
    if (trim(shell_exec('which wodim 2>/dev/null')))     return 'wodim';
    if (trim(shell_exec('which cdrecord 2>/dev/null')))  return 'cdrecord';
    if (trim(shell_exec('which growisofs 2>/dev/null'))) return 'growisofs';
    return null;
}

// ─── Detección de unidades ópticas ───────────────────────────────────────────
function getUnidadesOpticas() {
    $unidades = [];

    if (ES_WINDOWS) {
        $cmd = 'powershell -NoProfile -NonInteractive -Command "Get-WmiObject Win32_LogicalDisk -Filter \'DriveType=5\' | Select-Object -ExpandProperty DeviceID"';
        exec($cmd, $output);
        foreach ($output as $line) {
            $u = strtoupper(trim($line));
            if (preg_match('/^[A-Z]:$/', $u)) {
                $unidades[] = $u;
            }
        }
    } else {
        // Buscar dispositivos sr* (CD/DVD estándar en Linux)
        $devsSR = glob('/dev/sr*');
        // También bus scd* en algunos sistemas
        $devsSCD = glob('/dev/scd*');
        $todos = array_merge($devsSR ?: [], $devsSCD ?: []);

        foreach ($todos as $dev) {
            if (is_readable($dev)) {
                $unidades[] = $dev;
            }
        }

        // Fallback: si no se detecta nada, intentar /dev/cdrom o /dev/dvd
        if (empty($unidades)) {
            foreach (['/dev/cdrom', '/dev/dvd', '/dev/dvdrw', '/dev/cdrom0', '/dev/sr0'] as $fallback) {
                if (file_exists($fallback)) {
                    $unidades[] = $fallback;
                    break;
                }
            }
        }

        // Último recurso: asumir /dev/sr0
        if (empty($unidades)) {
            $unidades[] = '/dev/sr0';
        }
    }

    return array_unique($unidades);
}

// ─── Comando para expulsar disco ─────────────────────────────────────────────
/**
 * En Windows: usa eject_windows.ps1 con 3 métodos de fallback.
 * En Linux: usa cdrom_helper.sh (eject → udisksctl → ioctl).
 */
function expulsarUnidad($unidad) {
    if (empty($unidad)) return false;

    if (ES_WINDOWS) {
        $psScript = __DIR__ . DIRECTORY_SEPARATOR . 'eject_windows.ps1';
        if (!file_exists($psScript)) {
            // Fallback si el script no existe
            $letra = strtoupper(str_replace(':', '', $unidad));
            $cmd   = 'powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command '
                   . '"(New-Object -ComObject Shell.Application).Namespace(17).ParseName('
                   . "'" . $letra . ":').InvokeVerb('Eject')\"";
        } else {
            $cmd = 'powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass'
                 . ' -File ' . escapeshellarg($psScript)
                 . ' -DriveLetter ' . escapeshellarg($unidad);
        }
    } else {
        // Linux: helper con sudo (soporta eject, udisksctl, fallback ioctl)
        $cmd = getCdromHelperCmd('eject', $unidad);
    }

    $output = [];
    $ret    = 1;
    exec($cmd . ' 2>&1', $output, $ret);
    $outStr = implode(' | ', array_filter($output));

    if ($ret === 0) {
        error_log("expulsarUnidad OK ($unidad): $outStr");
        return true;
    }

    // En Windows: algunos métodos de expulsión no retornan código 0
    // aunque funcionen (Shell.Application no retorna). Verificar por mensaje.
    if (ES_WINDOWS && stripos($outStr, 'EJECT_OK') !== false) {
        return true;
    }

    error_log("expulsarUnidad FAIL ($unidad): $outStr");
    return false;
}

// ─── Comando para detectar estado del disco ───────────────────────────────────
function getComandoEstadoDisco($unidad) {
    if (ES_WINDOWS) {
        $letra = strtoupper(str_replace(':', '', $unidad));
        // Script PowerShell compatible con PowerShell 5.1+ (Windows 10 por defecto)
        // NO usa operadores ternarios, usa if/else para máxima compatibilidad
        $ps = '$ErrorActionPreference = "SilentlyContinue"; '
            . '$ready = "False"; $blank = "False"; $rw = "False"; $cap = "0"; $fs = ""; $vol = ""; '
            . 'try { '
            . '  $m = New-Object -ComObject IMAPI2.MsftDiscMaster2; '
            . '  foreach ($id in $m) { '
            . '    $r = New-Object -ComObject IMAPI2.MsftDiscRecorder2; '
            . '    $r.InitializeDiscRecorder($id); '
            . '    if ($r.DriveLetter -eq "' . $letra . ':") { '
            . '      try { '
            . '        $f = New-Object -ComObject IMAPI2.MsftDiscFormat2Data; '
            . '        $f.Recorder = $r; '
            . '        $st = $f.CurrentMediaStatus; '
            . '        $mt = $f.CurrentPhysicalMediaType; '
            . '        $rwTypes = @(3,7,8,10,14,17); '
            . '        $ready = "True"; '
            . '        $isPhysBlank = "False"; try { if ($f.MediaPhysicallyBlank) { $isPhysBlank = "True" } } catch {}; '
            . '        $isHeurBlank = "False"; try { if ($f.MediaHeuristicallyBlank) { $isHeurBlank = "True" } } catch {}; '
            . '        if ($isPhysBlank -eq "True" -or $isHeurBlank -eq "True" -or ($st -band 1) -eq 1) { $blank = "True" } else { $blank = "False" }; '
            . '        if ($rwTypes -contains $mt) { $rw = "True" } else { $rw = "False" }; '
            . '        $freeSec = $f.FreeSectorsOnMedia; '
            . '        $totalSec = $f.TotalSectorsOnMedia; '
            . '        $cap = [Math]::Round($freeSec * 2048 / 1073741824, 2); '
            . '        Write-Host "SEC_FREE=$freeSec"; '
            . '        Write-Host "SEC_TOTAL=$totalSec"; '
            . '      } catch { $ready = "False" }; '
            . '      break; '
            . '    } '
            . '  } '
            . '} catch { $ready = "False" }; '
            . '$wmi = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID=' . "'" . $letra . ":" . "'" . '" -ErrorAction SilentlyContinue; '
            . 'if ($wmi) { '
            . '  if ($wmi.FileSystem) { $fs = $wmi.FileSystem } else { $fs = "" }; '
            . '  if ($wmi.VolumeName) { $vol = $wmi.VolumeName } else { $vol = "" }; '
            . '}; '
            . 'Write-Host "READY=$ready"; '
            . 'Write-Host "BLANK=$blank"; '
            . 'Write-Host "RW=$rw"; '
            . 'Write-Host "CAP=${cap}GB"; '
            . 'Write-Host "FS=$fs"; '
            . 'Write-Host "VOLNAME=$vol"';

        return 'powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "' . $ps . '"';
    }
    // Linux: helper con sudo devuelve STATUS=BLANK|DATA|EMPTY + FS=tipo + RW=True|False
    return getCdromHelperCmd('status', $unidad);
}

// ─── Comando de grabación en Linux ───────────────────────────────────────────
function getComandoGrabarLinux($unidad, $rutaISO, $volName = 'EstudioMedico') {
    $herramienta = getHerramientaGrabacionLinux();
    switch ($herramienta) {
        case 'wodim':
        case 'cdrecord':
            // Helper maneja genisoimage + wodim internamente
            return getCdromHelperCmd('burn_wodim', $unidad, $rutaISO, $volName);
        case 'growisofs':
            return getCdromHelperCmd('burn_growisofs', $unidad, $rutaISO, $volName);
        default:
            error_log("WARN: No se encontró wodim, cdrecord ni growisofs. Instala con: sudo apt-get install wodim genisoimage");
            return null;
    }
}

// ─── Obtener información completa del disco ──────────────────────────────────
/**
 * Retorna array con: ready, blank, rw, has_data, fs, capacity, volname
 */
function getInfoDisco($unidad) {
    if (empty($unidad)) return null;

    $comando = getComandoEstadoDisco($unidad);
    $salida  = [];
    exec($comando . ' 2>&1', $salida);
    $detalle = trim(implode("\n", $salida));

    $info = [
        'ready'    => false,
        'blank'    => false,
        'rw'       => false,
        'has_data' => false,
        'fs'       => '',
        'capacity' => '\u2014',
        'volname'  => '',
        'raw'      => $detalle
    ];

    if (ES_WINDOWS) {
        if (stripos($detalle, 'READY=True') !== false) {
            $info['ready'] = true;
            $esBlanco = stripos($detalle, 'BLANK=True') !== false;
            $esRW     = stripos($detalle, 'RW=True')    !== false;
            
            // Detección matemática de disco virgen (Sectores Libres >= Sectores Totales)
            $secFree = 0; $secTotal = 0;
            if (preg_match('/SEC_FREE=([0-9]+)/i', $detalle, $m)) $secFree = (float)$m[1];
            if (preg_match('/SEC_TOTAL=([0-9]+)/i', $detalle, $m)) $secTotal = (float)$m[1];
            
            if ($secTotal > 0 && ($secFree >= ($secTotal * 0.95))) {
                $esBlanco = true;
            }

            $info['blank'] = $esBlanco;
            $info['rw']    = $esRW;

            if (preg_match('/CAP=([0-9\.]+[GMK]?B?)/i', $detalle, $m)) {
                $info['capacity'] = $m[1];
            }

            $fs = '';
            if (preg_match('/FS=([a-zA-Z0-9_]+)/i', $detalle, $m)) {
                $fs = trim($m[1]);
            }
            $info['fs'] = $fs;

            if (preg_match('/VOLNAME=([^\r\n]+)/i', $detalle, $m)) {
                $info['volname'] = trim($m[1]);
            }

            // Si es blanco por hardware o por sectores, NO tiene datos.
            if ($esBlanco) {
                $info['has_data'] = false;
            } else {
                // Solo si NO es blanco, verificamos si tiene un sistema de archivos conocido o nombre de volumen
                $fsKnown = ($fs !== '' && stripos($fs, 'Unknown') === false && stripos($fs, 'Raw') === false);
                $hasVolName = ($info['volname'] !== '' && $info['volname'] !== 'CD-ROM' && $info['volname'] !== 'Unidad de CD');
                $info['has_data'] = ($fsKnown || $hasVolName);
            }
        }
    } else {
        // Linux
        if (stripos($detalle, 'STATUS=BLANK') !== false) {
            $info['ready']    = true;
            $info['blank']    = true;
            $info['has_data'] = false;
        } elseif (stripos($detalle, 'STATUS=DATA') !== false) {
            $info['ready']    = true;
            $info['blank']    = false;
            $info['has_data'] = true;
        } elseif (stripos($detalle, 'STATUS=EMPTY') === false) {
             // Fallback
             $info['ready'] = true;
        }
        
        $info['rw'] = stripos($detalle, 'RW=True') !== false;

        if (preg_match('/FS=([a-zA-Z0-9_]*)/i', $detalle, $m)) {
            $info['fs'] = trim($m[1]);
        }
        if (preg_match('/VOLNAME=([^\r\n]+)/i', $detalle, $m)) {
            $info['volname'] = trim($m[1]);
        }
        if (preg_match('/CAP=([0-9\.]+[GMK]?B?)/i', $detalle, $m)) {
            $info['capacity'] = $m[1];
        }
    }

    return $info;
}

// ─── Alerta sonora ────────────────────────────────────────────────────────────
function reproducirAlerta($exito = true) {
    if (ES_WINDOWS) {
        if ($exito) {
            exec('powershell -NoProfile -Command "[console]::beep(523,200); [console]::beep(659,200); [console]::beep(784,400)"');
        } else {
            exec('powershell -NoProfile -Command "[console]::beep(200,600)"');
        }
    } else {
        // En Linux: paplay (PulseAudio) o aplay, con fallback al beep del terminal
        if ($exito) {
            @exec('paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null &'
                . ' || aplay /usr/share/sounds/alsa/Front_Center.wav 2>/dev/null &'
                . ' || echo -e "\a" 2>/dev/null');
        } else {
            @exec('paplay /usr/share/sounds/freedesktop/stereo/dialog-warning.oga 2>/dev/null &'
                . ' || echo -e "\a\a" 2>/dev/null');
        }
    }
}

// ─── Clase DB (JSON) ──────────────────────────────────────────────────────────
class DB {
    private static function load() {
        if (!file_exists(DB_JSON)) {
            $initial = [
                'estudios'      => [],
                'logs'          => [],
                'configuracion' => [
                    'modo_automatico'    => '1',
                    'velocidad_grabacion'=> '8x',
                    'carpeta_monitoreo'  => 'estudios_pendientes',
                    'unidad_optica'      => ES_WINDOWS ? 'D:' : '/dev/sr0',
                    'expulsar_al_terminar'=> '1',
                    'nombre_sistema' => 'Robot Médico',
                    'logo_sistema' => '💿',
                    'licencia' => [
                        'activada' => false,
                        'credenciales' => 'admin:123456',
                        'vencimiento' => date('Y-m-d', strtotime('+30 days')),
                        'clave_activacion' => ''
                    ]
                ]
            ];
            self::save($initial);
            return $initial;
        }

        // Lectura robusta con bloqueo compartido y reintentos (evita parpadeo)
        $intentos = 0;
        $data = null;
        while ($intentos < 5) {
            $fp = fopen(DB_JSON, "r");
            if ($fp && flock($fp, LOCK_SH)) {
                $json = "";
                while (!feof($fp)) { $json .= fread($fp, 8192); }
                flock($fp, LOCK_UN);
                fclose($fp);
                
                $data = json_decode($json, true);
                if ($data !== null) break; // Éxito
            }
            if ($fp) fclose($fp);
            $intentos++;
            usleep(100000); // 100ms
        }

        if ($data === null) return ['estudios' => [], 'logs' => [], 'configuracion' => []];

        // Asegurar que existan campos de personalización
        if (isset($data['configuracion'])) {
            $changed = false;
            if (!isset($data['configuracion']['nombre_sistema'])) {
                $data['configuracion']['nombre_sistema'] = 'Robot Médico';
                $changed = true;
            }
            if (!isset($data['configuracion']['logo_sistema'])) {
                $data['configuracion']['logo_sistema'] = '💿';
                $changed = true;
            }
            if (!isset($data['configuracion']['licencia'])) {
                $data['configuracion']['licencia'] = [
                    'activada' => false,
                    'credenciales' => 'admin:123456',
                    'vencimiento' => date('Y-m-d', strtotime('+30 days')),
                    'clave_activacion' => ''
                ];
                $changed = true;
            }
            if ($changed) self::save($data);
        }
        
        return $data;
    }

    private static $licenseCache = null;

    public static function esLicenciaValida() {
        // Usar caché estática durante la ejecución de la petición para evitar múltiples accesos a disco
        if (self::$licenseCache !== null) return self::$licenseCache;

        $config = self::getConfig();
        
        // Si no se puede cargar la config (error de lectura), NO bloqueamos el sistema inmediatamente
        // Esto evita que un parpadeo de lectura lance el modal de licencia
        if (!isset($config['licencia'])) {
            return true; 
        }
        
        $lic = $config['licencia'];
        if (!$lic['activada']) {
            self::$licenseCache = false;
            return false;
        }
        
        $hoy = date('Y-m-d');
        $esValida = ($hoy <= $lic['vencimiento']);
        self::$licenseCache = $esValida;
        return $esValida;
    }

    private static function save($data) {
        $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        if (file_put_contents(DB_JSON, $json, LOCK_EX) === false) {
            error_log("CRITICAL: No se pudo escribir en " . DB_JSON);
            return false;
        }
        return true;
    }

    public static function getEstudios() {
        $data = self::load();
        if (!$data || !isset($data['estudios'])) return [];
        
        // Eliminamos el filtro is_dir que causaba que los estudios desaparecieran intermitentemente
        // Si el estudio está en la DB, se muestra.
        $estudios = array_reverse(array_values($data['estudios']));
        return $estudios;
    }

    public static function getEstudio($id) {
        $data = self::load();
        foreach ($data['estudios'] as $e) {
            if ($e['id'] == $id) {
                if (!isset($e['descripcion'])) $e['descripcion'] = '';
                $e['logs'] = array_values(array_filter($data['logs'], function($l) use ($id) {
                    return $l['estudio_id'] == $id;
                }));
                return $e;
            }
        }
        return null;
    }

    public static function insertEstudio($estudio) {
        $data = self::load();
        $estudio['id']            = count($data['estudios']) + 1;
        $estudio['fecha_creacion']= date('Y-m-d H:i:s');
        $data['estudios'][]       = $estudio;
        self::save($data);
        return $estudio['id'];
    }

    public static function updateEstudio($id, $campos) {
        $data = self::load();
        $updated = false;
        foreach ($data['estudios'] as &$e) {
            if ($e['id'] == $id) {
                foreach ($campos as $k => $v) {
                    $e[$k] = $v;
                }
                if (isset($campos['estado']) && $campos['estado'] === 'finalizado') {
                    $e['fecha_finalizacion'] = date('Y-m-d H:i:s');
                }
                $updated = true;
                break;
            }
        }
        if ($updated) self::save($data);
        return $updated;
    }

    public static function deleteEstudio($id) {
        $data = self::load();
        $originalCount = count($data['estudios']);
        
        $rutaArchivos = null;
        $rutaTemporal = null;

        foreach ($data['estudios'] as $e) {
            if ($e['id'] == $id) {
                $rutaArchivos = $e['ruta_archivos'] ?? null;
                $rutaTemporal = RUTA_RAIZ . 'temp_disco_' . $id;
                break;
            }
        }

        $data['estudios'] = array_values(array_filter($data['estudios'], function($e) use ($id) {
            return $e['id'] != $id;
        }));
        
        if (count($data['estudios']) !== $originalCount) {
            self::save($data);
            
            // ELIMINACIÓN FÍSICA: Si borramos de gestión, borramos los archivos
            // para que el robot no los vuelva a detectar.
            if ($rutaArchivos && is_dir($rutaArchivos)) {
                self::rmDir($rutaArchivos);
            }
            if ($rutaTemporal && is_dir($rutaTemporal)) {
                self::rmDir($rutaTemporal);
            }
            return true;
        }
        return false;
    }

    public static function registrarLog($estudio_id, $evento, $mensaje) {
        $data = self::load();
        $data['logs'][] = [
            'estudio_id' => $estudio_id,
            'evento'     => $evento,
            'mensaje'    => $mensaje,
            'fecha'      => date('Y-m-d H:i:s')
        ];
        // Mantener solo los últimos 1000 logs
        if (count($data['logs']) > 1000) {
            array_shift($data['logs']);
        }
        self::save($data);
    }

    // --- UTILIDADES DE ARCHIVOS COMPARTIDAS ---
    public static function rmDir($dir) {
        if (!is_dir($dir)) return;
        $it = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::CHILD_FIRST
        );
        foreach ($it as $file) {
            if ($file->isDir()) @rmdir($file->getRealPath());
            else @unlink($file->getRealPath());
        }
        @rmdir($dir);
    }

    public static function cpDir($src, $dst) {
        if (!is_dir($src)) return;
        @mkdir($dst, 0755, true);
        $dir = opendir($src);
        while (false !== ($file = readdir($dir))) {
            if ($file === '.' || $file === '..') continue;
            $srcPath = $src . DIRECTORY_SEPARATOR . $file;
            $dstPath = $dst . DIRECTORY_SEPARATOR . $file;
            if (is_dir($srcPath)) {
                self::cpDir($srcPath, $dstPath);
            } else {
                @copy($srcPath, $dstPath);
            }
        }
        closedir($dir);
    }

    public static function getConfig() {
        $data = self::load();
        return $data['configuracion'] ?? [];
    }

    public static function setConfig($config) {
        $data = self::load();
        $data['configuracion'] = array_merge($data['configuracion'] ?? [], $config);
        self::save($data);
    }

    public static function getEstudioPorRuta($ruta) {
        $data = self::load();
        foreach ($data['estudios'] as $e) {
            if (($e['ruta_archivos'] ?? '') === $ruta) return $e;
        }
        return null;
    }
}

// ─── Compatibilidad hacia atrás ───────────────────────────────────────────────
function getConexion()                    { return new DB(); }
function registrarLog($id, $ev, $msg)    { DB::registrarLog($id, $ev, $msg); }
