<?php

declare(strict_types=1);

namespace SiteFort\Modules\Firewall\ExtendedProtection;

use SiteFort\Core\Database\SchemaInspector;
use SiteFort\Core\Installer;

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Targeted runtime storage for pre-WordPress WAF bootstrap payloads.
 *
 * This keeps hot-path WAF artifacts out of the shared settings.state JSON blob,
 * so explicit mysqli bootstrap reads do not require decoding unrelated state.
 */
class WafRuntimeStore {
    public const TABLE = 'sitefort_waf_runtime';
    public const KEY_BOOTSTRAP_CONFIG = 'bootstrap_config';
    public const KEY_BLOCKLIST_META = 'blocklist_meta';

    private const MAX_IDENTIFIER_LENGTH = 64;

    private static bool $table_ready = false;
    private static ?string $context_prefix = null;

    /**
     * Persist a runtime payload by key.
     *
     * @param array<string,mixed> $payload
     */
    public static function put( string $key, array $payload, int $expires_at = 0 ): bool {
        self::sync_context();

        if ( ! self::is_valid_key( $key ) || ! self::ensure_table() ) {
            return false;
        }

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

        global $wpdb;

        $table      = self::table_identifier();
        $hash       = hash( 'sha256', $encoded );
        $updated_at = time();
        $expires_at = max( 0, $expires_at );

        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
        $sql = $wpdb->prepare(
            "INSERT INTO {$table} (`runtime_key`, `payload`, `payload_hash`, `updated_at`, `expires_at`)
             VALUES (%s, %s, %s, %d, %d)
             ON DUPLICATE KEY UPDATE
                `payload` = VALUES(`payload`),
                `payload_hash` = VALUES(`payload_hash`),
                `updated_at` = VALUES(`updated_at`),
                `expires_at` = VALUES(`expires_at`)",
            $key,
            $encoded,
            $hash,
            $updated_at,
            $expires_at
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter

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

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Writes a targeted plugin-owned runtime payload.
        return $wpdb->query( $sql ) !== false;
    }

    /**
     * Delete one runtime key if the table exists.
     */
    public static function delete( string $key ): bool {
        self::sync_context();

        if ( ! self::is_valid_key( $key ) ) {
            return false;
        }

        try {
            $table_name = self::table_name();
        } catch ( \InvalidArgumentException $e ) {
            return false;
        }

        if ( ! SchemaInspector::table_exists( $table_name ) ) {
            return true;
        }

        global $wpdb;
        $table = self::table_identifier();

        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
        $sql = $wpdb->prepare(
            "DELETE FROM {$table} WHERE `runtime_key` = %s",
            $key
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter

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

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Deletes a targeted plugin-owned runtime payload.
        return $wpdb->query( $sql ) !== false;
    }

    /**
     * Read and verify a runtime payload.
     *
     * @return mixed
     */
    public static function get( string $key, $default = null ) {
        self::sync_context();

        if ( ! self::is_valid_key( $key ) ) {
            return $default;
        }

        try {
            $table_name = self::table_name();
        } catch ( \InvalidArgumentException $e ) {
            return $default;
        }

        if ( ! SchemaInspector::table_exists( $table_name ) ) {
            return $default;
        }

        global $wpdb;
        $table = self::table_identifier();

        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
        $sql = $wpdb->prepare(
            "SELECT `payload`, `payload_hash`, `expires_at` FROM {$table} WHERE `runtime_key` = %s LIMIT 1",
            $key
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter

        if ( ! is_string( $sql ) || $sql === '' ) {
            return $default;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reads a targeted plugin-owned runtime payload.
        $row = $wpdb->get_row( $sql, ARRAY_A );
        if ( ! is_array( $row ) ) {
            return $default;
        }

        $payload = isset( $row['payload'] ) && is_string( $row['payload'] ) ? $row['payload'] : '';
        if ( $payload === '' ) {
            return $default;
        }

        $expires_at = (int) ( $row['expires_at'] ?? 0 );
        if ( $expires_at > 0 && $expires_at < time() ) {
            return $default;
        }

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

        $decoded = json_decode( $payload, true );

        return is_array( $decoded ) ? $decoded : $default;
    }

    public static function ensure_table(): bool {
        self::sync_context();

        if ( self::$table_ready ) {
            return true;
        }

        try {
            $table_name = self::table_name();
        } catch ( \InvalidArgumentException $e ) {
            return false;
        }

        if ( SchemaInspector::table_exists( $table_name ) ) {
            self::$table_ready = true;
            return true;
        }

        if ( class_exists( Installer::class ) && method_exists( Installer::class, 'ensure_waf_runtime_table_schema' ) ) {
            Installer::ensure_waf_runtime_table_schema();
        } else {
            self::create_table();
        }

        SchemaInspector::flush_cache( $table_name );
        self::$table_ready = SchemaInspector::table_exists( $table_name );

        return self::$table_ready;
    }

    private static function create_table(): void {
        global $wpdb;

        try {
            $table = self::table_name();
        } catch ( \InvalidArgumentException $e ) {
            return;
        }

        $charset_collate = SchemaInspector::validated_charset_collate( $wpdb );
        $sql = "CREATE TABLE {$table} (
            runtime_key varchar(64) NOT NULL,
            payload longtext NULL,
            payload_hash char(64) NOT NULL DEFAULT '',
            updated_at int(10) unsigned NOT NULL,
            expires_at int(10) unsigned NOT NULL DEFAULT 0,
            PRIMARY KEY  (runtime_key),
            KEY idx_expires_at (expires_at)
        ) {$charset_collate};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql );
    }

    private static function is_valid_key( string $key ): bool {
        return $key !== ''
            && strlen( $key ) <= 64
            && preg_match( '/^[A-Za-z0-9_:-]+$/', $key ) === 1;
    }

    private static function table_name(): string {
        global $wpdb;

        $table = (string) $wpdb->prefix . self::TABLE;
        if (
            $table === ''
            || strlen( $table ) > self::MAX_IDENTIFIER_LENGTH
            || preg_match( '/^[A-Za-z0-9_]+$/', $table ) !== 1
        ) {
            throw new \InvalidArgumentException( 'Invalid SiteFort WAF runtime table identifier.' );
        }

        return $table;
    }

    private static function table_identifier(): string {
        return '`' . str_replace( '`', '``', self::table_name() ) . '`';
    }

    private static function sync_context(): void {
        global $wpdb;

        $prefix = isset( $wpdb->prefix ) ? (string) $wpdb->prefix : '';
        if ( self::$context_prefix === $prefix ) {
            return;
        }

        self::$context_prefix = $prefix;
        self::$table_ready    = false;
    }
}
