<?php

namespace App\Services\Payments;

use App\Contracts\Payments\PaymentProviderContract;
use App\Models\PaymentProvider as PaymentProviderModel;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class ProviderRegistry
{
    protected array $providers = [];

    protected array $configurations = [];

    public function __construct()
    {
        $this->loadConfigurationsFromDatabase();
        $this->registerProvidersFromDatabase();
    }

    /**
     * Register a payment provider
     */
    public function register(string $name, PaymentProviderContract $provider): void
    {
        $this->providers[$name] = $provider;

        Log::info('Payment provider registered', [
            'provider' => $name,
            'class' => get_class($provider),
        ]);
    }

    /**
     * Get a specific provider
     */
    public function get(string $name): ?PaymentProviderContract
    {
        return $this->providers[$name] ?? null;
    }

    /**
     * Get all registered providers
     */
    public function getAllProviders(): Collection
    {
        return collect($this->providers);
    }

    /**
     * Get only active providers
     */
    public function getActiveProviders(): Collection
    {
        return collect($this->providers)
            ->filter(fn ($provider) => $provider->isActive());
    }

    /**
     * Check if provider exists
     */
    public function has(string $name): bool
    {
        return isset($this->providers[$name]);
    }

    /**
     * Unregister a provider
     */
    public function unregister(string $name): bool
    {
        if (isset($this->providers[$name])) {
            unset($this->providers[$name]);
            Log::info('Payment provider unregistered', ['provider' => $name]);

            return true;
        }

        return false;
    }

    /**
     * Get provider configuration
     */
    public function getConfiguration(string $providerName): array
    {
        return $this->configurations[$providerName] ?? [];
    }

    /**
     * Update provider configuration
     */
    public function updateConfiguration(string $providerName, array $config): void
    {
        try {
            // Update database - encode as JSON string since the model expects to cast it
            PaymentProviderModel::where('name', $providerName)
                ->update(['configuration' => $config]); // Laravel will automatically cast array to JSON

            // Update local cache
            $this->configurations[$providerName] = array_merge(
                $this->configurations[$providerName] ?? [],
                ['config' => $config]
            );

            // Clear main cache
            Cache::forget('payment_providers_config');

            Log::info('Payment provider configuration updated', [
                'provider' => $providerName,
                'config_keys' => array_keys($config),
            ]);
        } catch (\Exception $e) {
            Log::error('Failed to update payment provider configuration', [
                'provider' => $providerName,
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Get providers that support recurring payments
     */
    public function getRecurringProviders(): Collection
    {
        return $this->getActiveProviders()
            ->filter(fn ($provider) => $provider->supportsRecurring());
    }

    /**
     * Get providers that support one-time payments
     */
    public function getOneTimeProviders(): Collection
    {
        return $this->getActiveProviders()
            ->filter(fn ($provider) => $provider->supportsOneTime());
    }

    /**
     * Get providers that support a specific currency
     */
    public function getProvidersForCurrency(string $currency): Collection
    {
        return $this->getActiveProviders()
            ->filter(function ($provider) use ($currency) {
                return in_array($currency, $provider->getSupportedCurrencies());
            });
    }

    /**
     * Get provider by webhook URL pattern
     */
    public function getProviderByWebhookUrl(string $url): ?PaymentProviderContract
    {
        return $this->getActiveProviders()
            ->first(function ($provider) use ($url) {
                $config = $provider->getConfiguration();
                $webhookUrl = $config['webhook_url'] ?? null;

                return $webhookUrl && str_contains($url, parse_url($webhookUrl, PHP_URL_PATH));
            });
    }

    /**
     * Validate provider health status
     */
    public function validateProviders(): array
    {
        $results = [];

        foreach ($this->providers as $name => $provider) {
            try {
                $isActive = $provider->isActive();
                $config = $provider->getConfiguration();

                $results[$name] = [
                    'active' => $isActive,
                    'configured' => ! empty($config),
                    'supports_recurring' => $provider->supportsRecurring(),
                    'supports_one_time' => $provider->supportsOneTime(),
                    'supported_currencies' => $provider->getSupportedCurrencies(),
                    'last_checked' => now()->toISOString(),
                ];

                if (! $isActive) {
                    Log::warning('Payment provider is inactive', ['provider' => $name]);
                }

            } catch (\Exception $e) {
                $results[$name] = [
                    'active' => false,
                    'error' => $e->getMessage(),
                    'last_checked' => now()->toISOString(),
                ];

                Log::error('Payment provider health check failed', [
                    'provider' => $name,
                    'error' => $e->getMessage(),
                ]);
            }
        }

        return $results;
    }

    /**
     * Get provider statistics
     */
    public function getProviderStats(): array
    {
        $stats = [
            'total_providers' => count($this->providers),
            'active_providers' => 0,
            'recurring_providers' => 0,
            'one_time_providers' => 0,
            'supported_currencies' => [],
            'providers' => [],
        ];

        foreach ($this->providers as $name => $provider) {
            $isActive = $provider->isActive();

            if ($isActive) {
                $stats['active_providers']++;
            }

            if ($provider->supportsRecurring()) {
                $stats['recurring_providers']++;
            }

            if ($provider->supportsOneTime()) {
                $stats['one_time_providers']++;
            }

            $stats['supported_currencies'] = array_merge(
                $stats['supported_currencies'],
                $provider->getSupportedCurrencies()
            );

            $stats['providers'][$name] = [
                'active' => $isActive,
                'class' => get_class($provider),
                'supports_recurring' => $provider->supportsRecurring(),
                'supports_one_time' => $provider->supportsOneTime(),
                'currencies' => $provider->getSupportedCurrencies(),
            ];
        }

        $stats['supported_currencies'] = array_unique($stats['supported_currencies']);

        return $stats;
    }

    /**
     * Load provider configurations from database
     */
    protected function loadConfigurationsFromDatabase(): void
    {
        try {
            // Load from cache first
            $cachedConfigs = Cache::get('payment_providers_config', []);

            if (empty($cachedConfigs)) {
                // Load from database
                $providers = PaymentProviderModel::where('is_active', true)->get();

                $this->configurations = [];
                foreach ($providers as $provider) {
                    // Configuration is already cast to array by the model
                    $configData = $provider->configuration ?? [];

                    $this->configurations[$provider->name] = [
                        'enabled' => $provider->is_active,
                        'display_name' => $provider->display_name,
                        'class' => $configData['class'] ?? null,
                        'config' => $configData,
                        'supports_recurring' => $provider->supports_recurring,
                        'supports_one_time' => $provider->supports_one_time,
                        'supported_currencies' => $provider->supported_currencies ?? [],
                        'fee_structure' => $provider->fee_structure ?? [],
                        'priority' => $provider->priority,
                        'is_fallback' => $provider->is_fallback,
                    ];
                }

                // Cache for 1 hour
                Cache::put('payment_providers_config', $this->configurations, 3600);
            } else {
                $this->configurations = $cachedConfigs;
            }
        } catch (\Exception $e) {
            Log::error('Failed to load payment provider configurations from database', [
                'error' => $e->getMessage(),
            ]);
            $this->configurations = [];
        }
    }

    /**
     * Register providers from database
     */
    protected function registerProvidersFromDatabase(): void
    {
        try {
            $activeProviders = PaymentProviderModel::where('is_active', true)->get();

            foreach ($activeProviders as $providerModel) {
                $this->registerProviderFromModel($providerModel);
            }
        } catch (\Exception $e) {
            Log::error('Failed to register payment providers from database', [
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Register provider from database model
     */
    protected function registerProviderFromModel(PaymentProviderModel $providerModel): void
    {
        // Configuration is already cast to array by the model
        $configData = $providerModel->configuration ?? [];
        $providerClass = $configData['class'] ?? null;

        if (! $providerClass || ! class_exists($providerClass)) {
            Log::error('Payment provider class not found', [
                'provider' => $providerModel->name,
                'class' => $providerClass,
            ]);

            return;
        }

        try {
            // Use the full configuration data for the provider
            $config = $this->getConfiguration($providerModel->name);
            $provider = new $providerClass($config);

            if ($provider instanceof PaymentProviderContract) {
                $this->register($providerModel->name, $provider);
            } else {
                Log::error('Payment provider does not implement contract', [
                    'provider' => $providerModel->name,
                    'class' => $providerClass,
                ]);
            }

        } catch (\Exception $e) {
            Log::error('Failed to register payment provider', [
                'provider' => $providerModel->name,
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Get provider class name based on provider name from database
     */
    protected function getProviderClass(string $providerName): ?string
    {
        $config = $this->getConfiguration($providerName);

        return $config['class'] ?? null;
    }

    /**
     * Get API key from database configuration
     */
    protected function getApiKeyFromConfig(string $providerName): ?string
    {
        $config = $this->getConfiguration($providerName);
        $configData = $config['config'] ?? [];

        // Try different possible API key field names
        $apiKeyFields = [
            'secret_key',
            'api_key',
            'merchant_api_key',
            'private_key',
            'key',
        ];

        foreach ($apiKeyFields as $field) {
            if (isset($configData[$field]) && ! empty($configData[$field])) {
                return $configData[$field];
            }
        }

        return null;
    }

    /**
     * Get webhook secret from database configuration
     */
    protected function getWebhookSecretFromConfig(string $providerName): ?string
    {
        $config = $this->getConfiguration($providerName);
        $configData = $config['config'] ?? [];

        // Try different possible webhook secret field names
        $webhookFields = [
            'webhook_secret',
            'secret',
            'signing_secret',
        ];

        foreach ($webhookFields as $field) {
            if (isset($configData[$field]) && ! empty($configData[$field])) {
                return $configData[$field];
            }
        }

        return null;
    }

    /**
     * Refresh provider (useful for configuration changes)
     */
    public function refreshProvider(string $name): bool
    {
        try {
            // Clear cache to force reload from database
            Cache::forget('payment_providers_config');

            // Unregister current instance
            unset($this->providers[$name]);

            // Reload configurations from database
            $this->loadConfigurationsFromDatabase();

            // Re-register from database
            $providerModel = PaymentProviderModel::where('name', $name)
                ->where('is_active', true)
                ->first();

            if ($providerModel) {
                $this->registerProviderFromModel($providerModel);
            }

            return isset($this->providers[$name]);
        } catch (\Exception $e) {
            Log::error('Failed to refresh payment provider', [
                'provider' => $name,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    /**
     * Enable/disable a provider
     */
    public function toggleProvider(string $name, bool $enabled): bool
    {
        try {
            // Update database
            $updated = PaymentProviderModel::where('name', $name)
                ->update(['is_active' => $enabled]);

            if (! $updated) {
                return false;
            }

            // Clear cache to force reload
            Cache::forget('payment_providers_config');

            // Refresh the provider to apply changes
            return $this->refreshProvider($name);
        } catch (\Exception $e) {
            Log::error('Failed to toggle payment provider', [
                'provider' => $name,
                'enabled' => $enabled,
                'error' => $e->getMessage(),
            ]);

            return false;
        }
    }

    /**
     * Get provider for fallback
     */
    public function getFallbackProvider(): ?PaymentProviderContract
    {
        try {
            // First try to get the provider marked as fallback in database
            $fallbackProvider = PaymentProviderModel::where('is_fallback', true)
                ->where('is_active', true)
                ->first();

            if ($fallbackProvider && $this->has($fallbackProvider->name)) {
                $provider = $this->get($fallbackProvider->name);
                if ($provider && $provider->isActive()) {
                    return $provider;
                }
            }

            // Fallback to environment variable
            $fallbackProviderName = env('PAYMENT_FALLBACK_PROVIDER', 'stripe');
            if ($this->has($fallbackProviderName)) {
                $provider = $this->get($fallbackProviderName);
                if ($provider && $provider->isActive()) {
                    return $provider;
                }
            }

            // Return first active provider as final fallback
            return $this->getActiveProviders()->first();
        } catch (\Exception $e) {
            Log::error('Failed to get fallback provider', [
                'error' => $e->getMessage(),
            ]);

            return $this->getActiveProviders()->first();
        }
    }
}
