<?php

declare(strict_types=1);

namespace ShieldX\Core;

defined('ABSPATH') || exit;

// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Compatibility checks must report actual runtime writability without changing filesystem state.

final class EnvironmentDetector
{
    /** @var string */
    private $contentDir;

    public function __construct(string $contentDir)
    {
        $this->contentDir = rtrim($contentDir, '/\\');
    }

    public static function fromWordPress(): self
    {
        return new self((string) SHIELDX_CONTENT_DIR);
    }

    /**
     * @return array<string,mixed>
     */
    public function detect(): array
    {
        $hasNativeExtension = extension_loaded('shieldx_native');
        $hasSwoole = extension_loaded('swoole');
        $hasFFI = function_exists('ffi_load') && (string) ini_get('ffi.enable') === 'preload';
        $hasAPCu = function_exists('apcu_fetch');
        $hasRedis = class_exists('Redis');
        $hasINotify = function_exists('inotify_init');
        $hasUopz = function_exists('uopz_set_return');
        $hasAutoPrepend = $this->hasAutoPrepend();

        $tier = 'shared';
        if ($hasNativeExtension || $hasSwoole || $hasFFI) {
            $tier = 'enterprise';
        } elseif ($hasAPCu || $hasRedis || $hasINotify || $hasUopz) {
            $tier = 'enhanced';
        }

        $extensions = self::extensionAvailability([
            'apcu' => $hasAPCu,
            'ffi' => $hasFFI,
            'inotify' => $hasINotify,
            'redis' => $hasRedis,
            'shieldx_native' => $hasNativeExtension,
            'swoole' => $hasSwoole,
            'uopz' => $hasUopz,
        ]);
        $functions = self::functionAvailability();
        $memoryLimit = (string) ini_get('memory_limit');
        $memoryLimitBytes = self::parseSizeToBytes($memoryLimit);
        $maxExecutionTime = (int) ini_get('max_execution_time');
        $capabilities = [
            'waf' => 'php_log_only',
            'rasp' => $hasUopz ? 'uopz_limited' : 'heuristic',
            'file_monitor' => $hasINotify ? 'realtime' : 'scheduled',
            'rate_limit' => $hasRedis ? 'redis' : ($hasAPCu ? 'apcu' : 'file_db'),
            'ai_inference' => $hasFFI ? 'onnx_local' : 'classical_php',
            'cron' => 'wp_cron',
        ];
        $signals = [
            'php_version' => PHP_VERSION,
            'wp_version' => function_exists('get_bloginfo') ? get_bloginfo('version') : null,
            'php_sapi' => PHP_SAPI,
            'memory_limit' => $memoryLimit,
            'memory_limit_bytes' => $memoryLimitBytes,
            'max_execution_time' => $maxExecutionTime,
            'extensions' => $extensions,
            'functions' => $functions,
            'content_writable' => is_writable($this->contentDir),
            'has_auto_prepend' => $hasAutoPrepend,
            'capabilities' => $capabilities,
            'wpdb_available' => isset($GLOBALS['wpdb']) || class_exists('\wpdb'),
            'wp_cron_disabled' => defined('DISABLE_WP_CRON') && DISABLE_WP_CRON,
        ];

        return [
            'tier' => $tier,
            'php_version' => PHP_VERSION,
            'php_sapi' => PHP_SAPI,
            'wp_version' => $signals['wp_version'],
            'memory_limit' => $memoryLimit,
            'memory_limit_bytes' => $memoryLimitBytes,
            'max_execution_time' => $maxExecutionTime,
            'has_native_extension' => $hasNativeExtension,
            'has_swoole' => $hasSwoole,
            'has_ffi' => $hasFFI,
            'has_apcu' => $hasAPCu,
            'has_redis' => $hasRedis,
            'has_inotify' => $hasINotify,
            'has_uopz' => $hasUopz,
            'has_auto_prepend' => $hasAutoPrepend,
            'content_writable' => is_writable($this->contentDir),
            'capabilities' => $capabilities,
            'php_extensions' => self::extensionInventory($extensions),
            'php_functions' => self::functionInventory($functions),
            'compatibility' => self::compatibilityFromSignals($signals),
        ];
    }

    public static function parseSizeToBytes(string $value): int
    {
        $value = trim($value);
        if ($value === '' || $value === '-1') {
            return -1;
        }

        $unit = strtolower(substr($value, -1));
        $number = (float) $value;

        switch ($unit) {
            case 'g':
                $number *= 1024;
                // no break
            case 'm':
                $number *= 1024;
                // no break
            case 'k':
                $number *= 1024;
                break;
        }

        return (int) $number;
    }

    /**
     * @param array<string,mixed> $signals
     * @return array<string,mixed>
     */
    public static function compatibilityFromSignals(array $signals): array
    {
        $extensions = isset($signals['extensions']) && is_array($signals['extensions']) ? $signals['extensions'] : [];
        $functions = isset($signals['functions']) && is_array($signals['functions']) ? $signals['functions'] : [];
        $phpVersion = (string) ($signals['php_version'] ?? PHP_VERSION);
        $wpVersion = $signals['wp_version'] ?? null;
        $memoryLimitBytes = (int) ($signals['memory_limit_bytes'] ?? -1);
        $maxExecutionTime = (int) ($signals['max_execution_time'] ?? 0);
        $contentWritable = !empty($signals['content_writable']);
        $wpdbAvailable = !empty($signals['wpdb_available']);
        $wpCronDisabled = !empty($signals['wp_cron_disabled']);
        $phpOk = version_compare($phpVersion, '7.4', '>=');
        $wpKnown = is_scalar($wpVersion) && (string) $wpVersion !== '';
        $wpOk = $wpKnown && version_compare((string) $wpVersion, '6.0', '>=');
        $jsonOk = self::signalEnabled($extensions, 'json') && self::signalEnabled($functions, 'json_encode') && self::signalEnabled($functions, 'json_decode');
        $pcreOk = self::signalEnabled($extensions, 'pcre') && self::signalEnabled($functions, 'preg_match');
        $fileStateOk = self::signalEnabled($functions, 'file_put_contents') && self::signalEnabled($functions, 'flock') && $contentWritable;
        $cryptoOk = self::signalEnabled($functions, 'hash_hmac') && self::signalEnabled($functions, 'random_bytes');
        $curlOk = self::signalEnabled($extensions, 'curl') || self::signalEnabled($functions, 'curl_init');
        $stringOk = self::signalEnabled($extensions, 'mbstring') || self::signalEnabled($functions, 'mb_strlen');
        $dbOk = $wpdbAvailable || self::signalEnabled($extensions, 'mysqli') || self::signalEnabled($extensions, 'pdo_mysql');
        $memoryOk = $memoryLimitBytes === -1 || $memoryLimitBytes >= 67108864;
        $memoryRecommended = $memoryLimitBytes === -1 || $memoryLimitBytes >= 134217728;
        $executionOk = $maxExecutionTime === 0 || $maxExecutionTime >= 30;
        $checks = [
            self::compatCheck('php_version', 'PHP runtime', $phpOk ? 'ready' : 'missing', 'required', $phpVersion, '>= 7.4', ['All SHIELD-X modules'], 'Runtime code is PHP 7.4-compatible and requires PHP 7.4 or newer.'),
            self::compatCheck('wordpress_version', 'WordPress version', $wpOk ? 'ready' : ($wpKnown ? 'warning' : 'warning'), 'recommended', $wpKnown ? (string) $wpVersion : 'unknown', '>= 6.0 recommended', ['Admin UI', 'REST API', 'WP hooks'], 'Older WordPress versions may miss hooks or REST behavior used by the dashboard.'),
            self::compatCheck('json', 'JSON support', $jsonOk ? 'ready' : 'missing', 'required', $jsonOk ? 'loaded' : 'missing', 'json extension and functions', ['REST status', 'Audit logging', 'Rate state'], 'SHIELD-X stores status and log records as JSON.'),
            self::compatCheck('pcre', 'PCRE regex support', $pcreOk ? 'ready' : 'missing', 'required', $pcreOk ? 'loaded' : 'missing', 'pcre extension and preg_match', ['WAF engine'], 'The bundled WAF rules are regex based.'),
            self::compatCheck('file_state', 'Writable local state', $fileStateOk ? 'ready' : 'missing', 'required', $fileStateOk ? 'writable' : 'not writable', 'writable wp-content plus file locks', ['Audit logging', 'Rate limiting', 'Login lockouts', 'File monitor'], 'Shared-hosting mode needs local storage for logs, state, and snapshots.'),
            self::compatCheck('crypto', 'Crypto primitives', $cryptoOk ? 'ready' : 'missing', 'required', $cryptoOk ? 'available' : 'missing', 'hash_hmac and random_bytes', ['Audit chain', 'Admin gate cookie'], 'HMAC and secure random values are needed for trustworthy local state.'),
            self::compatCheck('database', 'WordPress database layer', $dbOk ? 'ready' : 'warning', 'required', $dbOk ? 'available' : 'unknown', 'wpdb with MySQL-compatible database', ['Schema installer', 'Settings'], 'Activation creates SHIELD-X tables through WordPress database APIs.'),
            self::compatCheck('memory', 'PHP memory limit', $memoryOk ? ($memoryRecommended ? 'ready' : 'warning') : 'missing', 'recommended', (string) ($signals['memory_limit'] ?? 'unknown'), '64M minimum, 128M recommended', ['Scanner jobs', 'File monitor', 'Admin dashboard'], 'Low memory can interrupt scans and larger admin reports.'),
            self::compatCheck('execution_time', 'Execution time', $executionOk ? 'ready' : 'warning', 'recommended', (string) $maxExecutionTime . 's', '30s or unlimited recommended', ['Scanner jobs', 'Maintenance'], 'Short execution windows may require smaller scan batches.'),
            self::compatCheck('curl', 'HTTP client support', $curlOk ? 'ready' : 'warning', 'recommended', $curlOk ? 'available' : 'missing', 'curl extension recommended', ['Threat intelligence', 'Vulnerability scanner', 'Cloud checks'], 'Remote feed and vulnerability checks need outbound HTTP support when enabled.'),
            self::compatCheck('mbstring', 'Multibyte string support', $stringOk ? 'ready' : 'warning', 'recommended', $stringOk ? 'available' : 'missing', 'mbstring recommended', ['Normalization', 'Admin display'], 'Payload normalization is safer when multibyte string handling is available.'),
            self::compatCheck('wp_cron', 'WP-Cron', $wpCronDisabled ? 'warning' : 'ready', 'recommended', $wpCronDisabled ? 'disabled' : 'enabled', 'WP-Cron or server cron', ['Maintenance', 'File monitor', 'Scanner jobs'], 'Scheduled jobs need WP-Cron or an external cron runner.'),
        ];

        $modules = self::moduleCompatibility([
            'php_ok' => $phpOk,
            'json_ok' => $jsonOk,
            'pcre_ok' => $pcreOk,
            'file_state_ok' => $fileStateOk,
            'crypto_ok' => $cryptoOk,
            'curl_ok' => $curlOk,
            'db_ok' => $dbOk,
            'wp_cron_disabled' => $wpCronDisabled,
            'extensions' => $extensions,
            'capabilities' => isset($signals['capabilities']) && is_array($signals['capabilities']) ? $signals['capabilities'] : [],
        ]);

        return [
            'summary' => self::compatibilitySummary($checks, $modules),
            'checks' => $checks,
            'php_extensions' => self::extensionInventory($extensions),
            'php_functions' => self::functionInventory($functions),
            'modules' => $modules,
        ];
    }

    /**
     * @param array<string,bool> $overrides
     * @return array<string,bool>
     */
    private static function extensionAvailability(array $overrides): array
    {
        $names = [
            'json',
            'pcre',
            'mysqli',
            'pdo_mysql',
            'curl',
            'mbstring',
            'openssl',
            'sodium',
            'zlib',
            'zip',
            'gd',
            'imagick',
            'fileinfo',
            'dom',
            'xml',
            'libxml',
            'apcu',
            'redis',
            'inotify',
            'uopz',
            'ffi',
            'swoole',
            'shieldx_native',
        ];
        $availability = [];

        foreach ($names as $name) {
            $availability[$name] = extension_loaded($name);
        }

        foreach ($overrides as $name => $available) {
            $availability[$name] = (bool) $available;
        }

        return $availability;
    }

    /**
     * @return array<string,bool>
     */
    private static function functionAvailability(): array
    {
        $names = [
            'preg_match',
            'json_encode',
            'json_decode',
            'file_put_contents',
            'flock',
            'hash_hmac',
            'random_bytes',
            'curl_init',
            'mb_strlen',
            'openssl_encrypt',
            'sodium_crypto_generichash',
            'wp_next_scheduled',
        ];
        $availability = [];

        foreach ($names as $name) {
            $availability[$name] = function_exists($name);
        }

        return $availability;
    }

    /**
     * @param array<string,bool> $extensions
     * @return array<int,array<string,mixed>>
     */
    private static function extensionInventory(array $extensions): array
    {
        $items = [
            ['json', 'required', 'REST status, audit logging, state files', 'Required for JSON status, logs, and cached state.'],
            ['pcre', 'required', 'Regex WAF', 'Required for bundled WAF rule evaluation.'],
            ['mysqli', 'wordpress', 'WordPress database', 'Needed by WordPress on most MySQL-compatible hosts.'],
            ['pdo_mysql', 'wordpress', 'WordPress database fallback', 'Useful database driver on some hosts.'],
            ['curl', 'recommended', 'Threat intel, vulnerability scanner', 'Recommended for future remote feed checks.'],
            ['mbstring', 'recommended', 'Payload normalization, admin text', 'Recommended for safer string handling.'],
            ['openssl', 'recommended', 'Crypto and TLS', 'Recommended for secure outbound checks and local crypto helpers.'],
            ['sodium', 'optional', 'Modern crypto', 'Optional modern crypto helper.'],
            ['zlib', 'recommended', 'Backups and compressed feeds', 'Recommended for compressed artifacts and feeds.'],
            ['zip', 'optional', 'Patch/backup bundles', 'Optional packaging helper.'],
            ['gd', 'optional', 'Report images', 'Optional image/report helper.'],
            ['imagick', 'optional', 'Report images', 'Optional image/report accelerator.'],
            ['fileinfo', 'recommended', 'File scanner', 'Recommended for file type checks.'],
            ['dom', 'recommended', 'HTML/XML parsing', 'Recommended for scanner parsers.'],
            ['xml', 'recommended', 'XML parsing', 'Recommended for feeds and scanner parsers.'],
            ['libxml', 'recommended', 'XML parsing', 'Recommended for feeds and scanner parsers.'],
            ['apcu', 'accelerator', 'Rate limiting cache', 'Optional faster local cache; file state is the fallback.'],
            ['redis', 'accelerator', 'Rate limiting cache', 'Optional distributed cache for higher-traffic sites.'],
            ['inotify', 'accelerator', 'File monitor', 'Optional realtime file-change detection.'],
            ['uopz', 'accelerator', 'RASP hooks', 'Optional enhanced runtime instrumentation.'],
            ['ffi', 'accelerator', 'Local AI inference', 'Optional native model bridge.'],
            ['swoole', 'accelerator', 'Enterprise runtime', 'Optional long-running worker support.'],
            ['shieldx_native', 'accelerator', 'Native SHIELD-X runtime', 'Optional native acceleration layer.'],
        ];
        $inventory = [];

        foreach ($items as $item) {
            $name = $item[0];
            $loaded = self::signalEnabled($extensions, $name);
            $level = $item[1];
            $inventory[] = [
                'name' => $name,
                'loaded' => $loaded,
                'level' => $level,
                'status' => $loaded ? 'ready' : ($level === 'required' ? 'missing' : ($level === 'accelerator' || $level === 'optional' ? 'optional' : 'warning')),
                'used_by' => $item[2],
                'impact' => $item[3],
            ];
        }

        return $inventory;
    }

    /**
     * @param array<string,bool> $functions
     * @return array<int,array<string,mixed>>
     */
    private static function functionInventory(array $functions): array
    {
        $items = [
            ['preg_match', 'required', 'Regex WAF', 'Required for WAF pattern matching.'],
            ['json_encode', 'required', 'REST status, audit logging', 'Required to write JSON status and logs.'],
            ['json_decode', 'required', 'Rate state, log readers', 'Required to read JSON state and logs.'],
            ['file_put_contents', 'required', 'Storage, logs, state files', 'Required for shared-hosting file state.'],
            ['flock', 'required', 'Rate limiting, login lockouts', 'Required for safe concurrent state writes.'],
            ['hash_hmac', 'required', 'Audit chain, cookies', 'Required for HMAC-protected values.'],
            ['random_bytes', 'required', 'Secure tokens', 'Required for secure random values.'],
            ['curl_init', 'recommended', 'Threat intel, vulnerability scanner', 'Recommended for outbound HTTP checks.'],
            ['mb_strlen', 'recommended', 'Text normalization', 'Recommended for multibyte-safe strings.'],
            ['openssl_encrypt', 'recommended', 'Future encrypted local cache', 'Recommended for encrypted local artifacts.'],
            ['sodium_crypto_generichash', 'optional', 'Modern crypto', 'Optional modern crypto helper.'],
            ['wp_next_scheduled', 'wordpress', 'WP-Cron scheduler', 'Available when WordPress is loaded.'],
        ];
        $inventory = [];

        foreach ($items as $item) {
            $name = $item[0];
            $available = self::signalEnabled($functions, $name);
            $level = $item[1];
            $inventory[] = [
                'name' => $name,
                'available' => $available,
                'level' => $level,
                'status' => $available ? 'ready' : ($level === 'required' ? 'missing' : ($level === 'optional' ? 'optional' : 'warning')),
                'used_by' => $item[2],
                'impact' => $item[3],
            ];
        }

        return $inventory;
    }

    /**
     * @param array<string,mixed> $signals
     * @return array<int,array<string,mixed>>
     */
    private static function moduleCompatibility(array $signals): array
    {
        $extensions = isset($signals['extensions']) && is_array($signals['extensions']) ? $signals['extensions'] : [];
        $capabilities = isset($signals['capabilities']) && is_array($signals['capabilities']) ? $signals['capabilities'] : [];
        $modules = [];
        $baseOk = !empty($signals['php_ok']) && !empty($signals['json_ok']);

        $modules[] = self::moduleStatus(
            'Web Application Firewall',
            $baseOk && !empty($signals['pcre_ok']) ? 'ready' : 'missing',
            'Log-only regex engine',
            'PHP, JSON, PCRE',
            $baseOk && !empty($signals['pcre_ok']) ? [] : ['PHP/JSON/PCRE support'],
            $baseOk && !empty($signals['pcre_ok']) ? 'Bundled regex rules can evaluate requests.' : 'WAF rules cannot run without regex and JSON support.'
        );
        $modules[] = self::moduleStatus(
            'Audit Logging',
            $baseOk && !empty($signals['file_state_ok']) ? 'ready' : 'missing',
            'Local JSON logs',
            'JSON and writable file storage',
            $baseOk && !empty($signals['file_state_ok']) ? [] : ['Writable local storage'],
            $baseOk && !empty($signals['file_state_ok']) ? 'Security events can be written locally.' : 'Security events may be lost without writable storage.'
        );
        $modules[] = self::moduleStatus(
            'Rate Limiting',
            $baseOk && !empty($signals['file_state_ok']) ? 'ready' : 'missing',
            (string) ($capabilities['rate_limit'] ?? 'file_db'),
            'File storage; Redis/APCu optional',
            $baseOk && !empty($signals['file_state_ok']) ? [] : ['Writable locked state file'],
            self::signalEnabled($extensions, 'redis') || self::signalEnabled($extensions, 'apcu')
                ? 'Fast cache backend is available.'
                : 'File-backed shared-hosting fallback is active.'
        );
        $modules[] = self::moduleStatus(
            'Login Brute-Force Protection',
            $baseOk && !empty($signals['file_state_ok']) ? 'ready' : 'missing',
            'IP and username lockouts',
            'WordPress auth hooks and local state',
            $baseOk && !empty($signals['file_state_ok']) ? [] : ['Writable login lockout state'],
            'Failed login counters use local file state; no PHP sessions required.'
        );
        $modules[] = self::moduleStatus(
            'Stealth Hardening',
            $baseOk && !empty($signals['crypto_ok']) ? 'ready' : 'missing',
            'Signature hiding and admin gate',
            'WordPress hooks and HMAC helpers',
            $baseOk && !empty($signals['crypto_ok']) ? [] : ['hash_hmac/random_bytes'],
            'Admin gate and signature suppression can run in normal plugin hooks.'
        );
        $modules[] = self::moduleStatus(
            'File Integrity Monitor',
            $baseOk && !empty($signals['file_state_ok']) ? (self::signalEnabled($extensions, 'inotify') ? 'ready' : 'warning') : 'missing',
            self::signalEnabled($extensions, 'inotify') ? 'realtime' : 'scheduled',
            'Writable storage; inotify optional',
            $baseOk && !empty($signals['file_state_ok']) ? [] : ['Writable baseline storage'],
            self::signalEnabled($extensions, 'inotify') ? 'Realtime file events are possible.' : 'Scheduled scanning fallback is available; realtime extension is not loaded.'
        );
        $modules[] = self::moduleStatus(
            'Vulnerability Scanner',
            !empty($signals['curl_ok']) ? 'ready' : 'warning',
            'Local checks; remote feeds when enabled',
            'cURL recommended',
            !empty($signals['curl_ok']) ? [] : ['curl extension for remote checks'],
            !empty($signals['curl_ok']) ? 'Outbound feed checks can use cURL.' : 'Remote feed checks will be limited until cURL is available.'
        );
        $modules[] = self::moduleStatus(
            'Maintenance Scheduler',
            !empty($signals['wp_cron_disabled']) ? 'warning' : 'ready',
            'WP-Cron',
            'WP-Cron or external cron',
            !empty($signals['wp_cron_disabled']) ? ['External cron runner'] : [],
            !empty($signals['wp_cron_disabled']) ? 'WP-Cron is disabled; configure server cron for scheduled work.' : 'Scheduled maintenance can run through WP-Cron.'
        );
        $modules[] = self::moduleStatus(
            'AI / Behavior Profiling',
            self::signalEnabled($extensions, 'ffi') ? 'ready' : 'warning',
            self::signalEnabled($extensions, 'ffi') ? 'local native bridge' : 'classical PHP',
            'PHP fallback; FFI optional',
            [],
            self::signalEnabled($extensions, 'ffi') ? 'Native local inference bridge is available.' : 'Classical PHP heuristics are available; native local AI is not loaded.'
        );

        return $modules;
    }

    /**
     * @param array<int,array<string,mixed>> $checks
     * @param array<int,array<string,mixed>> $modules
     * @return array<string,mixed>
     */
    private static function compatibilitySummary(array $checks, array $modules): array
    {
        $summary = [
            'status' => 'ready',
            'ready' => 0,
            'warning' => 0,
            'missing' => 0,
            'optional' => 0,
            'module_ready' => 0,
            'module_warning' => 0,
            'module_missing' => 0,
        ];

        foreach ($checks as $check) {
            $status = (string) ($check['status'] ?? 'warning');
            if (isset($summary[$status])) {
                $summary[$status]++;
            }
        }

        foreach ($modules as $module) {
            $status = (string) ($module['status'] ?? 'warning');
            $key = 'module_' . $status;
            if (isset($summary[$key])) {
                $summary[$key]++;
            }
        }

        if ($summary['missing'] > 0 || $summary['module_missing'] > 0) {
            $summary['status'] = 'missing';
        } elseif ($summary['warning'] > 0 || $summary['module_warning'] > 0) {
            $summary['status'] = 'warning';
        }

        return $summary;
    }

    /**
     * @param array<int,string> $modules
     * @return array<string,mixed>
     */
    private static function compatCheck(string $id, string $label, string $status, string $level, string $detected, string $required, array $modules, string $impact): array
    {
        return [
            'id' => $id,
            'label' => $label,
            'status' => $status,
            'level' => $level,
            'detected' => $detected,
            'required' => $required,
            'used_by' => $modules,
            'impact' => $impact,
        ];
    }

    /**
     * @param array<int,string> $missing
     * @return array<string,mixed>
     */
    private static function moduleStatus(string $module, string $status, string $mode, string $dependency, array $missing, string $notes): array
    {
        return [
            'module' => $module,
            'status' => $status,
            'mode' => $mode,
            'dependency' => $dependency,
            'missing' => $missing,
            'notes' => $notes,
        ];
    }

    /**
     * @param array<string,mixed> $signals
     */
    private static function signalEnabled(array $signals, string $key): bool
    {
        return !empty($signals[$key]);
    }

    private function hasAutoPrepend(): bool
    {
        $autoPrepend = (string) ini_get('auto_prepend_file');

        return $autoPrepend !== '' && strtolower($autoPrepend) !== 'none';
    }
}
