using System;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;

namespace StellaOps.Cryptography;

internal sealed class EcdsaSigner : ICryptoSigner
{
    private static readonly string[] DefaultKeyOps = { "sign", "verify" };
    private readonly CryptoSigningKey signingKey;

    private EcdsaSigner(CryptoSigningKey signingKey)
        => this.signingKey = signingKey ?? throw new ArgumentNullException(nameof(signingKey));

    public string KeyId => signingKey.Reference.KeyId;

    public string AlgorithmId => signingKey.AlgorithmId;

    public static ICryptoSigner Create(CryptoSigningKey signingKey) => new EcdsaSigner(signingKey);

    public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        using var ecdsa = ECDsa.Create(signingKey.PrivateParameters);
        var hashAlgorithm = ResolveHashAlgorithm(signingKey.AlgorithmId);
        var signature = ecdsa.SignData(data.Span, hashAlgorithm);
        return ValueTask.FromResult(signature);
    }

    public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        using var ecdsa = ECDsa.Create(signingKey.PublicParameters);
        var hashAlgorithm = ResolveHashAlgorithm(signingKey.AlgorithmId);
        var verified = ecdsa.VerifyData(data.Span, signature.Span, hashAlgorithm);
        return ValueTask.FromResult(verified);
    }

    public JsonWebKey ExportPublicJsonWebKey()
    {
        var jwk = new JsonWebKey
        {
            Kid = signingKey.Reference.KeyId,
            Alg = signingKey.AlgorithmId,
            Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve,
            Use = JsonWebKeyUseNames.Sig,
            Crv = ResolveCurve(signingKey.AlgorithmId)
        };

        foreach (var op in DefaultKeyOps)
        {
            jwk.KeyOps.Add(op);
        }

        jwk.X = Base64UrlEncoder.Encode(signingKey.PublicParameters.Q.X ?? Array.Empty<byte>());
        jwk.Y = Base64UrlEncoder.Encode(signingKey.PublicParameters.Q.Y ?? Array.Empty<byte>());

        return jwk;
    }

    private static HashAlgorithmName ResolveHashAlgorithm(string algorithmId) =>
        algorithmId switch
        {
            { } alg when string.Equals(alg, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA256,
            { } alg when string.Equals(alg, SignatureAlgorithms.Es384, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA384,
            { } alg when string.Equals(alg, SignatureAlgorithms.Es512, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA512,
            _ => throw new InvalidOperationException($"Unsupported ECDSA signing algorithm '{algorithmId}'.")
        };

    private static string ResolveCurve(string algorithmId)
        => algorithmId switch
        {
            { } alg when string.Equals(alg, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P256,
            { } alg when string.Equals(alg, SignatureAlgorithms.Es384, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P384,
            { } alg when string.Equals(alg, SignatureAlgorithms.Es512, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P521,
            _ => throw new InvalidOperationException($"Unsupported ECDSA curve mapping for algorithm '{algorithmId}'.")
        };
}
