<?php
/**
 * Action: Hooks para sincronizar media con Wasabi S3
 *
 * 1. MEDIA_UPLOAD_FINISH → sube a Wasabi, trunca local a stub 0 bytes
 * 2. MEDIA_DELETE_FILE   → borra de Wasabi, elimina registro SQLite
 * 3. FETCH_MEDIA_STATUS  → redirige descarga a URL pre-firmada de Wasabi
 *
 * @author NICC
 * @date   2026-03-16
 */

class action_plugin_wasabisync extends \dokuwiki\Extension\ActionPlugin {

    /** @var helper_plugin_wasabisync */
    private $helper;

    public function register(\dokuwiki\Extension\EventHandler $controller) {
        $controller->register_hook('MEDIA_UPLOAD_FINISH',  'AFTER',  $this, 'onUploadFinish');
        $controller->register_hook('MEDIA_DELETE_FILE',    'BEFORE', $this, 'onDeleteFile');
        $controller->register_hook('FETCH_MEDIA_STATUS',   'BEFORE', $this, 'onFetchMedia');
    }

    /**
     * Obtiene helper (lazy)
     */
    private function getHelper() {
        if ($this->helper === null) {
            $this->helper = plugin_load('helper', 'wasabisync');
        }
        return $this->helper;
    }

    /**
     * MEDIA_UPLOAD_FINISH (AFTER)
     * Sube archivo recien guardado a Wasabi, luego trunca el local a 0 bytes.
     *
     * $event->data[0] = ruta temporal (ya no existe en AFTER)
     * $event->data[1] = media_id (ej: "compartidos:1:formato_1.docx")
     * $event->data[2] = ruta local final (ej: /data/media/compartidos/1/formato_1.docx)
     */
    public function onUploadFinish(\dokuwiki\Extension\Event $event, $param) {
        $helper = $this->getHelper();
        if (!$helper || !$helper->isReady()) {
            $this->log("HOOK ABORT: helper not ready. helper=" . ($helper ? 'loaded' : 'null') . " isReady=" . ($helper ? ($helper->isReady() ? 'yes' : 'no') : 'n/a'));
            return;
        }

        // Extraer datos del evento
        // DokuWiki pasa: [0] => tmp_name, [1] => media_id, [2] => localFile (en AFTER)
        // En algunas versiones: $event->data es array con: 'loc' => ruta, 'id' => media_id, 'mime' => mime
        $mediaId   = '';
        $localFile = '';
        $mimeType  = 'application/octet-stream';

        if (isset($event->data['id'])) {
            // Formato asociativo (algunas versiones de DokuWiki)
            $mediaId   = $event->data['id'];
            $localFile = $event->data['loc'] ?? mediaFN($mediaId);
            $mimeType  = $event->data['mime'] ?? '';
            if (!$mimeType) {
                $mt = mimetype($localFile, true);
                $mimeType = (!empty($mt[1])) ? $mt[1] : 'application/octet-stream';
            }
        } elseif (is_array($event->data) && isset($event->data[2])) {
            // Formato numerico DokuWiki:
            // [0]=tmp_source, [1]=ruta_destino, [2]=media_id, [3]=mime, [4]=overwrite
            $mediaId   = $event->data[2];
            $localFile = $event->data[1];
            $mimeType  = !empty($event->data[3]) ? $event->data[3] : '';
            if (!$mimeType) {
                $mt = mimetype($localFile, true);
                $mimeType = (!empty($mt[1])) ? $mt[1] : 'application/octet-stream';
            }
        }

        if (!$mediaId || !$localFile || !file_exists($localFile)) return;

        $fileSize  = filesize($localFile);
        $fileMtime = filemtime($localFile);

        // No subir archivos vacios (stubs)
        if ($fileSize === 0) return;

        // Generar key en Wasabi
        $key = $helper->mediaIdToKey($mediaId);

        // Subir a Wasabi
        $result = $helper->putObject($localFile, $key, $mimeType);

        if ($result['ok']) {
            // VERIFICAR que realmente llego a Wasabi con HEAD
            $head = $helper->headObject($key);
            if (!$head['exists']) {
                $this->log("UPLOAD VERIFY FAIL: $mediaId → PUT ok pero HEAD no lo encuentra");
                return; // NO truncar si no se confirmo en Wasabi
            }

            // VERIFICAR tamaño: comparar local vs Wasabi
            $wasabiSize = (int)$head['size'];
            if ($wasabiSize > 0 && $wasabiSize !== $fileSize) {
                $this->log("UPLOAD SIZE MISMATCH: $mediaId → local={$fileSize}, wasabi={$wasabiSize} - archivo local PROTEGIDO");
                return; // NO truncar si el tamaño no coincide
            }

            // Registrar en SQLite
            $helper->dbRegister($mediaId, $key, $fileSize, $fileMtime, $mimeType);

            // Truncar archivo local a 0 bytes (stub)
            $fh = @fopen($localFile, 'w');
            if ($fh) {
                ftruncate($fh, 0);
                fclose($fh);
            } else {
                @file_put_contents($localFile, '');
            }

            $this->log("UPLOAD OK: $mediaId → wasabi:$key ($fileSize bytes)");
        } else {
            // Si falla Wasabi, el archivo local queda intacto
            $this->log("UPLOAD FAIL: $mediaId → " . $result['error']);
        }
    }

    /**
     * MEDIA_DELETE_FILE (BEFORE)
     * Borra el archivo de Wasabi y elimina el registro SQLite.
     *
     * $event->data['id']   = media_id
     * $event->data['path'] = ruta local
     * $event->data['name'] = filename
     */
    public function onDeleteFile(\dokuwiki\Extension\Event $event, $param) {
        $helper = $this->getHelper();
        if (!$helper || !$helper->isReady()) return;

        $mediaId = '';
        if (isset($event->data['id'])) {
            $mediaId = $event->data['id'];
        } elseif (isset($event->data['path'])) {
            // Intentar derivar media_id de la ruta
            $mediaId = $this->pathToMediaId($event->data['path']);
        }

        if (!$mediaId) return;

        // Buscar en SQLite
        $record = $helper->dbLookup($mediaId);
        if (!$record) return; // No esta en Wasabi, nada que hacer

        // Borrar de Wasabi
        $result = $helper->deleteObject($record['wasabi_key']);

        if ($result['ok']) {
            $helper->dbRemove($mediaId);
            $this->log("DELETE OK: $mediaId");
        } else {
            $this->log("DELETE FAIL: $mediaId → " . $result['error']);
        }
    }

    /**
     * FETCH_MEDIA_STATUS (BEFORE)
     * Si el archivo esta en Wasabi (registrado en SQLite), redirige 302
     * a una URL pre-firmada. fetch.php ejecuta send_redirect() automaticamente.
     *
     * Tambien maneja el caso donde la URL tiene &amp;media= literalmente
     * (algunos navegadores/contextos no decodifican &amp; en el href).
     * En ese caso status=400 "Bad request" porque PHP ve 'amp;media' en vez de 'media'.
     */
    public function onFetchMedia(\dokuwiki\Extension\Event $event, $param) {
        $helper = $this->getHelper();
        if (!$helper || !$helper->isReady()) return;

        $data = &$event->data;

        $mediaId = $data['media'] ?? '';

        // FIX: Si status=400 y no hay media, intentar recuperar de 'amp;media'
        // Esto ocurre cuando la URL tiene &amp;media= literalmente
        if ($data['status'] === 400 && !$mediaId) {
            $mediaId = $_GET['amp;media'] ?? $_REQUEST['amp;media'] ?? '';
            if ($mediaId) {
                $mediaId = cleanID($mediaId);
                if ($mediaId) {
                    // Verificar permisos ACL (ya que checkFileStatus no pudo hacerlo)
                    // Superusuarios siempre pasan
                    global $USERINFO;
                    $isSuperuser = isset($_SERVER['REMOTE_USER']) && auth_isadmin($_SERVER['REMOTE_USER'], $USERINFO['grps'] ?? []);
                    if (!$isSuperuser && auth_quickaclcheck(getNS($mediaId) . ':X') < AUTH_READ) {
                        return; // Sin permiso
                    }

                    // Reconstruir datos que checkFileStatus no pudo calcular
                    $file = mediaFN($mediaId);
                    if (file_exists($file)) {
                        $data['media'] = $mediaId;
                        $data['file'] = $file;
                        $data['status'] = 200;
                        // Detectar MIME
                        [$ext, $mime] = mimetype($mediaId, false);
                        $data['mime'] = $mime;
                    }
                }
            }
        }

        // Solo interceptar si el status es 200 (archivo existe localmente como stub)
        if ($data['status'] !== 200) return;

        if (!$mediaId) return;

        // NOTA: No verificamos ACL aqui porque DokuWiki ya lo hizo en checkFileStatus()
        // Si el usuario no tiene permiso, checkFileStatus devuelve 403 y status !== 200,
        // asi que nunca llegamos aqui sin permiso.
        // Para el fix de &amp;media (status 400→200), verificamos ACL arriba al reconstruir.

        // Buscar en SQLite
        $record = $helper->dbLookup($mediaId);
        if (!$record) return; // No esta en Wasabi, servir normalmente

        // Verificar que es un stub (0 bytes) — si tiene contenido real, servir local
        $localFile = $data['file'] ?? '';
        if ($localFile && file_exists($localFile) && filesize($localFile) > 0) {
            return; // Archivo local tiene contenido, no redirigir
        }

        // Generar URL pre-firmada
        $url = $helper->getPresignedUrl($record['wasabi_key']);

        // Establecer redireccion 302 — fetch.php lo maneja automaticamente
        $data['status'] = 302;
        $data['statusmessage'] = $url;
    }

    // ─── Utilidades ───────────────────────────────────────────

    /**
     * Intenta convertir ruta de archivo a media_id
     */
    private function pathToMediaId($path) {
        global $conf;
        $mediaDir = $conf['mediadir'] ?? (DOKU_INC . 'data/media');
        $mediaDir = rtrim(str_replace('\\', '/', $mediaDir), '/') . '/';
        $path = str_replace('\\', '/', $path);

        if (strpos($path, $mediaDir) === 0) {
            $relative = substr($path, strlen($mediaDir));
            return str_replace('/', ':', $relative);
        }
        return '';
    }

    /**
     * Log simple a archivo
     */
    private function log($msg) {
        $logFile = DOKU_INC . 'data/log/wasabisync.log';
        $logDir = dirname($logFile);
        if (!is_dir($logDir)) @mkdir($logDir, 0755, true);
        @file_put_contents($logFile, date('Y-m-d H:i:s') . ' ' . $msg . "\n", FILE_APPEND);
    }
}
