<?php
/**
 * Centralized configuration manager for BotFend Anti-Bot Firewall
 *
 * @package BotFend\Core
 */

namespace BotFend\Core;

use BotFend\Security\Encryption;
use WP_Error;

defined('ABSPATH') || exit;

final class Config {

    private const CONFIG_VERSION = '1.0.0';
    private static $instance = null;
    private $wpdb;
    private $encryption;
    private $cache = [];
    private $dirty = [];
    private $definitions = [];
    private $cache_ttl = 3600;

    private $volatile = [
        'tor_last_update',
        'adaptive_mode_manual',
        'mode_history',
        'effectiveness_log',
        'feed_performance_stats',
        'feed_false_positives',
        'last_auto_unblock',
        'security_violations',
        'attack_log',
    ];

    private $use_object_cache = false;

    private function __construct($wpdb = null) {
        $this->wpdb = $wpdb ?: $GLOBALS['wpdb'];
        $this->encryption = Encryption::get_instance();
        $this->load_definitions();
        $this->use_object_cache = wp_using_ext_object_cache() && function_exists('wp_cache_flush_group');
        $this->cache_ttl = apply_filters('botfend_config_cache_ttl', 3600);
        
        if (defined('BOTFEND_PRELOAD_CONFIG') && BOTFEND_PRELOAD_CONFIG) {
            add_action('init', [$this, 'preload_autoload'], 1);
        }
    }

    private function load_definitions(): void {
        $definitions_file = BOTFEND_INC_PATH . 'config/Definitions.php';
        
        if (file_exists($definitions_file)) {
            $definitions = require $definitions_file;
            if (is_array($definitions)) {
                $this->definitions = $definitions;
            } else {
                $this->definitions = [];
            }
        } else {
            error_log('BotFend: Definitions file not found at ' . $definitions_file);
            $this->definitions = [];
        }
    }

    public static function get_instance(array $args = []): self {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function get(string $key, $default = null, bool $raw = false) {
        $value = $this->get_internal($key, $default);
        
        if (is_array($value) || is_object($value) || is_null($value)) {
            return $value ?? $default;
        }
        
        if (!$raw && $this->is_sensitive($key) && !empty($value) && is_string($value)) {
            if ($this->encryption->is_encrypted($value)) {
                $value = $this->encryption->decrypt($value);
            }
            return $this->encryption->mask($value);
        }
        
        return $value;
    }

    private function get_option_key(string $key): string {
        return (strpos($key, 'botfend_') === 0) ? $key : 'botfend_' . $key;
    }

    private function get_base_key(string $key): string {
        return (strpos($key, 'botfend_') === 0) ? substr($key, 8) : $key;
    }

    private function get_internal(string $key, $default = null) {
        $base_key = $this->get_base_key($key);
        $option_key = $this->get_option_key($key);
        $is_volatile = in_array($base_key, $this->volatile, true);
        
        if (!$is_volatile && $this->use_object_cache) {
            $cached = wp_cache_get($base_key, 'botfend_config');
            if (false !== $cached) {
                return $cached;
            }
        }
        
        if (isset($this->cache[$base_key]) && !isset($this->dirty[$base_key])) {
            $value = $this->cache[$base_key];
            if (!$is_volatile && $this->use_object_cache) {
                wp_cache_set($base_key, $value, 'botfend_config', $this->cache_ttl);
            }
            return $value;
        }

        $value = get_option($option_key, null);

        if (null === $value && isset($this->definitions[$option_key])) {
            $definition = $this->definitions[$option_key];
            if (null === $definition['default'] && $option_key === 'botfend_session_secure_cookie') {
                $value = is_ssl();
            } else {
                $value = $definition['default'];
            }
        } elseif (null === $value) {
            $value = $default;
        }

        if (isset($this->definitions[$option_key])) {
            $value = $this->cast_value($option_key, $value);
        }

        if (!$is_volatile) {
            $this->cache[$base_key] = $value;
            if ($this->use_object_cache) {
                wp_cache_set($base_key, $value, 'botfend_config', $this->cache_ttl);
            }
        }

        return $value;
    }

    public function get_bool(string $key, bool $default = false): bool {
        $value = $this->get_internal($key, $default);
        return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
    }

    public function get_int(string $key, int $default = 0): int {
        $value = $this->get_internal($key, $default);
        return intval($value);
    }

    public function get_float(string $key, float $default = 0.0): float {
        $value = $this->get_internal($key, $default);
        return floatval($value);
    }

    public function get_string(string $key, string $default = ''): string {
        $value = $this->get($key, $default);
        
        if (is_array($value)) {
            return !empty($value) ? wp_json_encode($value) : $default;
        }
        if (is_object($value)) {
            return wp_json_encode($value);
        }
        if (is_null($value)) {
            return $default;
        }
        
        return is_string($value) ? $value : (string) $value;
    }

    public function get_array(string $key, array $default = []): array {
        $value = $this->get_internal($key, $default);
        
        if (is_array($value)) {
            return $value;
        }
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                return $decoded;
            }
            return array_filter(array_map('trim', explode("\n", $value)));
        }
        if (is_null($value)) {
            return $default;
        }
        
        return (array) $value;
    }

    public function set(string $key, $value) {
        if (defined('DOING_AJAX') && DOING_AJAX) {
            if (!check_ajax_referer('botfend_admin_nonce', 'nonce', false)) {
                return new WP_Error('invalid_nonce', esc_html__('Invalid security token.', 'botfend-anti-bot-firewall'));
            }
        }

        if (!current_user_can('manage_options')) {
            return new WP_Error('forbidden', esc_html__('You do not have permission to modify configuration.', 'botfend-anti-bot-firewall'));
        }

        $option_key = $this->get_option_key($key);
        $base_key = $this->get_base_key($key);

        if (isset($this->definitions[$option_key])) {
            $validated = $this->validate_value($option_key, $value);
            if (is_wp_error($validated)) {
                return $validated;
            }
            $value = $validated;
            $autoload = isset($this->definitions[$option_key]['autoload']) ? (bool) $this->definitions[$option_key]['autoload'] : true;
            
            if ($this->is_sensitive($option_key) && !empty($value) && is_string($value)) {
                if (!$this->encryption->is_encrypted($value)) {
                    $value = $this->encryption->encrypt($value);
                }
            }
        } else {
            $autoload = true;
        }

        $exists = false !== get_option($option_key, false);
        
        if ($exists) {
            $updated = update_option($option_key, $value);
        } else {
            $updated = add_option($option_key, $value, '', $autoload ? 'yes' : 'no');
        }

        if ($updated) {
            $this->cache[$base_key] = $value;
            $this->dirty[$base_key] = true;
            $this->clear_object_cache($base_key);
            do_action('botfend_config_updated', $option_key, $value);
            $this->debug_log("Option updated: {$option_key}");
        }

        return $updated;
    }

    public function delete(string $key) {
        if (defined('DOING_AJAX') && DOING_AJAX) {
            if (!check_ajax_referer('botfend_admin_nonce', 'nonce', false)) {
                return new WP_Error('invalid_nonce', esc_html__('Invalid security token.', 'botfend-anti-bot-firewall'));
            }
        }

        if (!current_user_can('manage_options')) {
            return new WP_Error('forbidden', esc_html__('You do not have permission to delete configuration.', 'botfend-anti-bot-firewall'));
        }

        $option_key = $this->get_option_key($key);
        $base_key = $this->get_base_key($key);

        if (isset($this->definitions[$option_key])) {
            return new WP_Error('protected', sprintf(
                esc_html__('Option %s is protected and cannot be deleted.', 'botfend-anti-bot-firewall'),
                esc_html($option_key)
            ));
        }

        $deleted = delete_option($option_key);

        if ($deleted) {
            unset($this->cache[$base_key]);
            unset($this->dirty[$base_key]);
            $this->clear_object_cache($base_key);
            $this->debug_log("Option deleted: {$option_key}");
        }

        return $deleted;
    }

    public function has(string $key): bool {
        $base_key = $this->get_base_key($key);
        $option_key = $this->get_option_key($key);
        
        if (isset($this->cache[$base_key]) || isset($this->definitions[$option_key])) {
            return true;
        }
        return false !== get_option($option_key, false);
    }

    public function get_many(array $keys): array {
        $result = [];
        foreach ($keys as $key) {
            $result[$key] = $this->get_internal($key);
        }
        return $result;
    }

    public function set_many(array $values): array {
        $results = [];
        foreach ($values as $key => $value) {
            $results[$key] = $this->set($key, $value);
        }
        return $results;
    }

    public function preload(array $keys): void {
        $needs_load = [];
        foreach ($keys as $key) {
            $base_key = $this->get_base_key($key);
            if (!isset($this->cache[$base_key]) && !in_array($base_key, $this->volatile, true)) {
                $needs_load[] = $this->get_option_key($key);
            }
        }
        
        if (empty($needs_load)) {
            return;
        }
        
        $placeholders = implode(',', array_fill(0, count($needs_load), '%s'));
        $table = esc_sql($this->wpdb->options);
        
        $results = $this->wpdb->get_results(
            $this->wpdb->prepare(
                "SELECT option_name, option_value FROM {$table} WHERE option_name IN ({$placeholders})",
                $needs_load
            )
        );
        
        $loaded = [];
        foreach ($results as $row) {
            $loaded[$row->option_name] = maybe_unserialize($row->option_value);
        }
        
        foreach ($needs_load as $full_key) {
            $base_key = $this->get_base_key($full_key);
            if (isset($loaded[$full_key])) {
                $value = $this->cast_value($full_key, $loaded[$full_key]);
            } elseif (isset($this->definitions[$full_key])) {
                $value = $this->definitions[$full_key]['default'];
                if (null === $value && $full_key === 'botfend_session_secure_cookie') {
                    $value = is_ssl();
                }
            } else {
                continue;
            }
            $this->cache[$base_key] = $value;
        }
    }

    public function preload_autoload(): void {
        $autoload_keys = [];
        foreach ($this->definitions as $key => $definition) {
            if (!empty($definition['autoload']) && !in_array($this->get_base_key($key), $this->volatile, true)) {
                $autoload_keys[] = $key;
            }
        }
        $this->preload($autoload_keys);
    }

    public function clear_cache(): void {
        $this->cache = [];
        $this->dirty = [];
        if ($this->use_object_cache) {
            wp_cache_flush_group('botfend_config');
        }
        $this->debug_log('All caches cleared');
    }

    private function clear_object_cache(string $key): void {
        if ($this->use_object_cache) {
            wp_cache_delete($key, 'botfend_config');
        }
    }

    private function cast_value(string $key, $value) {
        if (!isset($this->definitions[$key]) || null === $value) {
            return $this->definitions[$key]['default'] ?? null;
        }

        $definition = $this->definitions[$key];

        switch ($definition['type']) {
            case 'bool':
                return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
            case 'int':
                return intval($value);
            case 'float':
                return floatval($value);
            case 'string':
                if (is_string($value)) {
                    if ($this->is_sensitive($key) && !empty($value) && $this->encryption->is_encrypted($value)) {
                        return $this->encryption->decrypt($value);
                    }
                    return $value;
                }
                return (string) $value;
            case 'array':
                if (is_array($value)) {
                    return $value;
                }
                if (is_string($value)) {
                    if ($this->is_sensitive($key) && !empty($value) && $this->encryption->is_encrypted($value)) {
                        $value = $this->encryption->decrypt($value);
                    }
                    $decoded = json_decode($value, true);
                    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                        return $decoded;
                    }
                    return array_filter(array_map('trim', explode("\n", $value)));
                }
                return [];
            default:
                return $value;
        }
    }

    private function validate_value(string $key, $value) {
        if (!isset($this->definitions[$key])) {
            return sanitize_text_field((string) $value);
        }

        $definition = $this->definitions[$key];

        switch ($definition['type']) {
            case 'bool':
                return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
            case 'int':
                $int_value = intval($value);
                if (isset($definition['min']) && $int_value < $definition['min']) {
                    return new WP_Error('validation_failed', sprintf(
                        esc_html__('%1$s must be between %2$d and %3$d', 'botfend-anti-bot-firewall'),
                        $key, $definition['min'], $definition['max'] ?? '∞'
                    ));
                }
                if (isset($definition['max']) && $int_value > $definition['max']) {
                    return new WP_Error('validation_failed', sprintf(
                        esc_html__('%1$s must be between %2$d and %3$d', 'botfend-anti-bot-firewall'),
                        $key, $definition['min'] ?? '0', $definition['max']
                    ));
                }
                return $int_value;
            case 'float':
                $float_value = floatval($value);
                if (isset($definition['min']) && $float_value < $definition['min']) {
                    return new WP_Error('validation_failed', sprintf(
                        esc_html__('%1$s must be between %2$f and %3$f', 'botfend-anti-bot-firewall'),
                        $key, $definition['min'], $definition['max'] ?? '∞'
                    ));
                }
                if (isset($definition['max']) && $float_value > $definition['max']) {
                    return new WP_Error('validation_failed', sprintf(
                        esc_html__('%1$s must be between %2$f and %3$f', 'botfend-anti-bot-firewall'),
                        $key, $definition['min'] ?? '0', $definition['max']
                    ));
                }
                return $float_value;
            case 'string':
                $string_value = sanitize_text_field((string) $value);
                if (isset($definition['allowed']) && !in_array($string_value, $definition['allowed'], true)) {
                    return new WP_Error('validation_failed', sprintf(
                        esc_html__('%1$s must be one of: %2$s', 'botfend-anti-bot-firewall'),
                        $key, implode(', ', $definition['allowed'])
                    ));
                }
                return $string_value;
            case 'array':
                if (is_string($value)) {
                    $decoded = json_decode($value, true);
                    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                        $array_value = $decoded;
                    } else {
                        $array_value = array_filter(array_map('trim', explode("\n", $value)));
                    }
                } else {
                    $array_value = (array) $value;
                }
                array_walk_recursive($array_value, function (&$item) {
                    if (is_string($item)) {
                        $item = sanitize_text_field($item);
                    }
                });
                return $array_value;
            default:
                return sanitize_text_field((string) $value);
        }
    }

    private function is_sensitive(string $key): bool {
        return isset($this->definitions[$key]['sensitive']) && true === $this->definitions[$key]['sensitive'];
    }

    private function debug_log($message): void {
        if (defined('WP_DEBUG') && WP_DEBUG && $this->get_bool('debug_mode', false)) {
            if (!is_string($message)) {
                $message = print_r($message, true);
            }
            error_log(sprintf('BotFend Config: %s', $message));
        }
    }

    public function get_definitions(): array {
        return $this->definitions;
    }

    public function get_volatile(): array {
        return $this->volatile;
    }

    public function export() {
        if (!current_user_can('manage_options')) {
            return new WP_Error('forbidden', esc_html__('You do not have permission to export configuration.', 'botfend-anti-bot-firewall'));
        }

        $export = [];
        foreach (array_keys($this->definitions) as $key) {
            $export[$key] = $this->get_internal($key);
        }

        return wp_json_encode([
            'plugin' => 'BotFend Anti-Bot Firewall',
            'version' => BOTFEND_VERSION,
            'config_version' => self::CONFIG_VERSION,
            'timestamp' => current_time('mysql'),
            'data' => $export,
        ], JSON_PRETTY_PRINT);
    }

    public function import(string $json) {
        if (!current_user_can('manage_options')) {
            return new WP_Error('forbidden', esc_html__('You do not have permission to import configuration.', 'botfend-anti-bot-firewall'));
        }

        $data = json_decode($json, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            return new WP_Error('invalid_json', esc_html__('Invalid JSON format.', 'botfend-anti-bot-firewall'));
        }

        if (!isset($data['data']) || !is_array($data['data'])) {
            return new WP_Error('invalid_structure', esc_html__('Invalid export structure.', 'botfend-anti-bot-firewall'));
        }

        if (strlen($json) > 1024 * 1024) {
            return new WP_Error('too_large', esc_html__('Import data too large.', 'botfend-anti-bot-firewall'));
        }

        $imported = 0;
        $failed = 0;
        $results = [];

        foreach ($data['data'] as $key => $value) {
            if (isset($this->definitions[$key])) {
                $result = $this->set($key, $value);
                if (true === $result) {
                    $imported++;
                    $results[$key] = 'success';
                } else {
                    $failed++;
                    $results[$key] = is_wp_error($result) ? $result->get_error_message() : 'failed';
                }
            }
        }

        return ['imported' => $imported, 'failed' => $failed, 'results' => $results];
    }

    public static function version(): string {
        return defined('BOTFEND_VERSION') ? BOTFEND_VERSION : '1.0.0';
    }

    public static function assets_url(string $path = ''): string {
        $base = defined('BOTFEND_ASSETS_URL') ? BOTFEND_ASSETS_URL : '';
        return $base . ltrim($path, '/');
    }

    public static function path(string $path = ''): string {
        $base = defined('BOTFEND_PATH') ? BOTFEND_PATH : '';
        return $base . ltrim($path, '/');
    }
}