<?php
/**
 * SignatureSeeder — Populates ultraguard_malware_signatures from the bundled
 * JSON database and/or the UltraGuard intelligence server.
 *
 * Uses DatabaseCrud::insert_raw() ($internal=true) to preserve regex patterns and
 * bypass the capability check so seeding works in all execution contexts.
 * load_existing() uses $wpdb->get_col() directly because during first-time
 * activation the DatabaseSchema registry is not yet populated.
 * Schema checks (SHOW TABLES) are DDL queries — intentionally not CRUD.
 *
 * @package UltraguardPro\modules\antivirus
 * @since   6.5.1
 */

namespace UltraguardPro\modules\antivirus;

use UltraguardPro\modules\antivirus\db\MalwareSignaturesTable;
use UltraguardPro\support\RemoteIntelligence;

use UltraguardPro\core\CacheManager;
use UltraguardPro\core\DatabaseCrud;

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

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Signature seeding intentionally reads/writes the raw signatures dataset during activation and updates.

class SignatureSeeder {

    const VERSION_OPTION   = 'ultraguard_antivirus_sig_db_version';
    const SEEDED_AT_OPTION = 'ultraguard_antivirus_signatures_seeded';
    const DATA_TYPE        = 'malware-signatures';

    /** @var RemoteIntelligence */
    private RemoteIntelligence $remote;

    public function __construct( ?MalwareSignaturesTable $table = null ) {
        // $table parameter kept for backwards-compatibility with any call sites
        // The seeder uses DatabaseCrud::insert_raw() directly
        // and does not require a BaseTable instance.
        $this->remote = new RemoteIntelligence();
    }

    // ── Public API ─────────────────────────────────────────────────────────────

    public function seed( bool $force = false ): array {
        global $wpdb;

        $result = [ 'inserted' => 0, 'skipped' => 0, 'total' => 0, 'version' => '', 'source' => 'local', 'error' => null ];

        // ── 1. Fetch (remote first, local JSON fallback) ───────────────────────
        $fetched = $this->remote->fetch(
            self::DATA_TYPE,
            $force ? '' : (string) get_option( self::VERSION_OPTION, '' ),
            $force
        );

        if ( $fetched['error'] ) {
            $result['error'] = 'Fetch failed: ' . $fetched['error'];
            \UltraguardPro\core\Logger::error( '[UltraGuard SignatureSeeder] ' . $result['error'] );
            return $result;
        }

        $data    = $fetched['data'];
        $version = $fetched['version'];
        $result['source']  = $fetched['source'];
        $result['version'] = $version;

        if ( ! empty( $fetched['up_to_date'] ) ) {
            return $result;
        }

        if ( empty( $data['signatures'] ) || ! is_array( $data['signatures'] ) ) {
            $result['error'] = 'No signatures array found in payload.';
            return $result;
        }

        $result['total'] = count( $data['signatures'] );

        // ── 2. Version check — skip when already current ───────────────────────
        if ( ! $force && $fetched['source'] !== 'remote'
             && get_option( self::VERSION_OPTION ) === $version ) {
            $result['skipped'] = $result['total'];
            return $result;
        }

        // ── 3. Verify table exists ─────────────────────────────────────────────
        $table = $wpdb->prefix . 'ultraguard_malware_signatures';
        if ( ! (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- query uses prepare(); table name is supplied as a value placeholder.
            $result['error'] = "Table {$table} does not exist — run plugin activation first.";
            \UltraguardPro\core\Logger::error( '[UltraGuard SignatureSeeder] ' . $result['error'] );
            return $result;
        }
        $safe_table = esc_sql( $table );

        // ── 4. Preload existing hashes (one SELECT, O(1) lookup) ───────────────
        // Direct $wpdb call: intentional exemption from DatabaseCrud routing.
        // Seeders run during first-time activation BEFORE DatabaseTablesModule
        // boots, so DatabaseSchema's table registry is empty and
        // DatabaseCrud::get_column() → get_validated_table_name() returns false.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
        $existing_rows = $wpdb->get_col( "SELECT signature_hash FROM `{$safe_table}`" ); // nosemgrep
        $existing      = $existing_rows ? array_fill_keys( $existing_rows, true ) : [];

        // ── 5. Insert new signatures ───────────────────────────────────────────
        $now = current_time( 'mysql', true );

        foreach ( $data['signatures'] as $sig ) {
            if ( empty( $sig['signature_hash'] ) || empty( $sig['name'] ) ) {
                $result['skipped']++;
                continue;
            }

            $hash = trim( (string) $sig['signature_hash'] );
            $name = sanitize_text_field( (string) $sig['name'] );

            if ( $hash === '' || $name === '' ) { $result['skipped']++; continue; }
            if ( isset( $existing[ $hash ] ) )  { $result['skipped']++; continue; }

            // Pattern field: regex-safe sanitizer — no sanitize_text_field/wp_strip_all_tags
            $pattern = isset( $sig['pattern'] ) && $sig['pattern'] !== null
                ? $this->sanitize_regex_pattern( (string) $sig['pattern'] )
                : null;

            // insert_raw() bypasses sanitize_text_field() to preserve regex
            // patterns — data has already been validated above.
            // $internal = true: seeders run in any context (activation, cron, init hook).
            $insert_id = DatabaseCrud::insert_raw( 'ultraguard_malware_signatures', [
                'signature_hash' => $hash,
                'name'           => $name,
                'type'           => $this->sanitize_type( $sig['type'] ?? 'pattern' ),
                'pattern'        => $pattern,
                'threat_level'   => $this->sanitize_level( $sig['threat_level'] ?? 'medium' ),
                'is_active'      => 1,
                'source'         => sanitize_text_field( (string) ( $sig['source'] ?? 'ultraguard' ) ),
                'hits_count'     => 0,
                'created_at'     => $now,
                'updated_at'     => $now,
            ], [ '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%d', '%s', '%s' ], true );
            $ok = ( $insert_id !== false );

            if ( ! $ok ) {
                \UltraguardPro\core\Logger::error( "[UltraGuard SignatureSeeder] insert_raw failed for hash '{$hash}' — check DatabaseCrud::get_last_error()" );
                $result['skipped']++;
            } else {
                $result['inserted']++;
                $existing[ $hash ] = true;
            }
        }

        // ── 6. Persist version + timestamp ────────────────────────────────────
        update_option( self::VERSION_OPTION,   $version, false );
        update_option( self::SEEDED_AT_OPTION, time(),   false );
        CacheManager::get_instance()->invalidate_db(); // Targeted DB-query cache flush after bulk seed

        do_action( 'ultraguard_pro/antivirus/signatures_seeded', $result );
        return $result;
    }

    public function force_reseed(): array {
        return $this->seed( true );
    }

    public function get_installed_version(): string {
        return (string) get_option( self::VERSION_OPTION, '' );
    }

    public function get_bundled_version(): string {
        return $this->remote->local_version( self::DATA_TYPE );
    }

    public function update_available(): bool {
        $installed = $this->get_installed_version();
        if ( ! $installed ) { return true; }

        if ( $this->remote->remote_update_available( self::DATA_TYPE, $installed ) ) {
            return true;
        }

        $available = $this->get_bundled_version();
        return '' !== $available && version_compare( $available, $installed, '>' );
    }

    // ── Private helpers ────────────────────────────────────────────────────────

    /**
     * Sanitize a regex/pattern string for DB storage.
     * Does NOT call sanitize_text_field() or wp_strip_all_tags().
     */
    private function sanitize_regex_pattern( string $v ): string {
        $v = str_replace( "\x00", '', $v );
        if ( function_exists( 'mb_check_encoding' ) && ! mb_check_encoding( $v, 'UTF-8' ) ) {
            return '';
        }
        return trim( $v );
    }

    private function sanitize_type( string $v ): string {
        return in_array( $v, [ 'hash', 'pattern', 'regex', 'heuristic' ], true ) ? $v : 'pattern';
    }

    private function sanitize_level( string $v ): string {
        return in_array( $v, [ 'critical', 'high', 'medium', 'low' ], true ) ? $v : 'medium';
    }
}
