using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;

namespace StellaOps.Cryptography;

/// <summary>
/// Default in-process crypto provider exposing password hashing capabilities.
/// </summary>
public sealed class DefaultCryptoProvider : ICryptoProvider
{
    private readonly ConcurrentDictionary<string, IPasswordHasher> passwordHashers;
    private readonly ConcurrentDictionary<string, CryptoSigningKey> signingKeys;
    private static readonly HashSet<string> SupportedSigningAlgorithms = new(StringComparer.OrdinalIgnoreCase)
    {
        SignatureAlgorithms.Es256
    };

    public DefaultCryptoProvider()
    {
        passwordHashers = new ConcurrentDictionary<string, IPasswordHasher>(StringComparer.OrdinalIgnoreCase);
        signingKeys = new ConcurrentDictionary<string, CryptoSigningKey>(StringComparer.Ordinal);

        var argon = new Argon2idPasswordHasher();
        var pbkdf2 = new Pbkdf2PasswordHasher();

        passwordHashers.TryAdd(PasswordHashAlgorithm.Argon2id.ToString(), argon);
        passwordHashers.TryAdd(PasswordHashAlgorithms.Argon2id, argon);
        passwordHashers.TryAdd(PasswordHashAlgorithm.Pbkdf2.ToString(), pbkdf2);
        passwordHashers.TryAdd(PasswordHashAlgorithms.Pbkdf2Sha256, pbkdf2);
    }

    public string Name => "default";

    public bool Supports(CryptoCapability capability, string algorithmId)
    {
        if (string.IsNullOrWhiteSpace(algorithmId))
        {
            return false;
        }

        return capability switch
        {
            CryptoCapability.PasswordHashing => passwordHashers.ContainsKey(algorithmId),
            CryptoCapability.Signing or CryptoCapability.Verification => SupportedSigningAlgorithms.Contains(algorithmId),
            _ => false
        };
    }

    public IPasswordHasher GetPasswordHasher(string algorithmId)
    {
        if (!Supports(CryptoCapability.PasswordHashing, algorithmId))
        {
            throw new InvalidOperationException($"Password hashing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
        }

        return passwordHashers[algorithmId];
    }

    public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
    {
        ArgumentNullException.ThrowIfNull(keyReference);

        if (!Supports(CryptoCapability.Signing, algorithmId))
        {
            throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
        }

        if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey))
        {
            throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
        }

        if (!string.Equals(signingKey.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException(
                $"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'.");
        }

        return EcdsaSigner.Create(signingKey);
    }

    public void UpsertSigningKey(CryptoSigningKey signingKey)
    {
        ArgumentNullException.ThrowIfNull(signingKey);
        EnsureSigningSupported(signingKey.AlgorithmId);
        ValidateSigningKey(signingKey);

        signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey);
    }

    public bool RemoveSigningKey(string keyId)
    {
        if (string.IsNullOrWhiteSpace(keyId))
        {
            return false;
        }

        return signingKeys.TryRemove(keyId, out _);
    }

    public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
        => signingKeys.Values.ToArray();

    private static void EnsureSigningSupported(string algorithmId)
    {
        if (!SupportedSigningAlgorithms.Contains(algorithmId))
        {
            throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'default'.");
        }
    }

    private static void ValidateSigningKey(CryptoSigningKey signingKey)
    {
        if (!string.Equals(signingKey.AlgorithmId, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException($"Only ES256 signing keys are currently supported by provider 'default'.");
        }

        var expected = ECCurve.NamedCurves.nistP256;
        var curve = signingKey.PrivateParameters.Curve;
        if (!curve.IsNamed || !string.Equals(curve.Oid.Value, expected.Oid.Value, StringComparison.Ordinal))
        {
            throw new InvalidOperationException("ES256 signing keys must use the NIST P-256 curve.");
        }
    }
}
