<?php

declare(strict_types=1);

namespace SecurWP\Modules\Firewall\ExtendedProtection;

use SecurWP\Includes\SecurWPKeyManager;
use SecurWP\Modules\Firewall\Blocklist\BlocklistService;
use SecurWP\Modules\Firewall\FirewallConfigService;
use SecurWP\Modules\Firewall\FirewallSettings;
use SecurWP\Modules\Firewall\IpBlocking;
use SecurWP\Modules\Firewall\SearchTrust\SearchTrustScopeBuilder;

// If this file is called directly, abort.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * WAF Configuration Writer
 *
 * Generates the PHP config file read by Bootstrap.php (which runs
 * before WordPress). The output is a plain PHP array - no WordPress
 * functions, classes, or database calls are used at read-time.
 */
class ConfigWriter {

    /**
     * Generate and atomically write the WAF config file.
     *
     * Reads current firewall settings from the WordPress database,
     * transforms them into a format suitable for the bootstrap layer,
     * and writes the result to the data directory.
     *
     * @return bool True on success, false on failure.
     */
    public static function write( ?array $settings = null ): bool {
        if ( is_file( Manager::get_bootstrap_path() ) ) {
            Manager::ensure_bootstrap_current();
        }

        $settings = is_array( $settings )
            ? FirewallSettings::sanitize( $settings )
            : FirewallConfigService::load_effective_settings();
        $trusted_proxies = \SecurWP\Modules\Firewall\RuleEngine::normalize_ip_entries(
            \SecurWP\Includes\IpDetector::resolve_trusted_proxies( $settings ),
            false
        );
        $cloudflare_ranges = [];
        if ( class_exists( '\SecurWP\Includes\ProxyPresets' ) ) {
            $cloudflare_ranges = \SecurWP\Includes\ProxyPresets::get_ranges( \SecurWP\Includes\ProxyPresets::PRESET_CLOUDFLARE );
        }

        $ip_blocking = new IpBlocking( $settings );
        $policy = FirewallSettings::compile_policy_from_sanitized(
            $settings,
            [
                'allowlisted_ips' => $ip_blocking->get_allowlisted_entries(),
                'blocked_ips' => $ip_blocking->get_active_banned_entries(),
            ]
        );
        $search_exception_scope = SearchTrustScopeBuilder::build_from_wordpress();

        $config_payload = [
            'engine_file' => trailingslashit( SECURWP_PLUGIN_DIR ) . 'modules/Firewall/RuleEngine.php',
            'search_trust_core_file' => trailingslashit( SECURWP_PLUGIN_DIR ) . 'modules/Firewall/SearchTrust/SearchTrustCore.php',
            'search_trust_context_file' => trailingslashit( SECURWP_PLUGIN_DIR ) . 'modules/Firewall/SearchTrust/SearchTrustContext.php',
            'search_trust_dns_verifier_file' => trailingslashit( SECURWP_PLUGIN_DIR ) . 'modules/Firewall/SearchTrust/SearchTrustDnsVerifier.php',
            'request_provenance_file' => trailingslashit( SECURWP_PLUGIN_DIR ) . 'includes/RequestProvenance.php',
            'origin_enforcement_mode' => FirewallConfigService::get_origin_enforcement_mode( $settings ),
            'policy' => [
                'allowlisted_ips' => array_values(
                    array_unique(
                        array_merge(
                            (array) $policy['allowlisted_ips'],
                            \SecurWP\Modules\Firewall\FirewallBypass::get_infrastructure_ips()
                        )
                    )
                ),
                'trusted_user_agents' => $policy['trusted_user_agents'],
                'blocked_ips' => $policy['blocked_ips'],
                'blocked_user_agents' => $policy['blocked_user_agents'],
                'bot_policy_enabled' => $policy['bot_policy_enabled'],
                'bot_policy_profile' => $policy['bot_policy_profile'],
                'rate_limit_enabled' => $policy['rate_limit_enabled'],
                'rate_limit_requests' => max( 1, (int) ( $settings['rate_limit_requests'] ?? 60 ) ),
                'country_mode' => $policy['country_mode'],
                'country_codes' => $policy['country_codes'],
            ],
            'community_blocklist' => [
                'enabled' => ! empty( $settings['blocklist_enabled'] ),
                'artifact_signature' => BlocklistService::get_waf_artifact_signature(),
            ],
            'search_exception' => $search_exception_scope,
            'ip_detection'    => [
                'mode'            => sanitize_text_field( $settings['proxy_detection'] ?? 'automatic' ),
                'header'          => sanitize_text_field( $settings['proxy_header'] ?? '' ),
                // Must match runtime IpDetector behavior (preset + custom + constant fallback).
                'trusted_proxies' => $trusted_proxies,
                // Needed for Cloudflare auto-detection parity in pre-WP bootstrap mode.
                'cloudflare_ranges' => \SecurWP\Modules\Firewall\RuleEngine::normalize_ip_entries( $cloudflare_ranges, false ),
            ],
            'internal_verification' => [
                'version' => '1',
                'window'  => 120,
                'secret'  => SecurWPKeyManager::get_server_verification_secret(),
            ],
            'lockout_message' => sanitize_text_field( $settings['lockout_message'] ?? 'Access denied.' ),
        ];
        $policy_signature = hash( 'sha256', (string) wp_json_encode( $config_payload ) );
        $config = $config_payload + [
            'policy_signature' => $policy_signature,
            'updated_at'       => time(),
        ];

        $data_dir    = Manager::get_data_dir();
        $config_file = $data_dir . '/' . 'waf-config.php';

        update_option( 'securwp_waf_policy_signature', '', false );

        if ( ! is_dir( $data_dir ) ) {
            if ( ! wp_mkdir_p( $data_dir ) ) {
                return false;
            }
        }

        $content  = "<?php\n";
        $content .= "// Auto-generated by SecurWP. Do not edit.\n";
        $content .= "// Last updated: " . gmdate( 'Y-m-d H:i:s' ) . " UTC\n";
        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export -- Pre-WordPress bootstrap consumes this generated PHP array directly.
        $content .= "return " . var_export( $config, true ) . ";\n";

        // Atomic write: write to a temporary file then rename.
        // This avoids partial reads if Bootstrap.php is executing concurrently.
        $temp = $config_file . '.tmp.' . self::temp_suffix();

        if ( file_put_contents( $temp, $content, LOCK_EX ) === false ) {
            return false;
        }

        // Set restrictive permissions.
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- Atomic temp-file write for the pre-WordPress bootstrap config.
        @chmod( $temp, 0644 );

        // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename -- Atomic replace keeps the bootstrap config consistent during concurrent reads.
        if ( ! rename( $temp, $config_file ) ) {
            // Clean up the temp file on failure.
            wp_delete_file( $temp );
            return false;
        }

        update_option( 'securwp_waf_policy_signature', $policy_signature, false );

        return true;
    }
    /**
     * Generate a filesystem-safe temporary suffix.
     */
    private static function temp_suffix(): string {
        try {
            return bin2hex( random_bytes( 6 ) );
        } catch ( \Throwable $e ) {
            return dechex( wp_rand( 0, PHP_INT_MAX ) );
        }
    }

}
