<?php

namespace App\Services\Core;

use App\Models\Setting;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Arr;

class ConfigurationManager
{
    private const CACHE_PREFIX = 'config_manager:';
    private const CACHE_TTL = 3600; // 1 hour

    private array $layerCache = [];

    /**
     * Configuration layers in order of priority (highest first)
     */
    private array $layers = [
        'runtime',      // Dynamic runtime config (database)
        'environment',  // Environment variables (.env)
        'framework',    // Framework default config files
        'module'        // Module-specific defaults
    ];

    /**
     * Get configuration value using layered approach
     */
    public function get(string $key, mixed $default = null): mixed
    {
        // Check cache first
        $cacheKey = self::CACHE_PREFIX . $key;
        if ($cached = Cache::get($cacheKey)) {
            return $cached;
        }

        // Try each layer in priority order
        foreach ($this->layers as $layer) {
            $value = $this->getFromLayer($layer, $key);
            if ($value !== null) {
                // Cache the result
                Cache::put($cacheKey, $value, self::CACHE_TTL);
                return $value;
            }
        }

        return $default;
    }

    /**
     * Set configuration value in runtime layer (database)
     */
    public function set(string $key, mixed $value, string $category = 'general', string $description = null): void
    {
        try {
            $setting = Setting::firstOrNew(['key' => $key]);
            $setting->value = $value;
            $setting->type = $this->getValueType($value);
            $setting->category = $category;
            if ($description) {
                $setting->description = $description;
            }
            $setting->is_active = true;
            $setting->save();

            // Clear cache
            $this->clearCache($key);

            Log::info("Configuration set: {$key}");
        } catch (\Throwable $e) {
            Log::error("Failed to set configuration: {$key}", [
                'error' => $e->getMessage()
            ]);
            throw $e;
        }
    }

    /**
     * Get multiple configuration values
     */
    public function getMultiple(array $keys): array
    {
        $result = [];
        foreach ($keys as $key) {
            $result[$key] = $this->get($key);
        }
        return $result;
    }

    /**
     * Set multiple configuration values
     */
    public function setMultiple(array $values, string $category = 'general'): void
    {
        foreach ($values as $key => $value) {
            $this->set($key, $value, $category);
        }
    }

    /**
     * Check if configuration key exists in any layer
     */
    public function has(string $key): bool
    {
        return $this->get($key) !== null;
    }

    /**
     * Delete configuration from runtime layer
     */
    public function forget(string $key): void
    {
        try {
            Setting::where('key', $key)->delete();
            $this->clearCache($key);
            Log::info("Configuration deleted: {$key}");
        } catch (\Throwable $e) {
            Log::error("Failed to delete configuration: {$key}", [
                'error' => $e->getMessage()
            ]);
            throw $e;
        }
    }

    /**
     * Get configuration values for a module
     */
    public function getModuleConfig(string $moduleName): array
    {
        $prefix = "modules.{$moduleName}.";
        $config = [];

        // Get all keys that start with the module prefix
        foreach ($this->getAllRuntimeKeys() as $key) {
            if (str_starts_with($key, $prefix)) {
                $moduleKey = substr($key, strlen($prefix));
                $config[$moduleKey] = $this->get($key);
            }
        }

        // Merge with framework defaults
        $frameworkConfig = config("modules.{$moduleName}", []);
        return array_merge($frameworkConfig, $config);
    }

    /**
     * Set configuration for a module
     */
    public function setModuleConfig(string $moduleName, array $config): void
    {
        foreach ($config as $key => $value) {
            $fullKey = "modules.{$moduleName}.{$key}";
            $this->set($fullKey, $value, 'module', "Module {$moduleName} configuration: {$key}");
        }
    }

    /**
     * Get configuration values by category
     */
    public function getByCategory(string $category): array
    {
        try {
            return Cache::remember(
                self::CACHE_PREFIX . 'category:' . $category,
                self::CACHE_TTL,
                function () use ($category) {
                    $settings = Setting::active()
                        ->byCategory($category)
                        ->get(['key', 'value', 'type', 'is_encrypted']);

                    $config = [];
                    foreach ($settings as $setting) {
                        $config[$setting->key] = $setting->is_encrypted 
                            ? $setting->getDecryptedValueAttribute() 
                            : $setting->value;
                    }

                    return $config;
                }
            );
        } catch (\Throwable $e) {
            Log::error('Failed to get category config', ['error' => $e->getMessage()]);
            return [];
        }
    }

    /**
     * Clear all configuration cache
     */
    public function clearAllCache(): void
    {
        $pattern = self::CACHE_PREFIX . '*';
        
        // Note: This is a simplified cache clear - in production you might want
        // to use cache tags or a more sophisticated cache clearing mechanism
        $this->layerCache = [];
        
        Log::info('Configuration cache cleared');
    }

    /**
     * Clear cache for all settings
     */
    public function clearCache(string $key = null): void
    {
        if ($key) {
            // Clear specific key
            $cacheKey = self::CACHE_PREFIX . $key;
            Cache::forget($cacheKey);
            
            // Also clear from layer cache
            foreach ($this->layers as $layer) {
                unset($this->layerCache["{$layer}:{$key}"]);
            }
        } else {
            // Clear all cache
            $this->clearAllCache();
        }
    }

    /**
     * Get configuration from specific layer
     */
    private function getFromLayer(string $layer, string $key): mixed
    {
        // Use layer cache to avoid repeated database queries
        $layerKey = "{$layer}:{$key}";
        if (isset($this->layerCache[$layerKey])) {
            return $this->layerCache[$layerKey];
        }

        $value = match ($layer) {
            'runtime' => $this->getRuntimeValue($key),
            'environment' => $this->getEnvironmentValue($key),
            'framework' => $this->getFrameworkValue($key),
            'module' => $this->getModuleDefaultValue($key),
            default => null
        };

        $this->layerCache[$layerKey] = $value;
        return $value;
    }

    /**
     * Get value from runtime database settings
     */
    private function getRuntimeValue(string $key): mixed
    {
        try {
            $setting = Setting::active()
                ->where('key', $key)
                ->first();

            if (!$setting) {
                return null;
            }

            return $setting->is_encrypted 
                ? $setting->getDecryptedValueAttribute() 
                : $setting->value;
        } catch (\Throwable $e) {
            Log::warning("Failed to get runtime config: {$key}", [
                'error' => $e->getMessage()
            ]);
            return null;
        }
    }

    /**
     * Get value from environment variables
     */
    private function getEnvironmentValue(string $key): mixed
    {
        // Convert config key to env variable format
        $envKey = strtoupper(str_replace('.', '_', $key));
        return env($envKey);
    }

    /**
     * Get value from framework config files
     */
    private function getFrameworkValue(string $key): mixed
    {
        return config($key);
    }

    /**
     * Get module default value
     */
    private function getModuleDefaultValue(string $key): mixed
    {
        if (!str_starts_with($key, 'modules.')) {
            return null;
        }

        // Extract module name and config key
        $parts = explode('.', substr($key, 8), 2); // Remove 'modules.' prefix
        if (count($parts) < 2) {
            return null;
        }

        [$moduleName, $configKey] = $parts;
        
        // Try to get from module config file
        return config("modules.{$moduleName}.{$configKey}");
    }

    /**
     * Decode stored configuration value
     */
    private function decodeValue(string $value, string $type): mixed
    {
        return match ($type) {
            'json' => json_decode($value, true),
            'boolean' => filter_var($value, FILTER_VALIDATE_BOOLEAN),
            'integer' => (int) $value,
            'float' => (float) $value,
            default => $value
        };
    }

    /**
     * Determine value type for storage
     */
    private function getValueType(mixed $value): string
    {
        return match (true) {
            is_bool($value) => 'boolean',
            is_int($value) => 'integer',
            is_float($value) => 'float',
            is_array($value) || is_object($value) => 'json',
            default => 'string'
        };
    }



    /**
     * Get all runtime configuration keys
     */
    private function getAllRuntimeKeys(): array
    {
        try {
            return Cache::remember(
                self::CACHE_PREFIX . 'runtime_keys',
                self::CACHE_TTL,
                fn() => Setting::active()->pluck('key')->toArray()
            );
        } catch (\Throwable $e) {
            Log::error('Failed to get runtime keys', ['error' => $e->getMessage()]);
            return [];
        }
    }

    /**
     * Get configuration schema for validation
     */
    public function getSchema(string $module = null): array
    {
        $schema = [];
        
        if ($module) {
            $configFile = config_path("modules/{$module}.php");
            if (file_exists($configFile)) {
                $config = include $configFile;
                $schema = $config['schema'] ?? [];
            }
        }
        
        return $schema;
    }

    /**
     * Validate configuration against schema
     */
    public function validateConfig(string $key, mixed $value, array $schema = []): bool
    {
        // Basic validation - can be extended with more sophisticated rules
        if (empty($schema)) {
            return true;
        }
        
        $rules = $schema[$key] ?? null;
        if (!$rules) {
            return true;
        }
        
        // Type validation
        if (isset($rules['type'])) {
            $expectedType = $rules['type'];
            $actualType = gettype($value);
            
            if ($actualType !== $expectedType) {
                return false;
            }
        }
        
        // Range validation for numbers
        if (is_numeric($value)) {
            if (isset($rules['min']) && $value < $rules['min']) {
                return false;
            }
            if (isset($rules['max']) && $value > $rules['max']) {
                return false;
            }
        }
        
        return true;
    }
}