using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;

namespace Tiku.Application.Security;

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

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

    [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);

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

    [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, true))
        {
            return false;
        }

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

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

            if (requirePrivateKey) _ = rsa.ExportParameters(true);

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