<?php

declare(strict_types=1);

namespace SiteFort\Modules\Scanner\Service;

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


/**
 * Scanner settings defaults, validation, and storage helpers.
 */
class ScannerSettings {

    private const REMOVED_SETTINGS_KEYS = [
        'scope_core',
        'scope_vuln',
        'scope_plugins',
        'scope_themes',
        'scope_uploads',
        'scope_root',
        'lowResourceMode',
    ];
    private const ALLOWED_SCHEDULES = [ 'none', 'every_6_hours', 'every_12_hours', 'daily', 'weekly' ];
    private const ALLOWED_INTENSITIES = [ 'standard', 'deep' ];
    private static $table_ready = false;
    /** @var string|null Table-prefix context for the current request (multisite). */
    private static ?string $context_prefix = null;

    /**
     * Get all settings with defaults applied.
     * 
     * @return array
     */
    public static function get_all() {
        $stored = self::strip_removed_settings( self::get_stored_settings() );
        $defaults = self::get_defaults();
        $settings = wp_parse_args( $stored, $defaults );
        $settings['schedule'] = self::normalize_schedule( $settings['schedule'] ?? $defaults['schedule'], (string) $defaults['schedule'] );
        $settings['scanIntensity'] = self::normalize_scan_intensity( $settings['scanIntensity'] ?? $defaults['scanIntensity'], (string) $defaults['scanIntensity'] );
        $settings['quarantine_retention_days'] = self::normalize_quarantine_retention_days(
            $settings['quarantine_retention_days'] ?? $defaults['quarantine_retention_days'],
            (int) $defaults['quarantine_retention_days']
        );
        $raw_scan_exclusions        = array_key_exists( 'scanExclusions', $stored )
            ? $stored['scanExclusions']
            : ( $defaults['scanExclusions'] ?? '' );
        $settings['scanExclusions'] = self::normalize_scan_exclusions( $raw_scan_exclusions );

        return $settings;
    }

    /**
     * Save settings.
     * 
     * @param array $input Raw input array
     * @return bool Success
     */
    public static function save( $input ) {
        // Preserve internal scanner metadata that should not be user-editable.
        $existing = self::strip_removed_settings( self::get_stored_settings() );
        $defaults = self::get_defaults();
        $current  = wp_parse_args( $existing, $defaults );
        $schedule = self::normalize_schedule( $input['schedule'] ?? $current['schedule'], $current['schedule'] );

        $settings = [
            'schedule'           => $schedule,
            'scanIntensity'      => self::normalize_scan_intensity( $input['scanIntensity'] ?? $current['scanIntensity'], $current['scanIntensity'] ),
            'emailNotifications' => filter_var( $input['emailNotifications'] ?? $current['emailNotifications'], FILTER_VALIDATE_BOOLEAN ),
            'notificationEmail'  => sanitize_email( $input['notificationEmail'] ?? $current['notificationEmail'] ),
            'scanMaxDuration'    => absint( $input['scanMaxDuration'] ?? $current['scanMaxDuration'] ),
            'stageMaxExecutionTime' => absint( $input['stageMaxExecutionTime'] ?? $current['stageMaxExecutionTime'] ),
            'scanExclusions'     => self::normalize_scan_exclusions(
                array_key_exists( 'scanExclusions', $input ) ? $input['scanExclusions'] : $current['scanExclusions']
            ),
            'quarantine_retention_days' => self::normalize_quarantine_retention_days(
                $input['quarantine_retention_days'] ?? $current['quarantine_retention_days'],
                (int) $current['quarantine_retention_days']
            ),
        ];

        $settings['malware_sig_version'] = sanitize_text_field(
            $existing['malware_sig_version'] ?? ( $defaults['malware_sig_version'] ?? '0.0.0' )
        );
        $settings['hash_cache_epoch'] = sanitize_text_field(
            $existing['hash_cache_epoch'] ?? ( $defaults['hash_cache_epoch'] ?? '0' )
        );
        $settings['hash_cache_keys'] = self::sanitize_hash_cache_keys(
            $existing['hash_cache_keys'] ?? ( $defaults['hash_cache_keys'] ?? [] )
        );

        $saved = self::save_settings( $settings );
        if ( $saved ) {
            do_action( 'sitefort_settings_saved_scanner_settings', $settings );
        }

        return $saved;
    }

    /**
     * Get default settings.
     * 
     * @return array
     */
    public static function get_defaults() {
        return [
            'schedule'           => 'daily',
            'scanIntensity'      => 'standard',
            'emailNotifications' => false,
            'notificationEmail'  => get_option( 'admin_email' ),
            'scanMaxDuration'    => 0,
            'stageMaxExecutionTime' => 0,
            'scanExclusions'     => '',
            'quarantine_retention_days' => 30,
            'malware_sig_version'=> '0.0.0', // Updated by Preflight
            'hash_cache_epoch'   => '0', // Updated by Preflight for global cache invalidation
            'hash_cache_keys'    => [], // Internal: proof verification keys
        ];
    }

    /**
     * Get a specific setting value.
     * 
     * @param string $key Setting key
     * @return mixed Setting value or null if invalid key
     */
    public static function get( $key ) {
        $all = self::get_all();
        return $all[$key] ?? null;
    }

    private static function normalize_schedule( $value, string $fallback ): string {
        $candidate = sanitize_key( (string) $value );
        if ( ! in_array( $candidate, self::ALLOWED_SCHEDULES, true ) ) {
            $candidate = sanitize_key( $fallback );
        }
        if ( ! in_array( $candidate, self::ALLOWED_SCHEDULES, true ) ) {
            $candidate = self::get_defaults()['schedule'];
        }

        return $candidate;
    }

    private static function normalize_scan_intensity( $value, string $fallback ): string {
        $candidate = sanitize_key( (string) $value );
        if ( ! in_array( $candidate, self::ALLOWED_INTENSITIES, true ) ) {
            $candidate = sanitize_key( $fallback );
        }
        if ( ! in_array( $candidate, self::ALLOWED_INTENSITIES, true ) ) {
            $candidate = self::get_defaults()['scanIntensity'];
        }

        return $candidate;
    }

    /**
     * Normalize quarantine retention days.
     *
     * @param mixed $value
     * @param int   $fallback
     * @return int
     */
    private static function normalize_quarantine_retention_days( $value, int $fallback ): int {
        $candidate = absint( $value );
        if ( $candidate <= 0 ) {
            $candidate = absint( $fallback );
        }
        if ( $candidate <= 0 ) {
            $candidate = (int) self::get_defaults()['quarantine_retention_days'];
        }

        return max( 1, min( 30, $candidate ) );
    }

    /**
     * Normalize scan exclusions while preserving explicit operator intent.
     *
     * @param mixed $value
     * @return string
     */
    public static function normalize_scan_exclusions( $value ): string {
        $raw = is_string( $value ) ? $value : '';

        $lines = preg_split( "/\r\n|\n|\r/", sanitize_textarea_field( $raw ) );
        if ( ! is_array( $lines ) ) {
            return '';
        }

        $normalized = [];
        $seen       = [];

        foreach ( $lines as $line ) {
            $candidate = trim( (string) $line );
            if ( $candidate === '' ) {
                continue;
            }

            $fingerprint = strtolower( $candidate );
            if ( isset( $seen[ $fingerprint ] ) ) {
                continue;
            }

            $seen[ $fingerprint ] = true;
            $normalized[]         = $candidate;
        }

        return implode( "\n", $normalized );
    }

    /**
     * Update internal scanner metadata without exposing it to user input.
     *
     * @param string $key Internal setting key.
     * @param mixed $value Internal setting value.
     * @return bool Success.
     */
    public static function set_internal_setting( string $key, $value ): bool {
        $allowed = [ 'malware_sig_version', 'hash_cache_epoch', 'hash_cache_keys' ];
        if ( ! in_array( $key, $allowed, true ) ) {
            return false;
        }

        $settings = self::strip_removed_settings( self::get_stored_settings() );
        if ( $key === 'malware_sig_version' ) {
            $settings[$key] = sanitize_text_field( (string) $value );
        } elseif ( $key === 'hash_cache_epoch' ) {
            $settings[$key] = sanitize_text_field( (string) $value );
        } elseif ( $key === 'hash_cache_keys' ) {
            $settings[$key] = self::sanitize_hash_cache_keys( is_array( $value ) ? $value : [] );
        }

        return self::save_settings( $settings );
    }

    public static function has_removed_settings(): bool {
        $stored = self::get_stored_settings();

        foreach ( self::REMOVED_SETTINGS_KEYS as $key ) {
            if ( array_key_exists( $key, $stored ) ) {
                return true;
            }
        }

        return false;
    }

    public static function cleanup_removed_settings(): bool {
        $stored = self::get_stored_settings();
        $clean = self::strip_removed_settings( $stored );

        if ( $clean === $stored ) {
            return true;
        }

        return self::save_settings( $clean );
    }

    /**
     * Persist settings into the scanner settings table.
     *
     * @param array $settings Settings payload.
     * @return bool Success.
     */
    private static function save_settings( array $settings ): bool {
        self::ensure_table();
        return \SiteFort\Includes\SettingsStore::save_service_settings( 'settings', $settings );
    }

    /**
     * Load stored settings without defaults.
     *
     * @return array
     */
    private static function get_stored_settings(): array {
        self::ensure_table();

        return \SiteFort\Includes\SettingsStore::get_service_settings( 'settings' );
    }

    /**
     * Reset the table-ready flag when the blog prefix changes on multisite.
     * Scanner settings are read fresh each call, so that flag is the only
     * request-local state to reset.
     *
     * @return void
     */
    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;
    }

    /**
     * Ensure the settings table exists for this install.
     *
     * Delegates to SettingsStore (the single version-gated schema owner) so
     * dbDelta runs once per version.
     *
     * @return void
     */
    private static function ensure_table(): void {
        self::sync_context();

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

        if ( \SiteFort\Includes\SettingsStore::ensure_table_exists() ) {
            self::$table_ready = true;
        }
    }

    /**
     * Sanitize hash cache public keys before persistence.
     *
     * @param array $keys Raw key map.
     * @return array<int, string>
     */
    private static function sanitize_hash_cache_keys( array $keys ): array {
        $clean = [];
        foreach ( $keys as $key_id => $public_key_b64 ) {
            $kid = (int) $key_id;
            if ( $kid < 0 || $kid > 255 ) {
                continue;
            }
            if ( ! is_string( $public_key_b64 ) ) {
                continue;
            }
            $public_key_b64 = trim( $public_key_b64 );
            if ( $public_key_b64 === '' ) {
                continue;
            }
            $raw = base64_decode( $public_key_b64, true );
            if ( false === $raw || strlen( $raw ) !== 32 ) {
                continue;
            }
            $clean[ $kid ] = $public_key_b64;
        }
        return $clean;
    }

    private static function strip_removed_settings( array $settings ): array {
        foreach ( self::REMOVED_SETTINGS_KEYS as $key ) {
            unset( $settings[ $key ] );
        }

        return $settings;
    }
}
