namespace Tiku.Application.Security;

public sealed class JwtOptions
{
    [System.ComponentModel.DataAnnotations.Required]
    public string Issuer { get; set; } = "tiku-backend";

    [System.ComponentModel.DataAnnotations.Required]
    public string Audience { get; set; } = "tiku-api";

    [System.ComponentModel.DataAnnotations.Required]
    public string KeyId { get; set; } = "development-ephemeral";

    public string PrivateKeyPem { get; set; } = string.Empty;

    public Dictionary<string, string> PublicKeys { get; set; } = new(StringComparer.Ordinal);

    [System.ComponentModel.DataAnnotations.Range(1, 1440)]
    public int AccessTokenMinutes { get; set; } = 15;

    [System.ComponentModel.DataAnnotations.Range(1, 365)]
    public int RefreshTokenDays { get; set; } = 30;

    public static bool BeValid(JwtOptions options, bool isProduction)
    {
        if (string.IsNullOrWhiteSpace(options.Issuer) ||
            string.IsNullOrWhiteSpace(options.Audience) ||
            string.IsNullOrWhiteSpace(options.KeyId) ||
            options.AccessTokenMinutes != 15 ||
            (isProduction && string.Equals(
                options.KeyId,
                "development-ephemeral",
                StringComparison.Ordinal)))
        {
            return false;
        }

        if (string.IsNullOrWhiteSpace(options.PrivateKeyPem))
        {
            if (isProduction)
            {
                return false;
            }
        }
        else if (!IsValidRsaPem(options.PrivateKeyPem, requirePrivateKey: true))
        {
            return false;
        }

        return options.PublicKeys.All(pair =>
            !string.IsNullOrWhiteSpace(pair.Key) &&
            !string.Equals(pair.Key, options.KeyId, StringComparison.Ordinal) &&
            IsValidRsaPem(pair.Value, requirePrivateKey: false));
    }

    private static bool IsValidRsaPem(string pem, bool requirePrivateKey)
    {
        try
        {
            using var rsa = System.Security.Cryptography.RSA.Create();
            rsa.ImportFromPem(pem);
            if (rsa.KeySize < 2048)
            {
                return false;
            }

            if (requirePrivateKey)
            {
                _ = rsa.ExportParameters(includePrivateParameters: true);
            }

            return true;
        }
        catch (Exception exception) when (
            exception is ArgumentException or
            System.Security.Cryptography.CryptographicException)
        {
            return false;
        }
    }
}
