using Microsoft.IdentityModel.Tokens;
using Mirea.Api.Security.Common.Interfaces;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;

namespace Mirea.Api.Endpoint.Common.Services.Security;

public class JwtTokenService : IAccessToken
{
    public required string Issuer { private get; init; }
    public required string Audience { private get; init; }
    public TimeSpan Lifetime { private get; init; }

    public ReadOnlyMemory<byte> EncryptionKey { private get; init; }
    public ReadOnlyMemory<byte> SigningKey { private get; init; }

    public (string Token, DateTime ExpireIn) GenerateToken(string userId)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var signingKey = new SymmetricSecurityKey(SigningKey.ToArray());
        var encryptionKey = new SymmetricSecurityKey(EncryptionKey.ToArray());
        var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha512);

        var expires = DateTime.UtcNow.Add(Lifetime);

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Issuer = Issuer,
            Audience = Audience,
            Expires = expires,
            SigningCredentials = signingCredentials,
            Subject = new ClaimsIdentity(
            [
                new Claim(ClaimTypes.NameIdentifier, userId),
                // todo: get role by userId
                new Claim(ClaimTypes.Role, "")
            ]),
            EncryptingCredentials = new EncryptingCredentials(encryptionKey, SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)
        };

        var token = tokenHandler.CreateToken(tokenDescriptor);

        return (tokenHandler.WriteToken(token), expires);
    }

    public DateTimeOffset GetExpireDateTime(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var signingKey = new SymmetricSecurityKey(SigningKey.ToArray());
        var encryptionKey = new SymmetricSecurityKey(EncryptionKey.ToArray());

        var tokenValidationParameters = new TokenValidationParameters
        {
            ValidIssuer = Issuer,
            ValidAudience = Audience,
            IssuerSigningKey = signingKey,
            TokenDecryptionKey = encryptionKey,
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidateLifetime = false
        };

        try
        {
            var claimsPrincipal = tokenHandler.ValidateToken(token, tokenValidationParameters, out _);

            var expClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "exp");

            if (expClaim != null && long.TryParse(expClaim.Value, out var expUnix))
                return DateTimeOffset.FromUnixTimeSeconds(expUnix);
        }
        catch (SecurityTokenException)
        {
            return DateTimeOffset.MinValue;
        }

        return DateTimeOffset.MinValue;
    }
}