using DigitalData.Core.Abstractions.Security.Common;
using DigitalData.Core.Abstractions.Security.Key;
using DigitalData.Core.Abstractions.Security.Services;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace DigitalData.Core.Abstractions.Security.Extensions;

public static class SecurityExtensions
{
    #region Unique Security Context
    public static IEnumerable<TUniqueSecurityContext> GetByIssuer<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string issuer) where TUniqueSecurityContext : IUniqueSecurityContext
        => contextes.Where(c => c.Issuer == issuer);

    public static IEnumerable<TUniqueSecurityContext> GetByAudience<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string audience) where TUniqueSecurityContext : IUniqueSecurityContext
        => contextes.Where(c => c.Audience == audience);

    public static TUniqueSecurityContext Get<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string issuer, string audience) where TUniqueSecurityContext : IUniqueSecurityContext
        => contextes.Where(c => c.Issuer == issuer && c.Audience == audience).SingleOrDefault()
        ?? throw new InvalidOperationException($"Exactly one {typeof(TUniqueSecurityContext).Name} must exist with Issuer: '{issuer}' and Audience: '{audience}'.");

    public static bool TryGet<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string issuer, string audience, out TUniqueSecurityContext context) where TUniqueSecurityContext : IUniqueSecurityContext
    {
#pragma warning disable CS8601 // Possible null reference assignment.
        context = contextes.SingleOrDefault(c => c.Issuer == issuer && c.Audience == audience);
#pragma warning restore CS8601 // Possible null reference assignment.
        return context is not null;
    }

    public static TUniqueSecurityContext Match<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, IUniqueSecurityContext lookupContext) where TUniqueSecurityContext : IUniqueSecurityContext
        => contextes.Get(lookupContext.Issuer, lookupContext.Audience);

    public static bool TryMatch<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, IUniqueSecurityContext lookupContext, out TUniqueSecurityContext context) where TUniqueSecurityContext : IUniqueSecurityContext
        => contextes.TryGet(lookupContext.Issuer, lookupContext.Audience, out context);
    #endregion Unique Security Context

    #region De/serilization
    internal static byte[] Base64ToByte(this string base64String) => Convert.FromBase64String(base64String);

    internal static string BytesToString(this byte[] bytes) => Encoding.UTF8.GetString(bytes);

    internal static string ToBase64String(this byte[] bytes) => Convert.ToBase64String(bytes);

    internal static byte[] ToBytes(this string str) => Encoding.UTF8.GetBytes(str);

    public static string Decrypt(this IAsymmetricDecryptor decryptor, string data) => decryptor
        .Decrypt(data.Base64ToByte()).BytesToString();
    #endregion De/serilization

    #region Asymmetric Encryptor
    public static string Encrypt(this IAsymmetricEncryptor encryptor, string data) => encryptor.Encrypt(data.ToBytes()).ToBase64String();
    #endregion Asymmetric Encryptor

    #region Jwt Signature Handler
    public static string WriteToken<TPrincipal>(this IJwtSignatureHandler<TPrincipal> handler, SecurityTokenDescriptor descriptor)
        => handler.WriteToken(handler.CreateToken(descriptor));

    public static string WriteToken<TPrincipal>(this IJwtSignatureHandler<TPrincipal> handler, TPrincipal subject, IAsymmetricTokenDescriptor descriptor)
        => handler.WriteToken(handler.CreateToken(subject: subject, descriptor: descriptor));

    public static string WriteToken<TPrincipal>(this IJwtSignatureHandler<TPrincipal> handler, TPrincipal subject, string issuer, string audience)
        => handler.WriteToken(handler.CreateToken(subject: subject, issuer: issuer, audience: audience));
    #endregion Jwt Signature Handler
}