<?php

declare(strict_types=1);

namespace ShieldX\Core;

defined('ABSPATH') || exit;

use ShieldX\Maintenance\LogRotationSettings;
use ShieldX\Maintenance\RestorePointManager;
use ShieldX\Scanner\ScheduledScanSettings;
use ShieldX\Security\Hardening;
use ShieldX\Security\HardeningSettings;
use ShieldX\Security\HumanChallengeSettings;
use ShieldX\Security\LockdownSettings;
use ShieldX\Security\LoginSecuritySettings;
use ShieldX\Security\MinimalWaf;
use ShieldX\Security\RateLimitSettings;
use ShieldX\Security\UserProfiler;
use ShieldX\Security\WafSettings;

final class SetupModeManager
{
    public const OPTION_NAME = 'shieldx_setup_mode_state';
    public const MODE_NORMAL = 'normal';
    public const MODE_EXTREME = 'extreme';
    public const MODE_PANIC = 'panic';

    private const PROFILE_VERSION = 2;

    /** @var array<int,string> */
    private const DEFAULT_FILE_SCOPES = [
        'root',
        'core-admin',
        'core-includes',
        'plugins',
        'themes',
        'uploads',
        'mu-plugins',
        'content-other',
    ];

    /** @var Plugin|null */
    private $plugin;

    /** @var callable|null */
    private $challengeProvisioner;

    /** @var callable|null */
    private $restorePointCreator;

    public function __construct(?Plugin $plugin = null, ?callable $challengeProvisioner = null, ?callable $restorePointCreator = null)
    {
        $this->plugin = $plugin;
        $this->challengeProvisioner = $challengeProvisioner;
        $this->restorePointCreator = $restorePointCreator;
    }

    /**
     * @return array<string,array<string,mixed>>
     */
    public static function profiles(): array
    {
        return [
            self::MODE_NORMAL => self::definition(
                __('Normal Security', 'xerotact-shield-x-security'),
                'good',
                __('Balanced protection', 'xerotact-shield-x-security'),
                __('Learning firewall, safe hardening, daily scans, and 90-day log rotation.', 'xerotact-shield-x-security'),
                __('Review Normal Security', 'xerotact-shield-x-security'),
                [
                    'waf_mode' => 'log_only',
                    'waf_rule_action_policy' => 'defaults',
                    'request_limit' => 120,
                    'burst_limit' => 40,
                    'login_failure_limit' => 5,
                    'login_lockout_seconds' => 900,
                    'scan_recurrence' => 'daily',
                    'log_retention_days' => 90,
                    'challenge_login' => false,
                    'challenge_fullscreen' => false,
                    'challenge_waf' => false,
                    'xmlrpc_policy' => 'preserve',
                    'disable_admin_scripts_concatenation' => false,
                    'visitor_profiler' => false,
                    'lockdown_available' => false,
                ]
            ),
            self::MODE_EXTREME => self::definition(
                __('Extreme Security', 'xerotact-shield-x-security'),
                'warn',
                __('Strict daily operation', 'xerotact-shield-x-security'),
                __('Enforced firewall, stronger lockouts, twice-daily scans, and 14-day logs.', 'xerotact-shield-x-security'),
                __('Review Extreme Security', 'xerotact-shield-x-security'),
                [
                    'waf_mode' => 'enforce',
                    'waf_rule_action_policy' => 'block_high_critical',
                    'request_limit' => 90,
                    'burst_limit' => 25,
                    'login_failure_limit' => 3,
                    'login_lockout_seconds' => 1800,
                    'scan_recurrence' => 'twicedaily',
                    'log_retention_days' => 14,
                    'challenge_login' => true,
                    'challenge_fullscreen' => false,
                    'challenge_waf' => false,
                    'xmlrpc_policy' => 'block',
                    'disable_admin_scripts_concatenation' => true,
                    'visitor_profiler' => false,
                    'lockdown_available' => false,
                ]
            ),
            self::MODE_PANIC => self::definition(
                __('Panic Mode', 'xerotact-shield-x-security'),
                'danger',
                __('Incident response posture', 'xerotact-shield-x-security'),
                __('Aggressive enforcement, hourly scans, full-screen challenge, visitor profiling, and 7-day logs.', 'xerotact-shield-x-security'),
                __('Review Panic Mode', 'xerotact-shield-x-security'),
                [
                    'waf_mode' => 'enforce',
                    'waf_rule_action_policy' => 'challenge_medium',
                    'request_limit' => 60,
                    'burst_limit' => 20,
                    'login_failure_limit' => 2,
                    'login_lockout_seconds' => 3600,
                    'scan_recurrence' => 'hourly',
                    'log_retention_days' => 7,
                    'challenge_login' => true,
                    'challenge_fullscreen' => true,
                    'challenge_waf' => true,
                    'xmlrpc_policy' => 'block',
                    'disable_admin_scripts_concatenation' => true,
                    'visitor_profiler' => true,
                    'lockdown_available' => true,
                ]
            ),
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public static function profile(string $mode): array
    {
        $profiles = self::profiles();
        $mode = self::sanitizeMode($mode);

        return $profiles[$mode];
    }

    public static function sanitizeMode(string $mode): string
    {
        $normalized = strtolower(trim($mode));
        $mode = sanitize_key($normalized);
        if ($mode !== $normalized) {
            return self::MODE_NORMAL;
        }

        return in_array($mode, [self::MODE_NORMAL, self::MODE_EXTREME, self::MODE_PANIC], true) ? $mode : self::MODE_NORMAL;
    }

    /**
     * @return array<string,mixed>
     */
    public static function lastApplied(): array
    {
        $state = function_exists('get_option') ? get_option(self::OPTION_NAME, []) : [];

        return is_array($state) ? $state : [];
    }

    /**
     * This is intentionally read-only. Apply() reuses the same profile contract
     * so the review dialog cannot promise settings that are not applied.
     *
     * @param array<string,mixed> $options
     * @return array<string,mixed>
     */
    public function preview(string $mode, array $options = []): array
    {
        $mode = self::sanitizeMode($mode);
        $profile = self::profile($mode);
        $settings = isset($profile['settings']) && is_array($profile['settings']) ? $profile['settings'] : [];
        $challenge = HumanChallengeSettings::fromWordPress();
        $challengeConfigured = $challenge->isConfigured();
        $applyLockdown = !empty($settings['lockdown_available']) && !empty($options['apply_lockdown']);
        $changes = [];
        $warnings = [];

        $waf = WafSettings::fromWordPress()->all();
        $wafMode = (string) ($settings['waf_mode'] ?? 'log_only');
        $changes[__('Firewall', 'xerotact-shield-x-security')] = [
            $this->changeLine(
                __('Firewall mode', 'xerotact-shield-x-security'),
                (string) ($waf['mode'] ?? 'log_only'),
                $wafMode
            ),
            $this->wafActionSummary((string) ($settings['waf_rule_action_policy'] ?? 'defaults')),
        ];

        $rate = RateLimitSettings::fromWordPress()->all();
        $changes[__('Rate Limiting', 'xerotact-shield-x-security')] = [
            $this->changeLine(
                __('Global request rate', 'xerotact-shield-x-security'),
                sprintf('%d / %ds (%s)', (int) ($rate['request_limit'] ?? 120), (int) ($rate['request_window_seconds'] ?? 60), (string) ($rate['request_rate_mode'] ?? 'log_only')),
                sprintf('%d / 60s (%s)', (int) ($settings['request_limit'] ?? 120), $wafMode)
            ),
            $this->changeLine(
                __('Login lockout', 'xerotact-shield-x-security'),
                sprintf('%d failures / %d minutes', (int) ($rate['login_failure_limit'] ?? 5), max(1, (int) (($rate['login_lockout_seconds'] ?? 900) / 60))),
                sprintf('%d failures / %d minutes', (int) ($settings['login_failure_limit'] ?? 5), max(1, (int) (($settings['login_lockout_seconds'] ?? 900) / 60)))
            ),
        ];

        $login = LoginSecuritySettings::fromWordPress()->all();
        $changes[__('Login Security', 'xerotact-shield-x-security')] = [
            $this->changeLine(
                __('Login protection', 'xerotact-shield-x-security'),
                !empty($login['enabled']) ? __('enabled', 'xerotact-shield-x-security') : __('disabled', 'xerotact-shield-x-security'),
                __('enabled', 'xerotact-shield-x-security')
            ),
        ];

        $challengeEnabled = !empty($settings['challenge_login']);
        $changes[__('Human Challenge', 'xerotact-shield-x-security')] = [
            $challengeEnabled
                ? ($challengeConfigured
                    ? __('Challenge is enabled for the selected login and firewall surfaces.', 'xerotact-shield-x-security')
                    : __('Local Challenge credentials will be created only because none are configured.', 'xerotact-shield-x-security'))
                : __('Login, reset, and WAF Challenge placements are disabled without changing configured credentials.', 'xerotact-shield-x-security'),
        ];
        if ($challengeEnabled && !$challengeConfigured && !HumanChallengeSettings::secretStorageReady()) {
            $warnings[] = __('Challenge credentials cannot be created until WordPress salts and OpenSSL are available. WAF rules will block instead of challenging.', 'xerotact-shield-x-security');
        }

        $changes[__('Hardening', 'xerotact-shield-x-security')] = [
            __('Security headers, file-editor protection, generic login errors, pingback protection, author-scan blocking, bot protection, direct-core protection, sensitive-file protection, directory-browsing protection, and data PHP execution protection are enabled.', 'xerotact-shield-x-security'),
            (string) ($settings['xmlrpc_policy'] ?? 'preserve') === 'block'
                ? __('XML-RPC is blocked.', 'xerotact-shield-x-security')
                : __('XML-RPC remains unchanged.', 'xerotact-shield-x-security'),
            __('Admin gate is unchanged.', 'xerotact-shield-x-security'),
        ];

        $scans = ScheduledScanSettings::fromWordPress()->sections();
        $recurrence = (string) ($settings['scan_recurrence'] ?? 'daily');
        $changes[__('Scans and Logs', 'xerotact-shield-x-security')] = [
            $this->changeLine(
                __('Scheduled file scans', 'xerotact-shield-x-security'),
                (string) (($scans['file']['recurrence'] ?? 'daily')),
                $recurrence
            ),
            $this->changeLine(
                __('Scheduled database scans', 'xerotact-shield-x-security'),
                (string) (($scans['database']['recurrence'] ?? 'daily')),
                $recurrence
            ),
            $this->changeLine(
                __('Log retention', 'xerotact-shield-x-security'),
                sprintf('%d %s', (int) (LogRotationSettings::fromWordPress()->all()['retention_days'] ?? 90), __('days', 'xerotact-shield-x-security')),
                sprintf('%d %s', (int) ($settings['log_retention_days'] ?? 90), __('days', 'xerotact-shield-x-security'))
            ),
        ];

        $changes[__('Privacy and Lockdown', 'xerotact-shield-x-security')] = [
            !empty($settings['visitor_profiler'])
                ? __('Visitor profiling is enabled for incident response.', 'xerotact-shield-x-security')
                : __('Visitor profiling is disabled.', 'xerotact-shield-x-security'),
            empty($settings['lockdown_available'])
                ? __('Filesystem Lockdown is not applied.', 'xerotact-shield-x-security')
                : ($applyLockdown
                    ? __('Filesystem Lockdown will be applied using the existing policy.', 'xerotact-shield-x-security')
                    : __('Filesystem Lockdown remains unchanged until explicitly selected.', 'xerotact-shield-x-security')),
        ];

        return [
            'mode' => $mode,
            'label' => (string) ($profile['label'] ?? $mode),
            'profile_version' => self::PROFILE_VERSION,
            'will_change' => $changes,
            'will_preserve' => self::preservedAreas(),
            'warnings' => $warnings,
            'lockdown_requested' => $applyLockdown,
        ];
    }

    /**
     * @param array<string,mixed> $options
     * @return array<string,mixed>
     */
    public function apply(string $mode, array $options = []): array
    {
        $mode = self::sanitizeMode($mode);
        $profile = self::profile($mode);
        $settings = isset($profile['settings']) && is_array($profile['settings']) ? $profile['settings'] : [];
        $warnings = [];
        $appliedAreas = [];
        $createRestorePoint = !array_key_exists('create_restore_point', $options) || !empty($options['create_restore_point']);
        $applyLockdown = !empty($settings['lockdown_available']) && !empty($options['apply_lockdown']);
        $preview = $this->preview($mode, ['apply_lockdown' => $applyLockdown]);

        if ($createRestorePoint) {
            $this->createRestorePoint($mode, $warnings);
        }

        $this->applySafely(__('Login Security', 'xerotact-shield-x-security'), function () use (&$warnings): void {
            $this->applyLoginSecurity($warnings);
        }, $warnings, $appliedAreas);

        $challengeReady = (bool) $this->applySafely(__('Human Challenge', 'xerotact-shield-x-security'), function () use ($settings, &$warnings): bool {
            return $this->applyHumanChallenge($settings, $warnings);
        }, $warnings, $appliedAreas, false, true);

        $this->applySafely(__('Firewall', 'xerotact-shield-x-security'), function () use ($settings, $challengeReady): void {
            $this->applyWaf($settings, $challengeReady);
        }, $warnings, $appliedAreas);

        $this->applySafely(__('Rate Limiting', 'xerotact-shield-x-security'), function () use ($settings): void {
            $this->applyRateLimiting($settings);
        }, $warnings, $appliedAreas);

        $this->applySafely(__('Hardening', 'xerotact-shield-x-security'), function () use ($settings, &$warnings): void {
            $this->applyHardening($settings, $warnings);
        }, $warnings, $appliedAreas);

        $this->applySafely(__('Scheduled Scans', 'xerotact-shield-x-security'), function () use ($settings, &$warnings): void {
            $this->applySchedules($settings, $warnings);
        }, $warnings, $appliedAreas);

        $this->applySafely(__('Log Rotation', 'xerotact-shield-x-security'), function () use ($settings): void {
            $this->applyLogRotation($settings);
        }, $warnings, $appliedAreas);

        $this->applySafely(__('Visitor Profiler', 'xerotact-shield-x-security'), function () use ($settings): void {
            UserProfiler::saveSettings(!empty($settings['visitor_profiler']));
        }, $warnings, $appliedAreas);

        $lockdownApplied = false;
        if ($applyLockdown) {
            $lockdownApplied = (bool) $this->applySafely(__('Filesystem Lockdown', 'xerotact-shield-x-security'), function () use ($applyLockdown, &$warnings): bool {
                return $this->applyLockdownPolicy($applyLockdown, $warnings);
            }, $warnings, $appliedAreas, false, true);
        }

        $state = [
            'mode' => $mode,
            'label' => (string) ($profile['label'] ?? $mode),
            'profile_version' => self::PROFILE_VERSION,
            'applied_at' => self::now(),
            'user_id' => function_exists('get_current_user_id') ? (int) get_current_user_id() : 0,
            'warnings' => $warnings,
            'managed_areas' => $appliedAreas,
            'preserved_areas' => array_keys((array) ($preview['will_preserve'] ?? [])),
            'lockdown_applied' => $lockdownApplied,
        ];

        if (function_exists('update_option')) {
            try {
                update_option(self::OPTION_NAME, $state, false);
            } catch (\Throwable $error) {
                $warnings[] = sprintf(
                    /* translators: %s is an option-storage error message. */
                    __('Setup mode state could not be saved: %s', 'xerotact-shield-x-security'),
                    $error->getMessage()
                );
                $state['warnings'] = $warnings;
                \ShieldX\Support\ErrorLog::write('SHIELD-X setup mode state save failed: ' . $error->getMessage());
            }
        }

        if (function_exists('do_action')) {
            try {
                do_action('shieldx_settings_updated');
            } catch (\Throwable $error) {
                \ShieldX\Support\ErrorLog::write('SHIELD-X setup mode settings-updated hook failed: ' . $error->getMessage());
            }
        }

        return $state;
    }

    /**
     * @return array<string,mixed>
     */
    private static function definition(
        string $label,
        string $tone,
        string $headline,
        string $summary,
        string $button,
        array $settings
    ): array {
        return [
            'label' => $label,
            'tone' => $tone,
            'headline' => $headline,
            'summary' => $summary,
            'button' => $button,
            'settings' => $settings,
        ];
    }

    /**
     * @return array<string,array<int,string>>
     */
    private static function preservedAreas(): array
    {
        return [
            __('Firewall and Rate Limiting', 'xerotact-shield-x-security') => [
                __('Disabled rules, allowlists, plugin exceptions, trusted-proxy settings, advanced inspection thresholds, and endpoint-specific rate limits.', 'xerotact-shield-x-security'),
            ],
            __('Login and Recovery', 'xerotact-shield-x-security') => [
                __('2FA role requirements, session policies, magic login, configured recovery details, and existing recovery tokens.', 'xerotact-shield-x-security'),
            ],
            __('Hardening', 'xerotact-shield-x-security') => [
                __('Admin-gate state and key, Force SSL, HSTS, strict cookies, and deployment-specific web-server configuration.', 'xerotact-shield-x-security'),
            ],
            __('Scans, Backups, and Storage', 'xerotact-shield-x-security') => [
                __('Selected scan scopes and limits, database scan depth, recipients, runner tokens, Backup file types, backup storage, restore-point retention, and backup schedules.', 'xerotact-shield-x-security'),
            ],
            __('Notifications, Updates, and Extensions', 'xerotact-shield-x-security') => [
                __('Notification routing, license state, private update settings, and all add-on settings.', 'xerotact-shield-x-security'),
            ],
        ];
    }

    private function wafActionSummary(string $policy): string
    {
        if ($policy === 'challenge_medium') {
            return __('High and critical rules block. Medium rules challenge when available, otherwise block.', 'xerotact-shield-x-security');
        }

        if ($policy === 'block_high_critical') {
            return __('High and critical rules block.', 'xerotact-shield-x-security');
        }

        return __('Bundled rule actions return to their defaults.', 'xerotact-shield-x-security');
    }

    /**
     * @param array<int,string> $warnings
     */
    private function createRestorePoint(string $mode, array &$warnings): void
    {
        try {
            if (is_callable($this->restorePointCreator)) {
                call_user_func($this->restorePointCreator, $mode);
                return;
            }

            RestorePointManager::fromWordPress()->create('setup_mode_' . $mode, [], function_exists('get_current_user_id') ? (int) get_current_user_id() : 0, true);
        } catch (\Throwable $throwable) {
            $warnings[] = sprintf(
                /* translators: %s is an error message from restore point creation. */
                __('Restore point could not be created: %s', 'xerotact-shield-x-security'),
                $throwable->getMessage()
            );
        }
    }

    /**
     * @param array<int,string> $warnings
     */
    private function applyLoginSecurity(array &$warnings): void
    {
        $settings = LoginSecuritySettings::fromWordPress();
        $settings->save(['enabled' => true]);

        try {
            $settings->ensureEmergencyRecoveryToken();
        } catch (\Throwable $throwable) {
            $warnings[] = sprintf(
                /* translators: %s is an error message from recovery token setup. */
                __('Recovery URL could not be ensured: %s', 'xerotact-shield-x-security'),
                $throwable->getMessage()
            );
        }
    }

    /**
     * @param array<string,mixed> $profile
     * @param array<int,string> $warnings
     */
    private function applyHumanChallenge(array $profile, array &$warnings): bool
    {
        $settings = HumanChallengeSettings::fromWordPress();
        $enable = !empty($profile['challenge_login']);
        $configured = $settings->isConfigured();

        if ($enable && !$configured) {
            try {
                if (is_callable($this->challengeProvisioner)) {
                    call_user_func($this->challengeProvisioner, $settings);
                } else {
                    $settings->saveGeneratedStandaloneDefaults();
                }
                $settings = HumanChallengeSettings::fromWordPress();
                $configured = $settings->isConfigured();
            } catch (\Throwable $throwable) {
                $warnings[] = sprintf(
                    /* translators: %s is an error message from human challenge setup. */
                    __('Human Challenge credentials could not be created: %s', 'xerotact-shield-x-security'),
                    $throwable->getMessage()
                );
            }
        }

        $settings->save([
            'login_enabled' => $enable && $configured,
            'login_fullscreen_enabled' => $enable && !empty($profile['challenge_fullscreen']) && $configured,
            'password_reset_enabled' => $enable && $configured,
            'waf_enabled' => !empty($profile['challenge_waf']) && $configured,
        ]);

        return !$enable || $configured;
    }

    /**
     * @param array<string,mixed> $profile
     */
    private function applyWaf(array $profile, bool $challengeReady): void
    {
        $settings = WafSettings::fromWordPress();
        $current = $settings->all();
        $extra = $current;
        $extra['mode'] = (string) ($profile['waf_mode'] ?? 'log_only');
        $extra['rule_actions'] = $this->ruleActionsForProfile($profile, $challengeReady);

        $disabledRules = isset($current['disabled_rules']) && is_array($current['disabled_rules']) ? $current['disabled_rules'] : [];
        $pluginWhitelist = isset($current['plugin_whitelist']) && is_array($current['plugin_whitelist']) ? $current['plugin_whitelist'] : [];
        $settings->save($disabledRules, $pluginWhitelist, $extra);
    }

    /**
     * @return array<string,string>
     */
    private function ruleActionsForProfile(array $profile, bool $challengeReady): array
    {
        $policy = (string) ($profile['waf_rule_action_policy'] ?? 'defaults');
        if ($policy === 'defaults') {
            return [];
        }

        $actions = [];
        foreach (MinimalWaf::defaultRules() as $rule) {
            $id = isset($rule['id']) ? (string) $rule['id'] : '';
            $severity = strtolower((string) ($rule['severity'] ?? ''));
            if ($id === '') {
                continue;
            }

            if (in_array($severity, ['critical', 'high'], true)) {
                $actions[$id] = 'block';
            } elseif ($policy === 'challenge_medium' && $severity === 'medium') {
                $actions[$id] = $challengeReady ? 'challenge' : 'block';
            }
        }

        return $actions;
    }

    /**
     * @param array<string,mixed> $profile
     */
    private function applyRateLimiting(array $profile): void
    {
        $current = RateLimitSettings::fromWordPress()->all();
        RateLimitSettings::fromWordPress()->save([
            'request_rate_enabled' => true,
            'request_rate_mode' => (string) ($profile['waf_mode'] ?? 'log_only'),
            'request_limit' => (int) ($profile['request_limit'] ?? 120),
            'request_window_seconds' => 60,
            'burst_limit' => (int) ($profile['burst_limit'] ?? 40),
            'burst_window_seconds' => 10,
            'login_protection_enabled' => true,
            'login_failure_limit' => (int) ($profile['login_failure_limit'] ?? 5),
            'login_window_seconds' => 900,
            'login_lockout_seconds' => (int) ($profile['login_lockout_seconds'] ?? 900),
            'endpoint_profiles' => (array) ($current['endpoint_profiles'] ?? []),
        ]);
    }

    /**
     * @param array<string,mixed> $profile
     * @param array<int,string> $warnings
     */
    private function applyHardening(array $profile, array &$warnings): void
    {
        $settings = HardeningSettings::fromWordPress();
        $current = $settings->all();
        $adminGateEnabled = !empty($current['admin_gate_enabled']);
        $forceSslEnabled = !empty($current['force_ssl_enabled']) || $this->safeHttpsAvailable();
        $advanced = [
            'generic_login_errors' => true,
            'block_xmlrpc' => (string) ($profile['xmlrpc_policy'] ?? 'preserve') === 'block' || !empty($current['block_xmlrpc']),
            'disable_pingbacks' => true,
            'disable_admin_scripts_concatenation' => !empty($profile['disable_admin_scripts_concatenation']) || !empty($current['disable_admin_scripts_concatenation']),
            'block_author_scans' => true,
            'bot_protection_enabled' => true,
            'direct_core_access_protection' => true,
            'sensitive_file_protection' => true,
            'directory_browsing_protection' => true,
            'data_php_execution_protection' => true,
            'admin_gate_strict_cookie' => !empty($current['admin_gate_strict_cookie']),
        ];

        $adminGateKey = (string) ($current['admin_gate_key'] ?? '');
        if ($adminGateKey === '' || $adminGateKey === HardeningSettings::RESERVED_ADMIN_GATE_KEY) {
            $adminGateKey = HardeningSettings::fromWordPress()->adminGateKey();
        }

        $settings->save(
            true,
            $adminGateEnabled,
            $adminGateKey,
            $forceSslEnabled,
            true,
            isset($current['response_headers']) && is_array($current['response_headers'])
                ? $current['response_headers']
                : HardeningSettings::defaultResponseHeaders(),
            $advanced
        );

        try {
            $fileEdit = Hardening::syncFileEditConfig(true, '', 'shieldx_apply_setup_mode');
            if (empty($fileEdit['ok']) && !empty($fileEdit['message'])) {
                $warnings[] = (string) $fileEdit['message'];
            }
        } catch (\Throwable $throwable) {
            $warnings[] = sprintf(
                /* translators: %s is an error message from wp-config.php synchronization. */
                __('File editor hardening could not be written: %s', 'xerotact-shield-x-security'),
                $throwable->getMessage()
            );
        }

        try {
            $webRules = Hardening::syncWebServerRules($settings, '', '', null, 'shieldx_apply_setup_mode');
            if (empty($webRules['ok']) && !empty($webRules['messages']) && is_array($webRules['messages'])) {
                $warnings[] = implode(' ', array_map('strval', $webRules['messages']));
            }
        } catch (\Throwable $throwable) {
            $warnings[] = sprintf(
                /* translators: %s is an error message from web-server rule synchronization. */
                __('Web-server hardening could not be written: %s', 'xerotact-shield-x-security'),
                $throwable->getMessage()
            );
        }

        try {
            if ($adminGateEnabled) {
                Hardening::setAccessCookie($settings);
            } else {
                Hardening::clearAccessCookie();
            }
        } catch (\Throwable $throwable) {
            $warnings[] = sprintf(
                /* translators: %s is an error message from admin gate cookie setup. */
                __('Admin gate cookie could not be updated: %s', 'xerotact-shield-x-security'),
                $throwable->getMessage()
            );
        }
    }

    private function safeHttpsAvailable(): bool
    {
        if (!function_exists('is_ssl') || !is_ssl()) {
            return false;
        }

        $home = function_exists('home_url') ? (string) home_url('/') : '';
        $admin = function_exists('admin_url') ? (string) admin_url('/') : '';

        return stripos($home, 'https://') === 0 && stripos($admin, 'https://') === 0;
    }

    /**
     * @param array<string,mixed> $profile
     * @param array<int,string> $warnings
     */
    private function applySchedules(array $profile, array &$warnings): void
    {
        $settings = ScheduledScanSettings::fromWordPress();
        $sections = $settings->sections();
        $recurrence = (string) ($profile['scan_recurrence'] ?? 'daily');

        foreach (['file', 'database', 'vulnerability'] as $type) {
            $section = isset($sections[$type]) && is_array($sections[$type]) ? $sections[$type] : [];
            $changes = [
                'enabled' => true,
                'recurrence' => $recurrence,
            ];
            if ($this->isPristineScheduleSection($type, $section)) {
                $changes['scopes'] = $type === 'file' ? self::DEFAULT_FILE_SCOPES : ($type === 'database' ? ['database'] : []);
            }
            $sections[$type] = array_merge($section, $changes);
        }

        $settings->save(['sections' => $sections]);

        try {
            SchedulerBridge::refresh();
        } catch (\Throwable $throwable) {
            $warnings[] = sprintf(
                /* translators: %s is an error message from scheduled scan setup. */
                __('Scheduled scans could not be registered: %s', 'xerotact-shield-x-security'),
                $throwable->getMessage()
            );
        }
    }

    /**
     * @param array<string,mixed> $profile
     */
    private function applyLogRotation(array $profile): void
    {
        $current = LogRotationSettings::fromWordPress()->all();
        LogRotationSettings::fromWordPress()->save([
            'enabled' => true,
            'retention_days' => (int) ($profile['log_retention_days'] ?? 90),
            'max_files' => (int) ($current['max_files'] ?? 90),
            'targets' => isset($current['targets']) && is_array($current['targets']) ? $current['targets'] : [],
            'excluded_file_logs' => isset($current['excluded_file_logs']) && is_array($current['excluded_file_logs']) ? $current['excluded_file_logs'] : [],
        ]);
    }

    /**
     * @param array<int,string> $warnings
     */
    private function applyLockdownPolicy(bool $applyLockdown, array &$warnings): bool
    {
        if (!$applyLockdown) {
            return false;
        }

        if (!$this->plugin) {
            $warnings[] = __('Lockdown was requested, but the runtime coordinator is not available.', 'xerotact-shield-x-security');

            return false;
        }

        try {
            $policy = LockdownSettings::fromWordPress()->policy();
            $result = $this->plugin->applyLockdown($policy, true, false, 'shieldx_apply_setup_mode');
            if (empty($result['ok'])) {
                $warnings[] = (string) ($result['message'] ?? __('Lockdown could not be applied.', 'xerotact-shield-x-security'));

                return false;
            }

            return true;
        } catch (\Throwable $throwable) {
            $warnings[] = sprintf(
                /* translators: %s is an error message from lockdown application. */
                __('Lockdown could not be applied: %s', 'xerotact-shield-x-security'),
                $throwable->getMessage()
            );

            return false;
        }
    }

    /**
     * @param array<string,mixed> $section
     */
    private function isPristineScheduleSection(string $type, array $section): bool
    {
        if (!empty($section['enabled']) || !empty($section['last_run_at']) || !empty($section['last_scan_id']) || !empty($section['last_status'])) {
            return false;
        }
        if (!empty($section['email_reports_enabled']) || !empty($section['external_runner_enabled']) || !empty($section['recipients'])) {
            return false;
        }

        $scopes = isset($section['scopes']) && is_array($section['scopes']) ? array_values($section['scopes']) : [];
        if ($type === 'file') {
            return $scopes === [];
        }

        return $scopes === ($type === 'database' ? ['database'] : []);
    }

    /**
     * @param array<int,string> $warnings
     * @param array<int,string> $appliedAreas
     * @param mixed $fallback
     * @return mixed
     */
    private function applySafely(string $area, callable $operation, array &$warnings, array &$appliedAreas, $fallback = null, bool $requireSuccess = false)
    {
        try {
            $result = $operation();
            if (!$requireSuccess || $result === true) {
                $appliedAreas[] = $area;
            }

            return $result;
        } catch (\Throwable $throwable) {
            $warnings[] = sprintf(
                /* translators: 1: setup area label, 2: error message. */
                __('%1$s could not be applied: %2$s', 'xerotact-shield-x-security'),
                $area,
                $throwable->getMessage()
            );

            return $fallback;
        }
    }

    private function changeLine(string $label, string $from, string $to): string
    {
        if ($from === $to) {
            return sprintf(
                /* translators: 1: setting label, 2: setting value. */
                __('%1$s stays %2$s.', 'xerotact-shield-x-security'),
                $label,
                $to
            );
        }

        return sprintf(
            /* translators: 1: setting label, 2: current value, 3: new value. */
            __('%1$s: %2$s to %3$s.', 'xerotact-shield-x-security'),
            $label,
            $from,
            $to
        );
    }

    private static function now(): string
    {
        if (function_exists('current_time')) {
            return (string) current_time('mysql');
        }

        return gmdate('Y-m-d H:i:s');
    }
}
