<?php
/**
 * Two-Factor Provider Registry
 *
 * Central registry for all 2FA providers. Allows Pro plugin to register
 * additional providers (SMS, WebAuthn) via filters without modifying Free code.
 *
 * @package     WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor
 * @since       2.0.0
 * @author      Ultimate Security Team
 * @license     GPL-2.0-or-later
 */

declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor;

use WPUltimateSecurity\UltimateSecurity\Contracts\TwoFactorProviderInterface;
use WPUltimateSecurity\UltimateSecurity\Config\Hooks;
use WPUltimateSecurity\UltimateSecurity\Config\Settings;
use WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Providers\EmailProvider;
use WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Providers\TOTPProvider;
use WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Providers\HOTPProvider;

/**
 * Registry for 2FA authentication providers.
 *
 * This class manages the registration and retrieval of 2FA providers.
 * Free plugin registers Email, TOTP, and HOTP providers by default.
 * Pro plugin can add SMS, WebAuthn, and other providers via the
 * FILTER_TWO_FA_PROVIDERS filter.
 *
 * Example usage:
 * ```php
 * // Get all registered providers
 * $providers = ProviderRegistry::getProviders();
 *
 * // Get a specific provider
 * $emailProvider = ProviderRegistry::getProvider('email');
 *
 * // Get enabled providers for a user
 * $userProviders = ProviderRegistry::getEnabledProvidersForUser($userId);
 * ```
 *
 * Example Pro extension:
 * ```php
 * add_filter(Hooks::FILTER_TWO_FA_PROVIDERS, function($providers) {
 *     $providers['sms'] = SMSProvider::class;
 *     $providers['webauthn'] = WebAuthnProvider::class;
 *     return $providers;
 * });
 * ```
 *
 * @since 2.0.0
 */
final class ProviderRegistry
{
    /**
     * Cached provider instances.
     *
     * @var array<string, TwoFactorProviderInterface>
     */
    private static array $instances = [];

    /**
     * Whether providers have been loaded.
     *
     * @var bool
     */
    private static bool $loaded = false;

    /**
     * Get all registered provider classes.
     *
     * Returns an array of provider slug => class name mappings.
     * Applies the FILTER_TWO_FA_PROVIDERS filter to allow Pro extensions.
     *
     * @since 2.0.0
     *
     * @return array<string, string> Provider slug => class name mapping.
     */
    public static function getProviderClasses(): array
    {
        // Default Free providers
        $providers = [
            'email' => EmailProvider::class,
            'totp' => TOTPProvider::class,
            'hotp' => HOTPProvider::class,
        ];

        /**
         * Filter to add or modify 2FA providers.
         *
         * Pro plugin hooks into this to add SMS, WebAuthn, etc.
         *
         * @since 2.0.0
         *
         * @param array<string, string> $providers Provider slug => class name mapping.
         * @return array<string, string> Modified providers array.
         */
        return apply_filters(Hooks::FILTER_TWO_FA_PROVIDERS, $providers);
    }

    /**
     * Get all provider instances.
     *
     * @since 2.0.0
     *
     * @return array<string, TwoFactorProviderInterface> Provider instances.
     */
    public static function getProviders(): array
    {
        self::loadProviders();
        return self::$instances;
    }

    /**
     * Get a specific provider instance.
     *
     * @since 2.0.0
     *
     * @param string $slug The provider slug.
     *
     * @return TwoFactorProviderInterface|null The provider instance or null.
     */
    public static function getProvider(string $slug): ?TwoFactorProviderInterface
    {
        self::loadProviders();
        return self::$instances[$slug] ?? null;
    }

    /**
     * Check if a provider is registered.
     *
     * @since 2.0.0
     *
     * @param string $slug The provider slug.
     *
     * @return bool True if the provider is registered.
     */
    public static function hasProvider(string $slug): bool
    {
        $classes = self::getProviderClasses();
        return isset($classes[$slug]);
    }

    /**
     * Get providers that are globally enabled in settings.
     *
     * @since 2.0.0
     *
     * @return array<string, TwoFactorProviderInterface> Enabled provider instances.
     */
    public static function getEnabledProviders(): array
    {
        $enabled = [];

        foreach (self::getProviders() as $slug => $provider) {
            if (self::isProviderEnabled($slug)) {
                $enabled[$slug] = $provider;
            }
        }

        return $enabled;
    }

    /**
     * Check if a provider is globally enabled in settings.
     *
     * @since 2.0.0
     *
     * @param string $slug The provider slug.
     *
     * @return bool True if the provider is enabled.
     */
    public static function isProviderEnabled(string $slug): bool
    {
        $provider = self::getProvider($slug);
        if (!$provider) {
            return false;
        }

        $settingsKey = $provider->getSettingsKey();
        $key = Settings::TWO_FA . '.' . $settingsKey . '.enable';

        return Settings::get($key, '0') === '1';
    }

    /**
     * Get providers enabled for a specific user.
     *
     * @since 2.0.0
     *
     * @param int $userId The WordPress user ID.
     *
     * @return array<string, TwoFactorProviderInterface> Enabled provider instances.
     */
    public static function getEnabledProvidersForUser(int $userId): array
    {
        $enabled = [];

        foreach (self::getEnabledProviders() as $slug => $provider) {
            if ($provider->isEnabledForUser($userId)) {
                $enabled[$slug] = $provider;
            }
        }

        return $enabled;
    }

    /**
     * Check if a user has any 2FA enabled.
     *
     * @since 2.0.0
     *
     * @param int $userId The WordPress user ID.
     *
     * @return bool True if user has at least one 2FA method enabled.
     */
    public static function userHas2FAEnabled(int $userId): bool
    {
        return !empty(self::getEnabledProvidersForUser($userId));
    }

    /**
     * Get the primary (first) enabled provider for a user.
     *
     * @since 2.0.0
     *
     * @param int $userId The WordPress user ID.
     *
     * @return TwoFactorProviderInterface|null The primary provider or null.
     */
    public static function getPrimaryProviderForUser(int $userId): ?TwoFactorProviderInterface
    {
        $providers = self::getEnabledProvidersForUser($userId);
        return !empty($providers) ? reset($providers) : null;
    }

    /**
     * Get provider slugs as array.
     *
     * @since 2.0.0
     *
     * @return array<int, string> Array of provider slugs.
     */
    public static function getProviderSlugs(): array
    {
        return array_keys(self::getProviderClasses());
    }

    /**
     * Load and instantiate all provider classes.
     *
     * @since 2.0.0
     *
     * @return void
     */
    private static function loadProviders(): void
    {
        if (self::$loaded) {
            return;
        }

        foreach (self::getProviderClasses() as $slug => $class) {
            if (!class_exists($class)) {
                continue;
            }

            // Check if class implements the interface
            if (!is_subclass_of($class, TwoFactorProviderInterface::class)) {
                continue;
            }

            self::$instances[$slug] = new $class();
        }

        self::$loaded = true;
    }

    /**
     * Reset the registry (for testing).
     *
     * @since 2.0.0
     * @internal
     *
     * @return void
     */
    public static function reset(): void
    {
        self::$instances = [];
        self::$loaded = false;
    }

    /**
     * Private constructor to prevent instantiation.
     */
    private function __construct()
    {
    }
}
