<?php

declare(strict_types=1);

if ( ! defined( 'ABSPATH' ) ) {
    $sitefort_waf_storage_script = (string) filter_input( INPUT_SERVER, 'SCRIPT_FILENAME', FILTER_UNSAFE_RAW );
    if ( is_string( $sitefort_waf_storage_script ) && realpath( $sitefort_waf_storage_script ) === realpath( __FILE__ ) ) {
        http_response_code( 404 );
        exit;
    }
}

if ( ! function_exists( 'sitefort_waf_runtime_uses_mysqli' ) ) {
    function sitefort_waf_runtime_uses_mysqli( array $config ): bool {
        $backend = sitefort_waf_runtime_storage_backend( $config );

        return $backend === 'mysqli';
    }
}

if ( ! function_exists( 'sitefort_waf_runtime_storage_backend' ) ) {
    function sitefort_waf_runtime_storage_backend( array $config ): string {
        $backend = strtolower( trim( (string) ( $config['runtime_storage_backend'] ?? '' ) ) );
        if ( $backend === '' ) {
            $backend = sitefort_waf_runtime_storage_constant_backend();
        }

        return $backend === 'mysqli' ? 'mysqli' : 'filesystem';
    }
}

if ( ! function_exists( 'sitefort_waf_runtime_storage_constant_backend' ) ) {
    function sitefort_waf_runtime_storage_constant_backend(): string {
        $backend = '';
        if ( defined( 'SITEFORT_WAF_RUNTIME_STORAGE_BACKEND' ) ) {
            $backend = (string) constant( 'SITEFORT_WAF_RUNTIME_STORAGE_BACKEND' );
        } else {
            $server_backend = filter_input( INPUT_SERVER, 'SITEFORT_WAF_RUNTIME_STORAGE_BACKEND', FILTER_UNSAFE_RAW );
            if ( is_string( $server_backend ) ) {
                $backend = $server_backend;
            }
        }

        $backend = strtolower( trim( str_replace( "\0", '', $backend ) ) );

        return $backend === 'mysqli' ? 'mysqli' : '';
    }
}

if ( ! function_exists( 'sitefort_waf_runtime_storage_forced_mysqli' ) ) {
    function sitefort_waf_runtime_storage_forced_mysqli(): bool {
        return sitefort_waf_runtime_storage_constant_backend() === 'mysqli';
    }
}

if ( ! function_exists( 'sitefort_waf_runtime_uses_mysqli' ) ) {
    function sitefort_waf_runtime_uses_mysqli( array $config ): bool {
        return sitefort_waf_runtime_uses_mysqli( $config );
    }
}

if ( ! function_exists( 'sitefort_waf_runtime_storage_backend' ) ) {
    function sitefort_waf_runtime_storage_backend( array $config ): string {
        return sitefort_waf_runtime_storage_backend( $config );
    }
}

if ( ! function_exists( 'sitefort_waf_runtime_storage_constant_backend' ) ) {
    function sitefort_waf_runtime_storage_constant_backend(): string {
        return sitefort_waf_runtime_storage_constant_backend();
    }
}

if ( ! function_exists( 'sitefort_waf_runtime_storage_forced_mysqli' ) ) {
    function sitefort_waf_runtime_storage_forced_mysqli(): bool {
        return sitefort_waf_runtime_storage_forced_mysqli();
    }
}

if ( ! function_exists( 'sitefort_waf_storage_server_value' ) ) {
    function sitefort_waf_storage_server_value( string $key ): string {
        $value = filter_input( INPUT_SERVER, $key, FILTER_UNSAFE_RAW );
        return is_string( $value ) ? str_replace( "\0", '', $value ) : '';
    }
}

if ( ! function_exists( 'sitefort_waf_db_context' ) ) {
    /**
     * @return array{db:\mysqli,table_prefix:string,blog_id:int,verification_site_token:string}|null
     */
    function sitefort_waf_db_context( array $config, string $data_dir ): ?array {
        static $cache = [];

        if ( ! sitefort_waf_runtime_uses_mysqli( $config ) || ! class_exists( '\mysqli' ) ) {
            return null;
        }

        $encoded_cache_key = json_encode(
            [
                $config['db_table_prefix'] ?? '',
                $config['blog_id'] ?? 1,
                $config['internal_verification']['nonce_site_token'] ?? '',
                $data_dir,
            ]
        );
        $cache_key = md5( is_string( $encoded_cache_key ) ? $encoded_cache_key : $data_dir );

        if ( array_key_exists( $cache_key, $cache ) ) {
            return $cache[ $cache_key ];
        }

        $credentials = sitefort_waf_db_credentials( $config, $data_dir );
        if ( $credentials === null ) {
            $cache[ $cache_key ] = null;
            return null;
        }

        $table_prefix = sitefort_waf_valid_table_prefix( (string) ( $config['db_table_prefix'] ?? '' ) );
        if ( $table_prefix === '' ) {
            $table_prefix = sitefort_waf_valid_table_prefix( (string) ( $credentials['table_prefix'] ?? '' ) );
        }

        if ( $table_prefix === '' ) {
            $cache[ $cache_key ] = null;
            return null;
        }

        $host_parts = sitefort_waf_parse_db_host_parts( (string) $credentials['host'] );
        $db = @mysqli_init();
        if ( ! $db instanceof \mysqli ) {
            $cache[ $cache_key ] = null;
            return null;
        }

        @mysqli_options( $db, MYSQLI_OPT_CONNECT_TIMEOUT, 2 );

        $connected = @mysqli_real_connect(
            $db,
            $host_parts['host'],
            (string) $credentials['user'],
            (string) $credentials['password'],
            (string) $credentials['database'],
            $host_parts['port'],
            $host_parts['socket']
        );

        if ( ! $connected ) {
            $cache[ $cache_key ] = null;
            return null;
        }

        $charset = trim( (string) ( $credentials['charset'] ?? '' ) );
        if ( $charset !== '' ) {
            @mysqli_set_charset( $db, $charset );
        }

        $context = [
            'db' => $db,
            'table_prefix' => $table_prefix,
            'blog_id' => max( 1, (int) ( $config['blog_id'] ?? 1 ) ),
            'verification_site_token' => substr(
                (string) ( $config['internal_verification']['nonce_site_token'] ?? '' ),
                0,
                128
            ),
        ];

        $cache[ $cache_key ] = $context;

        return $context;
    }
}

if ( ! function_exists( 'sitefort_waf_db_credentials' ) ) {
    /**
     * @return array{database:string,user:string,password:string,host:string,charset:string,table_prefix:string}|null
     */
    function sitefort_waf_db_credentials( array $config, string $data_dir ): ?array {
        $database = defined( 'DB_NAME' ) ? (string) constant( 'DB_NAME' ) : '';
        $user     = defined( 'DB_USER' ) ? (string) constant( 'DB_USER' ) : '';
        $password = defined( 'DB_PASSWORD' ) ? (string) constant( 'DB_PASSWORD' ) : '';
        $host     = defined( 'DB_HOST' ) ? (string) constant( 'DB_HOST' ) : '';
        $charset  = defined( 'DB_CHARSET' ) ? (string) constant( 'DB_CHARSET' ) : 'utf8mb4';
        $prefix   = isset( $GLOBALS['table_prefix'] ) && is_string( $GLOBALS['table_prefix'] )
            ? $GLOBALS['table_prefix']
            : '';

        if ( $database !== '' && $user !== '' && $host !== '' ) {
            return [
                'database' => $database,
                'user' => $user,
                'password' => $password,
                'host' => $host,
                'charset' => $charset,
                'table_prefix' => $prefix,
            ];
        }

        foreach ( sitefort_waf_wp_config_candidates( $data_dir ) as $candidate ) {
            $parsed = sitefort_waf_parse_wp_config_credentials( $candidate );
            if ( $parsed !== null ) {
                return $parsed;
            }
        }

        return null;
    }
}

if ( ! function_exists( 'sitefort_waf_wp_config_candidates' ) ) {
    /**
     * @return array<int,string>
     */
    function sitefort_waf_wp_config_candidates( string $data_dir ): array {
        $candidates = [];
        $root       = dirname( dirname( $data_dir ) );
        $primary    = $root . '/wp-config.php';
        $parent     = dirname( $root ) . '/wp-config.php';

        foreach ( [ $primary, $parent ] as $candidate ) {
            if ( is_file( $candidate ) && is_readable( $candidate ) ) {
                $real = realpath( $candidate );
                if ( is_string( $real ) && $real !== '' && ! in_array( $real, $candidates, true ) ) {
                    $candidates[] = $real;
                }
            }
        }

        return $candidates;
    }
}

if ( ! function_exists( 'sitefort_waf_parse_wp_config_credentials' ) ) {
    /**
     * @return array{database:string,user:string,password:string,host:string,charset:string,table_prefix:string}|null
     */
    function sitefort_waf_parse_wp_config_credentials( string $path ): ?array {
        $contents = @file_get_contents( $path );
        if ( ! is_string( $contents ) || $contents === '' ) {
            return null;
        }

        $database = sitefort_waf_wp_config_define( $contents, 'DB_NAME' );
        $user     = sitefort_waf_wp_config_define( $contents, 'DB_USER' );
        $password = sitefort_waf_wp_config_define( $contents, 'DB_PASSWORD' );
        $host     = sitefort_waf_wp_config_define( $contents, 'DB_HOST' );
        $charset  = sitefort_waf_wp_config_define( $contents, 'DB_CHARSET' );
        $prefix   = sitefort_waf_wp_config_variable( $contents, 'table_prefix' );

        if ( $database === '' || $user === '' || $host === '' ) {
            return null;
        }

        return [
            'database' => $database,
            'user' => $user,
            'password' => $password,
            'host' => $host,
            'charset' => $charset !== '' ? $charset : 'utf8mb4',
            'table_prefix' => $prefix,
        ];
    }
}

if ( ! function_exists( 'sitefort_waf_wp_config_define' ) ) {
    function sitefort_waf_wp_config_define( string $contents, string $name ): string {
        $pattern = '/define\s*\(\s*[\'"]' . preg_quote( $name, '/' ) . '[\'"]\s*,\s*([\'"])((?:\\\\.|(?!\1).)*)\1\s*\)/s';
        if ( preg_match( $pattern, $contents, $matches ) !== 1 ) {
            return '';
        }

        return stripcslashes( (string) $matches[2] );
    }
}

if ( ! function_exists( 'sitefort_waf_wp_config_variable' ) ) {
    function sitefort_waf_wp_config_variable( string $contents, string $name ): string {
        $pattern = '/\$' . preg_quote( $name, '/' ) . '\s*=\s*([\'"])((?:\\\\.|(?!\1).)*)\1\s*;/s';
        if ( preg_match( $pattern, $contents, $matches ) !== 1 ) {
            return '';
        }

        return stripcslashes( (string) $matches[2] );
    }
}

if ( ! function_exists( 'sitefort_waf_valid_table_prefix' ) ) {
    function sitefort_waf_valid_table_prefix( string $prefix ): string {
        $prefix = trim( $prefix );
        if ( $prefix === '' || preg_match( '/^[A-Za-z0-9_]+$/', $prefix ) !== 1 ) {
            return '';
        }

        return $prefix;
    }
}

if ( ! function_exists( 'sitefort_waf_db_table_identifier' ) ) {
    function sitefort_waf_db_table_identifier( string $prefix, string $suffix ): string {
        $table = $prefix . $suffix;
        if ( $table === '' || strlen( $table ) > 64 || preg_match( '/^[A-Za-z0-9_]+$/', $table ) !== 1 ) {
            return '';
        }

        return '`' . $table . '`';
    }
}

if ( ! function_exists( 'sitefort_waf_valid_runtime_key' ) ) {
    function sitefort_waf_valid_runtime_key( string $key ): string {
        $key = trim( $key );
        if ( $key === '' || strlen( $key ) > 64 || preg_match( '/^[A-Za-z0-9_:-]+$/', $key ) !== 1 ) {
            return '';
        }

        return $key;
    }
}

if ( ! function_exists( 'sitefort_waf_parse_db_host_parts' ) ) {
    /**
     * @return array{host:string,port:int,socket:?string}
     */
    function sitefort_waf_parse_db_host_parts( string $host ): array {
        $host = trim( $host );
        $port = 0;
        $socket = null;

        if ( $host === '' ) {
            return [
                'host' => 'localhost',
                'port' => 0,
                'socket' => null,
            ];
        }

        if ( strpos( $host, ':/' ) !== false ) {
            [ $host, $socket ] = explode( ':', $host, 2 );
            return [
                'host' => $host !== '' ? $host : 'localhost',
                'port' => 0,
                'socket' => $socket !== '' ? $socket : null,
            ];
        }

        if ( preg_match( '/^\[(.+)\](?::(\d+))?$/', $host, $matches ) === 1 ) {
            return [
                'host' => $matches[1],
                'port' => isset( $matches[2] ) ? (int) $matches[2] : 0,
                'socket' => null,
            ];
        }

        if ( substr_count( $host, ':' ) === 1 ) {
            [ $base_host, $tail ] = explode( ':', $host, 2 );
            if ( ctype_digit( $tail ) ) {
                $host = $base_host;
                $port = (int) $tail;
            }
        }

        return [
            'host' => $host !== '' ? $host : 'localhost',
            'port' => $port,
            'socket' => $socket,
        ];
    }
}

if ( ! function_exists( 'sitefort_waf_db_prepare' ) ) {
    /**
     * @param array<int,mixed> $params
     * @return \mysqli_stmt|null
     */
    function sitefort_waf_db_prepare( \mysqli $db, string $sql, string $types = '', array $params = [] ): ?\mysqli_stmt {
        $stmt = @$db->prepare( $sql );
        if ( ! $stmt instanceof \mysqli_stmt ) {
            return null;
        }

        if ( $types !== '' && $params !== [] ) {
            $bind = [ $types ];
            foreach ( $params as $index => $value ) {
                $bind[] = &$params[ $index ];
            }

            if ( ! @call_user_func_array( [ $stmt, 'bind_param' ], $bind ) ) {
                $stmt->close();
                return null;
            }
        }

        if ( ! @$stmt->execute() ) {
            $stmt->close();
            return null;
        }

        return $stmt;
    }
}

if ( ! function_exists( 'sitefort_waf_db_runtime_payload' ) ) {
    /**
     * Load one targeted runtime payload from sitefort_waf_runtime.
     *
     * @return mixed
     */
    function sitefort_waf_db_runtime_payload( array $config, string $data_dir, string $key, $default = null ) {
        static $payload_cache = [];

        $runtime_key = sitefort_waf_valid_runtime_key( $key );
        if ( $runtime_key === '' ) {
            return $default;
        }

        $context = sitefort_waf_db_context( $config, $data_dir );
        if ( $context === null ) {
            return $default;
        }

        $cache_key = $context['table_prefix'] . '|' . $runtime_key;
        if ( array_key_exists( $cache_key, $payload_cache ) ) {
            return $payload_cache[ $cache_key ];
        }

        $table = sitefort_waf_db_table_identifier( $context['table_prefix'], 'sitefort_waf_runtime' );
        if ( $table === '' ) {
            $payload_cache[ $cache_key ] = $default;
            return $default;
        }

        $stmt = sitefort_waf_db_prepare(
            $context['db'],
            "SELECT `payload`, `payload_hash`, `expires_at` FROM {$table} WHERE `runtime_key` = ? LIMIT 1",
            's',
            [ $runtime_key ]
        );
        if ( ! $stmt instanceof \mysqli_stmt ) {
            $payload_cache[ $cache_key ] = $default;
            return $default;
        }

        $payload = null;
        $payload_hash = null;
        $expires_at = 0;
        $stmt->bind_result( $payload, $payload_hash, $expires_at );
        $row = $stmt->fetch();
        $stmt->close();

        if ( ! $row || ! is_string( $payload ) || $payload === '' ) {
            $payload_cache[ $cache_key ] = $default;
            return $default;
        }

        $expires_at = (int) $expires_at;
        if ( $expires_at > 0 && $expires_at < time() ) {
            $payload_cache[ $cache_key ] = $default;
            return $default;
        }

        $payload_hash = is_string( $payload_hash ) ? $payload_hash : '';
        if ( $payload_hash !== '' && ! hash_equals( $payload_hash, hash( 'sha256', $payload ) ) ) {
            $payload_cache[ $cache_key ] = $default;
            return $default;
        }

        $decoded = @json_decode( $payload, true );
        if ( ! is_array( $decoded ) ) {
            $payload_cache[ $cache_key ] = $default;
            return $default;
        }

        $payload_cache[ $cache_key ] = $decoded;

        return $decoded;
    }
}

if ( ! function_exists( 'sitefort_waf_db_search_trust_cache_get' ) ) {
    function sitefort_waf_db_search_trust_cache_get( array $config, string $data_dir, string $cache_key ): ?bool {
        $runtime_key = sitefort_waf_valid_runtime_key( $cache_key );
        if ( $runtime_key === '' || ! function_exists( 'sitefort_waf_db_runtime_payload' ) ) {
            return null;
        }

        $payload = sitefort_waf_db_runtime_payload( $config, $data_dir, $runtime_key, null );
        if ( ! is_array( $payload ) || ! array_key_exists( 'verified', $payload ) ) {
            return null;
        }

        if ( is_bool( $payload['verified'] ) ) {
            return $payload['verified'];
        }

        if ( $payload['verified'] === 'yes' ) {
            return true;
        }

        if ( $payload['verified'] === 'no' ) {
            return false;
        }

        return null;
    }
}

if ( ! function_exists( 'sitefort_waf_db_search_trust_cache_set' ) ) {
    function sitefort_waf_db_search_trust_cache_set( array $config, string $data_dir, string $cache_key, bool $verified, int $ttl ): bool {
        $runtime_key = sitefort_waf_valid_runtime_key( $cache_key );
        if ( $runtime_key === '' ) {
            return false;
        }

        $context = sitefort_waf_db_context( $config, $data_dir );
        if ( $context === null ) {
            return false;
        }

        $table = sitefort_waf_db_table_identifier( $context['table_prefix'], 'sitefort_waf_runtime' );
        if ( $table === '' ) {
            return false;
        }

        $now        = time();
        $expires_at = $now + max( 1, $ttl );
        $payload    = json_encode(
            [
                'verified'   => $verified ? 'yes' : 'no',
                'updated_at' => $now,
            ],
            JSON_UNESCAPED_SLASHES
        );

        if ( ! is_string( $payload ) || $payload === '' ) {
            return false;
        }

        $cleanup_stmt = sitefort_waf_db_prepare(
            $context['db'],
            "DELETE FROM {$table} WHERE `runtime_key` LIKE ? AND `expires_at` > 0 AND `expires_at` < ?",
            'si',
            [ 'sitefort_vsearch_v3_%', $now ]
        );
        if ( $cleanup_stmt instanceof \mysqli_stmt ) {
            $cleanup_stmt->close();
        }

        $hash = hash( 'sha256', $payload );
        $stmt = sitefort_waf_db_prepare(
            $context['db'],
            "INSERT INTO {$table} (`runtime_key`, `payload`, `payload_hash`, `updated_at`, `expires_at`)
             VALUES (?, ?, ?, ?, ?)
             ON DUPLICATE KEY UPDATE
                `payload` = VALUES(`payload`),
                `payload_hash` = VALUES(`payload_hash`),
                `updated_at` = VALUES(`updated_at`),
                `expires_at` = VALUES(`expires_at`)",
            'sssii',
            [ $runtime_key, $payload, $hash, $now, $expires_at ]
        );

        if ( ! $stmt instanceof \mysqli_stmt ) {
            return false;
        }

        $ok = $stmt->affected_rows >= 0;
        $stmt->close();

        return $ok;
    }
}

if ( ! function_exists( 'sitefort_waf_db_mark_verification_nonce' ) ) {
    function sitefort_waf_db_mark_verification_nonce( array $config, string $data_dir, string $nonce, string $signature, int $timestamp, int $window ): ?bool {
        $context = sitefort_waf_db_context( $config, $data_dir );
        if ( $context === null || $context['verification_site_token'] === '' ) {
            return null;
        }

        $table = sitefort_waf_db_table_identifier( $context['table_prefix'], 'sitefort_request_nonces' );
        if ( $table === '' ) {
            return null;
        }

        $cutoff = time() - max( 1, $window );
        $delete_stmt = sitefort_waf_db_prepare(
            $context['db'],
            "DELETE FROM {$table} WHERE created_at < ? AND site_token = ?",
            'is',
            [ $cutoff, $context['verification_site_token'] ]
        );
        if ( $delete_stmt instanceof \mysqli_stmt ) {
            $delete_stmt->close();
        }

        $request_id = hash( 'sha256', $nonce . '|' . $signature . '|' . $timestamp );
        $created_at = time();
        $stmt = sitefort_waf_db_prepare(
            $context['db'],
            "INSERT IGNORE INTO {$table} (site_token, nonce, request_id, created_at) VALUES (?, ?, ?, ?)",
            'sssi',
            [ $context['verification_site_token'], $nonce, $request_id, $created_at ]
        );

        if ( ! $stmt instanceof \mysqli_stmt ) {
            return null;
        }

        $inserted = $stmt->affected_rows === 1;
        $stmt->close();

        return $inserted;
    }
}

if ( ! function_exists( 'sitefort_waf_db_blocklist_lookup' ) ) {
    function sitefort_waf_db_blocklist_lookup( array $config, string $data_dir, string $ip, string $expected_signature ): ?bool {
        if ( trim( $expected_signature ) === '' || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
            return null;
        }

        $context = sitefort_waf_db_context( $config, $data_dir );
        if ( $context === null ) {
            return null;
        }

        $meta = sitefort_waf_db_runtime_payload( $config, $data_dir, 'blocklist_meta', null );
        if ( ! is_array( $meta ) || ! hash_equals( $expected_signature, (string) ( $meta['signature'] ?? '' ) ) ) {
            return null;
        }

        $blocklist_table = sitefort_waf_db_table_identifier( $context['table_prefix'], 'sitefort_blocklist' );
        if ( $blocklist_table === '' ) {
            return null;
        }

        $exact_stmt = sitefort_waf_db_prepare(
            $context['db'],
            "SELECT 1 FROM {$blocklist_table} WHERE `host` = ? AND `type` = 'ip' LIMIT 1",
            's',
            [ $ip ]
        );
        if ( ! $exact_stmt instanceof \mysqli_stmt ) {
            return null;
        }

        $exact_match = 0;
        $exact_stmt->bind_result( $exact_match );
        $exact_found = $exact_stmt->fetch();
        $exact_stmt->close();

        if ( $exact_found ) {
            return true;
        }

        if ( (int) ( $meta['cidr_count'] ?? 0 ) <= 0 ) {
            return false;
        }

        $packed = @inet_pton( $ip );
        if ( $packed === false ) {
            return false;
        }

        $length = strlen( $packed );
        if ( $length !== 4 && $length !== 16 ) {
            return false;
        }

        $range_table = sitefort_waf_db_table_identifier( $context['table_prefix'], 'sitefort_blocklist_ranges' );
        if ( $range_table === '' ) {
            return null;
        }

        $family = $length === 4 ? 4 : 6;
        $range_stmt = sitefort_waf_db_prepare(
            $context['db'],
            "SELECT 1 FROM {$range_table} WHERE `family` = ? AND `range_start` <= ? AND `range_end` >= ? LIMIT 1",
            'iss',
            [ $family, $packed, $packed ]
        );
        if ( ! $range_stmt instanceof \mysqli_stmt ) {
            return null;
        }

        $range_match = 0;
        $range_stmt->bind_result( $range_match );
        $range_found = $range_stmt->fetch();
        $range_stmt->close();

        return (bool) $range_found;
    }
}

if ( ! function_exists( 'sitefort_waf_db_rate_limit_exceeded' ) ) {
    function sitefort_waf_db_rate_limit_exceeded( array $config, string $data_dir, string $ip, int $limit ): ?bool {
        $context = sitefort_waf_db_context( $config, $data_dir );
        if ( $context === null ) {
            return null;
        }

        $table = sitefort_waf_db_table_identifier( $context['table_prefix'], 'sitefort_rate_limits' );
        if ( $table === '' ) {
            return null;
        }

        $ip_hash        = hash( 'sha256', $ip );
        $hit_type       = 'request';
        $now            = time();
        $current_bucket = (int) floor( $now / 60 );
        $boundary_bucket = $current_bucket - 1;
        $seconds_in     = $now % 60;

        $cleanup_stmt = sitefort_waf_db_prepare(
            $context['db'],
            "DELETE FROM {$table} WHERE ip_hash = ? AND hit_type = ? AND minute_bucket < ?",
            'ssi',
            [ $ip_hash, $hit_type, $boundary_bucket ]
        );
        if ( $cleanup_stmt instanceof \mysqli_stmt ) {
            $cleanup_stmt->close();
        }

        $increment_stmt = sitefort_waf_db_prepare(
            $context['db'],
            "INSERT INTO {$table} (ip_hash, hit_type, minute_bucket, hits)
             VALUES (?, ?, ?, 1)
             ON DUPLICATE KEY UPDATE hits = hits + 1",
            'ssi',
            [ $ip_hash, $hit_type, $current_bucket ]
        );
        if ( ! $increment_stmt instanceof \mysqli_stmt ) {
            return null;
        }
        $increment_stmt->close();

        $select_stmt = sitefort_waf_db_prepare(
            $context['db'],
            "SELECT minute_bucket, hits FROM {$table}
             WHERE ip_hash = ? AND hit_type = ? AND minute_bucket >= ?
             ORDER BY minute_bucket DESC",
            'ssi',
            [ $ip_hash, $hit_type, $boundary_bucket ]
        );
        if ( ! $select_stmt instanceof \mysqli_stmt ) {
            return null;
        }

        $bucket = 0;
        $hits = 0;
        $current_hits = 0;
        $boundary_hits = 0;
        $select_stmt->bind_result( $bucket, $hits );
        while ( $select_stmt->fetch() ) {
            $bucket = (int) $bucket;
            $hits   = (int) $hits;

            if ( $bucket === $current_bucket ) {
                $current_hits = $hits;
            } elseif ( $bucket === $boundary_bucket ) {
                $boundary_hits = $hits;
            }
        }
        $select_stmt->close();

        $weighted_hits = $current_hits + (int) floor( $boundary_hits * ( 60 - $seconds_in ) / 60 );

        return $weighted_hits > $limit;
    }
}

if ( ! function_exists( 'sitefort_waf_db_log_event' ) ) {
    function sitefort_waf_db_log_event( array $config, string $data_dir, string $ip, string $attack_type, int $http_status = 403 ): bool {
        $context = sitefort_waf_db_context( $config, $data_dir );
        if ( $context === null ) {
            return false;
        }

        $table = sitefort_waf_db_table_identifier( $context['table_prefix'], 'sitefort_firewall_events' );
        if ( $table === '' ) {
            return false;
        }

        $request_uri = sitefort_waf_trim_utf8( sitefort_waf_storage_server_value( 'REQUEST_URI' ), 500 );
        $user_agent  = sitefort_waf_trim_utf8( sitefort_waf_storage_server_value( 'HTTP_USER_AGENT' ), 500 );
        $http_method = sitefort_waf_trim_utf8( strtoupper( sitefort_waf_storage_server_value( 'REQUEST_METHOD' ) ?: 'GET' ), 10 );
        $event_time  = time();
        $user_id     = 0;
        $blog_id     = $context['blog_id'];

        $stmt = sitefort_waf_db_prepare(
            $context['db'],
            "INSERT INTO {$table}
                (event_time, ip_address, request_uri, http_method, http_status, attack_type, user_agent, user_id, blog_id)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
            'isssissii',
            [ $event_time, $ip, $request_uri, $http_method, $http_status, $attack_type, $user_agent, $user_id, $blog_id ]
        );

        if ( ! $stmt instanceof \mysqli_stmt ) {
            return false;
        }

        $ok = $stmt->affected_rows === 1;
        $stmt->close();

        return $ok;
    }
}

if ( ! function_exists( 'sitefort_waf_trim_utf8' ) ) {
    function sitefort_waf_trim_utf8( string $value, int $limit ): string {
        if ( $limit <= 0 ) {
            return '';
        }

        if ( function_exists( 'mb_substr' ) ) {
            return mb_substr( $value, 0, $limit, 'UTF-8' );
        }

        return substr( $value, 0, $limit );
    }
}
