<?php

namespace WPD\Diagnostics\Checks;

use WPD\Diagnostics\CheckInterface;
use WPD\Domain\CheckResult;
use WPD\Domain\Finding;
use WPD\Domain\ScanContext;

final class CoreIntegrityCheck implements CheckInterface
{
    public function id()
    {
        return 'core_integrity';
    }

    public function label()
    {
        return 'Integridad del núcleo';
    }

    public function run(ScanContext $context, array $cursor)
    {
        $version = $context->wordpressVersion();
        if ($version === null) {
            return CheckResult::complete(array(), 'no disponible', 'No hay una versión reconocible para elegir el manifiesto.');
        }
        $manifestPath = WPD_ROOT . '/rules/core/' . basename($version) . '.json';
        if (!is_file($manifestPath)) {
            $finding = new Finding(
                'core_integrity.unsupported_version',
                'No hay un catálogo de integridad para esta versión',
                'información',
                'informativa',
                'Versión detectada: ' . $version . '.',
                'La estructura esencial se comprobará, pero no se pueden comparar todos los archivos con hashes conocidos.',
                array('No interpretes esta falta de catálogo como una comprobación correcta.', 'Actualiza la herramienta cuando exista un paquete con soporte para esta versión.'),
                'Consulta en una versión posterior si el catálogo ya está disponible.',
                'parcial'
            );
            return CheckResult::complete(array($finding), 'parcial', 'Manifiesto no disponible.');
        }

        $manifest = json_decode((string) @file_get_contents($manifestPath), true);
        if (!is_array($manifest) || empty($manifest['files']) || !is_array($manifest['files'])) {
            return CheckResult::complete(array(), 'error de comprobación', 'El catálogo de integridad está dañado.');
        }

        $paths = array_keys($manifest['files']);
        $offset = isset($cursor['offset']) ? (int) $cursor['offset'] : 0;
        $modified = isset($cursor['modified']) ? $cursor['modified'] : array();
        $missing = isset($cursor['missing']) ? $cursor['missing'] : array();
        $skipped = isset($cursor['skipped']) ? (int) $cursor['skipped'] : 0;
        $limit = min(count($paths), $offset + 120);

        for ($i = $offset; $i < $limit; $i++) {
            $relative = $paths[$i];
            $candidate = $context->root() . '/' . $relative;
            if (is_link($candidate)) {
                $skipped++;
                continue;
            }
            if (!is_file($candidate)) {
                if (count($missing) < 20) {
                    $missing[] = $relative;
                }
                continue;
            }
            $safe = $context->guard()->safeFile($candidate);
            if ($safe === null || !is_readable($safe)) {
                $skipped++;
                continue;
            }
            $hash = @hash_file('sha256', $safe);
            if (!is_string($hash)) {
                $skipped++;
            } elseif (!hash_equals((string) $manifest['files'][$relative], $hash) && count($modified) < 20) {
                $modified[] = $relative;
            }
        }

        if ($limit < count($paths)) {
            return new CheckResult(false, array('offset' => $limit, 'modified' => $modified, 'missing' => $missing, 'skipped' => $skipped), array(), 'correcta', 'Comparados ' . $limit . ' de ' . count($paths) . ' archivos.');
        }

        $findings = array();
        if ($modified) {
            $findings[] = new Finding(
                'core_integrity.modified_files',
                'Hay archivos del núcleo modificados',
                'problema confirmado',
                'alta',
                'No coinciden con WordPress ' . $version . ': ' . implode(', ', $modified) . '.',
                'Una modificación accidental, una actualización incompleta o una intrusión pueden impedir que WordPress arranque.',
                array('Haz una copia completa antes de actuar.', 'Descarga WordPress ' . $version . ' desde la fuente oficial.', 'Sustituye wp-admin, wp-includes y los archivos del núcleo; conserva wp-content y wp-config.php.', 'Si sospechas una intrusión, analiza después el sitio con una herramienta de seguridad separada.'),
                'Repite el diagnóstico y confirma que los hashes coinciden.'
            );
        }
        if ($missing) {
            $findings[] = new Finding(
                'core_integrity.catalog_files_missing',
                'Faltan archivos incluidos en la versión instalada',
                'problema confirmado',
                'crítica',
                'Ausentes según WordPress ' . $version . ': ' . implode(', ', $missing) . '.',
                'La distribución del núcleo está incompleta. El impacto depende de los archivos concretos; los esenciales se evalúan también en una comprobación separada.',
                array('Haz una copia completa.', 'Descarga la misma versión oficial de WordPress.', 'Restaura el núcleo sin reemplazar wp-content ni wp-config.php.'),
                'Repite el análisis y confirma que ya no faltan archivos.'
            );
        }
        if ($skipped > 0) {
            $findings[] = new Finding(
                'core_integrity.skipped_files',
                'Parte del núcleo no pudo compararse',
                'advertencia',
                'media',
                'Archivos omitidos o no legibles: ' . $skipped . '.',
                'No se puede afirmar que la integridad sea completa.',
                array('Revisa permisos y enlaces simbólicos de los archivos omitidos.', 'No interpretes el resto del análisis como una garantía completa.'),
                'Repite el análisis cuando todos los archivos sean legibles.',
                'parcial'
            );
        }

        return CheckResult::complete($findings, $skipped > 0 ? 'parcial' : null, 'Comparados ' . count($paths) . ' archivos con el catálogo de WordPress ' . $version . '.');
    }
}
