<?php
/**
 * Salt Change Module
 *
 * Handles regeneration of WordPress security salt keys.
 * Pro plugin can extend functionality via hooks for:
 * - Scheduled salt changes
 * - Pre-change notifications
 * - Extended logging
 *
 * @package     WPUltimateSecurity\UltimateSecurity\Modules\Maintenance
 * @since       1.0.0
 */

declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Modules\Maintenance;

use WPUltimateSecurity\UltimateSecurity\Support\Helpers\Utils;
use WPUltimateSecurity\UltimateSecurity\Support\Helpers\SaltHelper;
use WPUltimateSecurity\UltimateSecurity\Config\Hooks;
use WPUltimateSecurity\UltimateSecurity\Modules\Base\AbstractModule;

/**
 * Salt Change Module
 *
 * @since 1.0.0
 * @since 2.0.0 Migrated to extend AbstractModule.
 */
class SaltChange extends AbstractModule
{
    /**
     * Module ID constant.
     */
    public const MODULE_ID = 'maintenance.salt_change';

    /**
     * Module version.
     */
    protected const VERSION = '2.0.0';

    /**
     * Rate limit cooldown in seconds (5 minutes)
     */
    private const RATE_LIMIT_SECONDS = 300;

    /**
     * Get unique module identifier.
     *
     * @return string
     */
    public static function getId(): string
    {
        return self::MODULE_ID;
    }

    /**
     * Get human-readable module name.
     *
     * @return string
     */
    public static function getName(): string
    {
        return __('Salt Change', 'ultimate-security');
    }

    /**
     * Get module domain.
     *
     * @return string
     */
    public static function getDomain(): string
    {
        return 'maintenance';
    }

    /**
     * Get configuration schema.
     *
     * @return array<string, array{type: string, default: mixed, label?: string}>
     */
    public static function getConfigSchema(): array
    {
        return [
            'enabled' => [
                'type' => 'boolean',
                'default' => true,
                'label' => __('Enable Salt Change', 'ultimate-security'),
            ],
        ];
    }

    /**
     * Get hooks this module fires.
     *
     * @return array<string, array{type: string, params: string[]}>
     */
    public static function getHooks(): array
    {
        return [
            Hooks::BEFORE_SALT_CHANGE => [
                'type' => 'action',
                'params' => ['trigger', 'note'],
            ],
            Hooks::AFTER_SALT_CHANGE => [
                'type' => 'action',
                'params' => ['trigger', 'note', 'result'],
            ],
            Hooks::FILTER_HANDLE_SALT_CHANGE => [
                'type' => 'filter',
                'params' => ['result', 'trigger', 'note'],
            ],
            'ultimate_security_salt_keys_changed' => [
                'type' => 'action',
                'params' => ['trigger'],
            ],
        ];
    }

    /**
     * Check if module is enabled.
     *
     * Salt change is always enabled as it's a core security feature.
     *
     * @return bool
     */
    public static function isEnabled(): bool
    {
        return true;
    }

    /**
     * Boot the module - register hooks.
     *
     * @return void
     */
    public static function boot(): void
    {
        self::action(
            'wp_ajax_ultimate_security_update_wp_config_keys',
            [self::class, 'ultimate_security_update_wp_config_keys']
        );

        self::doAction(Hooks::MODULE_REGISTERED, self::getId(), self::class);
    }

    /**
     * Handle salt key regeneration AJAX request
     *
     * @return void
     */
    public static function ultimate_security_update_wp_config_keys(): void
    {
        // Check user capability first
        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('error_message' => esc_html__('You do not have permission to perform this action.', 'ultimate-security')));
            return;
        }

        if (!isset($_REQUEST['security']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['security'])), 'ultimate_security_nonce')) {
            wp_send_json_error(array('error_message' => esc_html__('Nonce verification failed. Please try again.', 'ultimate-security')));
            return;
        }

        // Check if this is an emergency regeneration (bypasses rate limit)
        $is_emergency = isset($_REQUEST['emergency']) && sanitize_text_field(wp_unslash($_REQUEST['emergency'])) === 'true';

        // Check rate limiting (skip for emergency)
        if (!$is_emergency) {
            $rate_limit_check = self::check_rate_limit();
            if (is_wp_error($rate_limit_check)) {
                wp_send_json_error(array('error_message' => $rate_limit_check->get_error_message()));
                return;
            }
        }

        // Get optional change note
        $change_note = isset($_REQUEST['note']) ? sanitize_textarea_field(wp_unslash($_REQUEST['note'])) : '';

        // Backup current salt keys before changing
        self::backup_current_salts();

        // Determine trigger type
        $trigger = $is_emergency ? 'emergency' : 'manual';

        /**
         * Fires before salt keys are changed.
         * Pro can use this for pre-change notifications.
         *
         * @since 2.0.0
         * @param string $trigger Type of trigger: 'manual' or 'emergency'
         * @param string $note    Optional change note
         */
        do_action(Hooks::BEFORE_SALT_CHANGE, $trigger, $change_note);

        /**
         * Filter to allow Pro to handle the salt change differently.
         * Return an array to skip default handling, false to continue.
         *
         * @since 2.0.0
         * @param false|array $result Return array to skip default, false to continue
         * @param string      $trigger Trigger type
         * @param string      $note    Optional change note
         */
        $handled = apply_filters(Hooks::FILTER_HANDLE_SALT_CHANGE, false, $trigger, $change_note);

        if ($handled !== false) {
            // Pro handled the salt change
            $result = $handled;
        } else {
            // Default: Perform the salt change
            $result = SaltHelper::rewrite_wp_config_keys();
        }

        if (is_wp_error($result)) {
            wp_send_json_error(array('error_message' => $result->get_error_message()));
            return;
        }

        // Log the salt change to history (with note if provided)
        self::log_salt_change($trigger, $change_note);

        /**
         * Fires after salt keys are changed.
         * Pro can use this for extended logging, notifications, etc.
         *
         * @since 2.0.0
         * @param string $trigger Type of trigger
         * @param string $note    Optional change note
         * @param mixed  $result  Result of the operation
         */
        do_action(Hooks::AFTER_SALT_CHANGE, $trigger, $change_note, $result);

        // Legacy hook for backwards compatibility
        do_action('ultimate_security_salt_keys_changed', $trigger);

        wp_send_json_success(array('result' => true));
    }

    /**
     * Check if salt regeneration is rate limited
     *
     * @return true|\WP_Error
     */
    private static function check_rate_limit()
    {
        $last_changed = get_option('ultimate_security_rewrite_wp_config_keys_last_date', null);

        if ($last_changed) {
            $last_change_time = strtotime($last_changed);
            $cooldown_end = $last_change_time + self::RATE_LIMIT_SECONDS;
            $current_time = time();

            if ($current_time < $cooldown_end) {
                $remaining_seconds = $cooldown_end - $current_time;
                $remaining_minutes = ceil($remaining_seconds / 60);

                return new \WP_Error(
                    'rate_limited',
                    sprintf(
                        /* translators: %d: number of minutes remaining */
                        esc_html__('Please wait %d minute(s) before regenerating salt keys again.', 'ultimate-security'),
                        $remaining_minutes
                    )
                );
            }
        }

        return true;
    }

    /**
     * Backup current salt keys to a transient before changing
     *
     * @return void
     */
    private static function backup_current_salts(): void
    {
        $current_salts = array(
            'AUTH_KEY' => defined('AUTH_KEY') ? AUTH_KEY : '',
            'SECURE_AUTH_KEY' => defined('SECURE_AUTH_KEY') ? SECURE_AUTH_KEY : '',
            'LOGGED_IN_KEY' => defined('LOGGED_IN_KEY') ? LOGGED_IN_KEY : '',
            'NONCE_KEY' => defined('NONCE_KEY') ? NONCE_KEY : '',
            'AUTH_SALT' => defined('AUTH_SALT') ? AUTH_SALT : '',
            'SECURE_AUTH_SALT' => defined('SECURE_AUTH_SALT') ? SECURE_AUTH_SALT : '',
            'LOGGED_IN_SALT' => defined('LOGGED_IN_SALT') ? LOGGED_IN_SALT : '',
            'NONCE_SALT' => defined('NONCE_SALT') ? NONCE_SALT : '',
        );

        // Store for 24 hours in case rollback is needed
        set_transient('ultimate_security_salt_backup', $current_salts, DAY_IN_SECONDS);
    }

    /**
     * Log salt change event.
     *
     * Fires an action hook that Pro can use for logging.
     * Free version fires the hook but doesn't log to database.
     *
     * @since 1.0.0
     * @since 2.0.0 Refactored to use hooks instead of direct class check.
     *
     * @param string $trigger Type of trigger: 'manual', 'scheduled', or 'emergency'
     * @param string $note    Optional note/reason for the change
     *
     * @return void
     */
    public static function log_salt_change(string $trigger = 'manual', string $note = ''): void
    {
        // Determine message based on trigger
        $message = match ($trigger) {
            'emergency' => __('Security salt keys regenerated (emergency)', 'ultimate-security'),
            'scheduled' => __('Security salt keys automatically regenerated', 'ultimate-security'),
            default => __('Security salt keys manually regenerated', 'ultimate-security'),
        };

        $severity = $trigger === 'emergency' ? 'critical' : 'high';

        $event_data = [
            'event_type' => 'salt_change',
            'message' => $message,
            'severity' => $severity,
            'metadata' => [
                'trigger' => $trigger,
            ],
        ];

        // Add note to metadata if provided
        if (!empty($note)) {
            $event_data['metadata']['note'] = $note;
        }

        /**
         * Fires when a salt change event should be logged.
         *
         * Pro plugin hooks into this to log to its EventLogger.
         *
         * @since 2.0.0
         *
         * @param string $event_type Event type identifier.
         * @param array  $event_data Event data including message, severity, metadata.
         */
        do_action(Hooks::EVENT_LOGGED, 'salt_change', $event_data);
    }

    /**
     * Get backup salts (if available)
     *
     * @return array|false
     */
    public static function get_backup_salts()
    {
        return get_transient('ultimate_security_salt_backup');
    }
}
