using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace StellaOps.Cryptography;

/// <summary>
/// Default implementation of <see cref="ICryptoProviderRegistry"/> with deterministic provider ordering.
/// </summary>
public sealed class CryptoProviderRegistry : ICryptoProviderRegistry
{
    private readonly ReadOnlyCollection<ICryptoProvider> providers;
    private readonly IReadOnlyDictionary<string, ICryptoProvider> providersByName;
    private readonly IReadOnlyList<string> preferredOrder;
    private readonly HashSet<string> preferredOrderSet;

    public CryptoProviderRegistry(
        IEnumerable<ICryptoProvider> providers,
        IEnumerable<string>? preferredProviderOrder = null)
    {
        if (providers is null)
        {
            throw new ArgumentNullException(nameof(providers));
        }

        var providerList = providers.ToList();
        if (providerList.Count == 0)
        {
            throw new ArgumentException("At least one crypto provider must be registered.", nameof(providers));
        }

        providersByName = providerList.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
        this.providers = new ReadOnlyCollection<ICryptoProvider>(providerList);

        preferredOrder = preferredProviderOrder?
            .Where(name => providersByName.ContainsKey(name))
            .Select(name => providersByName[name].Name)
            .ToArray() ?? Array.Empty<string>();
        preferredOrderSet = new HashSet<string>(preferredOrder, StringComparer.OrdinalIgnoreCase);
    }

    public IReadOnlyCollection<ICryptoProvider> Providers => providers;

    public bool TryResolve(string preferredProvider, out ICryptoProvider provider)
    {
        if (string.IsNullOrWhiteSpace(preferredProvider))
        {
            provider = default!;
            return false;
        }

        return providersByName.TryGetValue(preferredProvider, out provider!);
    }

    public ICryptoProvider ResolveOrThrow(CryptoCapability capability, string algorithmId)
    {
        if (string.IsNullOrWhiteSpace(algorithmId))
        {
            throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
        }

        foreach (var provider in EnumerateCandidates())
        {
            if (provider.Supports(capability, algorithmId))
            {
                return provider;
            }
        }

        throw new InvalidOperationException(
            $"No crypto provider is registered for capability '{capability}' and algorithm '{algorithmId}'.");
    }

    public CryptoSignerResolution ResolveSigner(
        CryptoCapability capability,
        string algorithmId,
        CryptoKeyReference keyReference,
        string? preferredProvider = null)
    {
        if (!string.IsNullOrWhiteSpace(preferredProvider) &&
            providersByName.TryGetValue(preferredProvider!, out var hinted))
        {
            if (!hinted.Supports(capability, algorithmId))
            {
                throw new InvalidOperationException(
                    $"Provider '{preferredProvider}' does not support capability '{capability}' and algorithm '{algorithmId}'.");
            }

            var signer = hinted.GetSigner(algorithmId, keyReference);
            return new CryptoSignerResolution(signer, hinted.Name);
        }

        var provider = ResolveOrThrow(capability, algorithmId);
        var resolved = provider.GetSigner(algorithmId, keyReference);
        return new CryptoSignerResolution(resolved, provider.Name);
    }

    private IEnumerable<ICryptoProvider> EnumerateCandidates()
    {
        foreach (var name in preferredOrder)
        {
            yield return providersByName[name];
        }

        foreach (var provider in providers)
        {
            if (!preferredOrderSet.Contains(provider.Name))
            {
                yield return provider;
            }
        }
    }
}
