<?php

namespace WPD\Diagnostics\Checks;

use WPD\Database\ReadOnlyDatabase;
use WPD\Database\DatabaseException;
use WPD\Diagnostics\CheckInterface;
use WPD\Domain\CheckResult;
use WPD\Domain\Finding;
use WPD\Domain\ScanContext;

final class DatabaseCheck implements CheckInterface
{
    public function id()
    {
        return 'database';
    }

    public function label()
    {
        return 'Base de datos, plugins y tema';
    }

    public function run(ScanContext $context, array $cursor)
    {
        $credentials = $context->databaseCredentials();
        if ($credentials === null) {
            $finding = new Finding(
                'database.no_credentials',
                'No se ha podido probar la base de datos',
                'advertencia',
                'alta',
                'No hay credenciales completas obtenidas de forma segura.',
                'Sin conexión no se pueden comprobar tablas, URL, plugins activos o tema.',
                array('Si usas HTTPS, introduce los datos en el formulario manual.', 'No compartas la contraseña ni la incluyas en el informe.'),
                'Repite el análisis y comprueba que la fase de base de datos finaliza.',
                'parcial'
            );
            return CheckResult::complete(array($finding), 'no disponible', 'Conexión no configurada.');
        }

        $prefix = isset($credentials['prefix']) ? (string) $credentials['prefix'] : '';
        if (preg_match('/^[A-Za-z0-9_]+$/', $prefix) !== 1) {
            $finding = new Finding(
                'database.invalid_prefix',
                'El prefijo de tablas no es válido',
                'problema confirmado',
                'crítica',
                'El prefijo contiene caracteres que la herramienta no puede aceptar.',
                'WordPress no podrá localizar sus tablas si el prefijo es incorrecto.',
                array('Compara $table_prefix con una copia correcta de wp-config.php.', 'No renombres tablas sin una copia completa y un plan específico.'),
                'Corrige el prefijo y repite el diagnóstico.'
            );
            return CheckResult::complete(array($finding));
        }

        $database = new ReadOnlyDatabase();
        try {
            $database->connect($credentials);
        } catch (\Throwable $error) {
            $category = $error instanceof DatabaseException ? $error->category() : 'unclassified';
            $finding = new Finding(
                'database.connection_failed',
                'WordPress no puede conectarse a la base de datos',
                'problema confirmado',
                'crítica',
                $this->categoryEvidence($category),
                'Sin conexión MySQL, WordPress no puede cargar configuración ni contenido.',
                array('Compara nombre, usuario, servidor y contraseña con el panel del alojamiento.', 'Comprueba que el servidor MySQL esté disponible.', 'Haz una copia de wp-config.php antes de corregir un valor.'),
                'Repite la prueba hasta que la conexión se complete.'
            );
            return CheckResult::complete(array($finding));
        }

        $findings = array();
        $essential = array('options', 'users', 'usermeta', 'posts', 'postmeta', 'terms', 'term_taxonomy', 'term_relationships', 'comments', 'commentmeta');
        $missing = array();
        foreach ($essential as $suffix) {
            $table = $prefix . $suffix;
            try {
                $database->rows('SELECT 1 AS ok FROM `' . $table . '` LIMIT 1');
            } catch (\Throwable $error) {
                $missing[] = $suffix;
            }
        }
        if ($missing) {
            $findings[] = new Finding(
                'database.missing_tables',
                'Faltan tablas esenciales o no son accesibles',
                'problema confirmado',
                'crítica',
                'Tablas esenciales no disponibles: ' . implode(', ', $missing) . '.',
                'WordPress necesita estas tablas para cargar opciones, usuarios y contenido.',
                array('Haz una copia de la base de datos antes de cualquier cambio.', 'Comprueba que $table_prefix coincide con las tablas reales.', 'Si las tablas se perdieron, restaura una copia de seguridad conocida; no crees tablas vacías como sustitución.'),
                'Repite el diagnóstico y confirma que todas las tablas sean legibles.'
            );
        }

        $options = array();
        try {
            $names = array('home', 'siteurl', 'template', 'stylesheet', 'active_plugins');
            $quoted = array_map(array($database, 'quote'), $names);
            $rows = $database->rows('SELECT option_name, option_value FROM `' . $prefix . 'options` WHERE option_name IN (' . implode(',', $quoted) . ')');
            foreach ($rows as $row) {
                $options[$row['option_name']] = $row['option_value'];
            }
        } catch (\Throwable $error) {
            $findings[] = new Finding(
                'database.options_unreadable',
                'No se pudieron leer las opciones básicas',
                'problema confirmado',
                'alta',
                'La tabla de opciones no está disponible para lectura.',
                'Sin estas opciones no se pueden confirmar dominio, tema o plugins activos.',
                array('Comprueba permisos del usuario MySQL y el prefijo de tablas.', 'No concedas permisos de escritura adicionales solo para esta herramienta.'),
                'Repite el diagnóstico cuando la tabla de opciones sea legible.',
                'parcial'
            );
        }

        $this->checkUrl($options, $findings);
        $this->checkTheme($context, $options, $findings);
        $this->checkPlugins($context, $options, $findings);
        $database->close();
        return CheckResult::complete($findings, null, 'Conexión de solo lectura completada desde ' . $credentials['source'] . '.');
    }

    private function checkUrl(array $options, array &$findings)
    {
        if (empty($options['home']) && empty($options['siteurl'])) {
            return;
        }
        $requestHost = isset($_SERVER['HTTP_HOST']) ? preg_replace('/:\d+$/', '', strtolower($_SERVER['HTTP_HOST'])) : '';
        $configured = !empty($options['home']) ? $options['home'] : $options['siteurl'];
        $configuredHost = strtolower((string) parse_url($configured, PHP_URL_HOST));
        if ($requestHost !== '' && $configuredHost !== '' && $requestHost !== $configuredHost) {
            $findings[] = new Finding(
                'database.domain_mismatch',
                'WordPress conserva otro dominio',
                'causa probable',
                'alta',
                'El dominio solicitado no coincide con el dominio guardado por WordPress.',
                'Después de una migración, WordPress puede redirigir al dominio anterior o generar recursos incorrectos.',
                array('Haz una copia de la base de datos.', 'Confirma cuál es el dominio correcto.', 'Actualiza home y siteurl con una herramienta segura que preserve datos serializados.'),
                'Abre el sitio en una ventana privada y confirma que usa el dominio actual.'
            );
        }
    }

    private function checkTheme(ScanContext $context, array $options, array &$findings)
    {
        foreach (array('template', 'stylesheet') as $key) {
            if (empty($options[$key])) {
                continue;
            }
            $slug = basename((string) $options[$key]);
            $path = $context->root() . '/wp-content/themes/' . $slug;
            if (!is_dir($path)) {
                $findings[] = new Finding(
                    'theme.active_missing.' . $key,
                    'El tema activo no está instalado',
                    'problema confirmado',
                    'crítica',
                    'La opción ' . $key . ' apunta a ' . $slug . ', pero su carpeta no existe.',
                    'WordPress intenta cargar un tema que no está disponible.',
                    array('Haz una copia de archivos y base de datos.', 'Restaura la carpeta exacta del tema desde una copia fiable.', 'Si debes cambiar de tema, hazlo con una herramienta de administración segura y no editando datos serializados a mano.'),
                    'Repite el diagnóstico y comprueba que el tema activo exista.'
                );
            }
        }
    }

    private function checkPlugins(ScanContext $context, array $options, array &$findings)
    {
        if (empty($options['active_plugins']) || strlen($options['active_plugins']) > 1048576) {
            return;
        }
        $plugins = @unserialize($options['active_plugins'], array('allowed_classes' => false));
        if (!is_array($plugins)) {
            return;
        }
        $missing = array();
        foreach ($plugins as $plugin) {
            if (!is_string($plugin) || preg_match('#^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+\.php$#', $plugin) !== 1) {
                continue;
            }
            if ($context->guard()->safeFile($context->root() . '/wp-content/plugins/' . $plugin) === null) {
                $missing[] = $plugin;
            }
        }
        if ($missing) {
            $findings[] = new Finding(
                'plugins.active_missing',
                'Hay plugins activos cuyos archivos faltan',
                'problema confirmado',
                'alta',
                'Entradas activas sin archivo principal: ' . implode(', ', array_slice($missing, 0, 20)) . '.',
                'Una actualización o migración incompleta puede dejar activado código que ya no existe.',
                array('Haz una copia de seguridad.', 'Restaura el plugin desde una fuente fiable o desactívalo desde una herramienta segura.', 'No descargues una versión distinta sin comprobar compatibilidad.'),
                'Repite el diagnóstico y confirma que las entradas activas sean coherentes.'
            );
        }
    }

    private function categoryEvidence($category)
    {
        $labels = array(
            'credentials_rejected' => 'Las credenciales fueron rechazadas.',
            'server_unavailable' => 'El servidor de base de datos no está disponible.',
            'database_unavailable' => 'La base de datos no existe o no es accesible.',
            'table_unavailable' => 'Una tabla necesaria no está disponible.',
            'extension_missing' => 'La extensión PHP para MySQL no está disponible.',
        );
        return isset($labels[$category]) ? $labels[$category] : 'Error de base de datos no clasificado · referencia ' . bin2hex(random_bytes(5)) . '.';
    }
}
