// ***********************************************************************
// 
//     AgileWebs
// 
// ***********************************************************************
using Core.Cerberos.Adapters.Common.Constants;
using Core.Cerberos.Adapters.Contracts;
using Core.Cerberos.Adapters.Options;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.Data;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;
namespace Core.Cerberos.Adapters.Services
{
    /// 
    /// Service responsible for manage authenticacion.
    /// 
    public class TokenService : ITokenService
    {
        private readonly JwtSecurityTokenHandler tokenHandler;
        private readonly IConfiguration configuration;
        private readonly JwtIssuerOptions jwtOptions;
        private readonly JsonSerializerOptions jsonOptions;
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public TokenService(
            IConfiguration configuration,
            IOptions jwtOptions
            )
        {
            tokenHandler = new JwtSecurityTokenHandler();
            this.configuration = configuration;
            this.jwtOptions = jwtOptions.Value;
            jsonOptions = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                WriteIndented = false
            };
        }
        /// 
        /// Refreshes the token.
        /// 
        public IActionResult RefreshAccessToken(HttpContext httpContext, TokenAdapter tokenAdapter)
        {
            var tokenString = httpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
            if (tokenString is not null)
            {
                var oldToken = tokenHandler.ReadJwtToken(tokenString);
                var tokenExpiration = oldToken.Claims.FirstOrDefault(c => c.Type == "exp")?.Value;
                var difference = ValidateTokenExpiration(tokenExpiration ?? "");
                if (difference.Value.TotalMinutes <= 5)
                    return new OkObjectResult(GenerateAccessToken(tokenAdapter));
            }
            return new BadRequestObjectResult("The token could not be refreshed");
        }
        /// 
        /// Generates a JWT token for the provided user data.
        /// 
        /// The user data.
        /// The user DTO with the generated token.
        public string GenerateAccessToken(TokenAdapter adapter)
        {
            var hours = 1;
            var minutes = 0;
            var expires = DateTime.UtcNow
                    .AddHours(hours)
                    .AddMinutes(minutes);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(Claims.Name, adapter?.User?.DisplayName ?? string.Empty),
                    new Claim(Claims.GUID, adapter?.User?.Guid ?? string.Empty),
                    new Claim(Claims.Email, adapter?.User?.Email ?? string.Empty),
                    new Claim(Claims.Role, adapter?.Role?.Name ?? string.Empty),
                    new Claim(Claims.RoleId, adapter?.Role?.Id ?? string.Empty),
                    new Claim(Claims.Applications, JsonSerializer.Serialize(adapter?.Role?.Applications), JsonClaimValueTypes.JsonArray),
                    new Claim(Claims.Modules, JsonSerializer.Serialize(adapter?.Modules?.Select(m => new { m.Name, m.Application, m.Route, m.Icon, m.Order }), jsonOptions), JsonClaimValueTypes.JsonArray),
                    new Claim(Claims.Companies, JsonSerializer.Serialize(adapter?.User?.Companies), JsonClaimValueTypes.JsonArray),
                    new Claim(Claims.Projects, JsonSerializer.Serialize(adapter?.User?.Projects), JsonClaimValueTypes.JsonArray),
                    new Claim(Claims.Permissions, JsonSerializer.Serialize(adapter?.Permissions?.Select(p => $"{p.Name}.{p.AccessLevel}".Replace(" ", "")).ToArray()), JsonClaimValueTypes.JsonArray),
                }),
                Expires = expires,
                Issuer = jwtOptions.Issuer,
                Audience = jwtOptions.Audience,
                SigningCredentials = jwtOptions.SigningCredentials
            };
            var token = tokenHandler.CreateEncodedJwt(tokenDescriptor);
            return token;
        }
        public ActionResult ValidateTokenExpiration(string tokenExpiration)
        {
            long unixTimestamp = long.Parse(tokenExpiration ?? "0");
            DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
            DateTime dateTimeExpiration = dateTimeOffset.UtcDateTime;
            var difference = dateTimeExpiration - DateTime.UtcNow;
            if (difference.TotalMinutes <= 0)
                return new BadRequestObjectResult("Expired token");
            else return difference;
        }
        /// 
        /// Extracts the user email claim from the http context.
        /// 
        public string GetEmailClaim(HttpContext httpContext)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var tokenString = httpContext.Request.Headers.Authorization.FirstOrDefault()?.Split(" ").Last();
            var token = tokenHandler.ReadJwtToken(tokenString);
            var email = !string.IsNullOrEmpty(token.Claims.FirstOrDefault(c => c.Type == "email")?.Value)
                ? token.Claims.FirstOrDefault(c => c.Type == "email")?.Value
                : token.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value;
            return (email is not null) ? email : "";
        }
    }
}