Clear        


                
                    using CORE.APP.Models.Authentication;
using CORE.APP.Services;
using CORE.APP.Services.Authentication;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Users.APP.Domain;

namespace Users.APP.Features.Tokens
{
    /// <summary>
    /// Represents a token request to obtain a token response, including a JWT (access token) and refresh token.
    /// Inherits from <see cref="TokenRequestBase"/> and implements <see cref="IRequest{TokenResponse}"/> for MediatR pipeline integration.
    /// </summary>
    public class TokenRequest : TokenRequestBase, IRequest<TokenResponse>
    {
    }

    /// <summary>
    /// Handles a <see cref="TokenRequest"/> by validating credentials and generating a <see cref="TokenResponse"/> including JWT and refresh token.
    /// </summary>
    public class TokenHandler : Service<User>, IRequestHandler<TokenRequest, TokenResponse>
    {
        /// <summary>
        /// The token authentication service that will provide token operations in the methods of this class.
        /// </summary>
        private readonly ITokenAuthService _tokenAuthService; // readonly means the value or instance can only be assigned
                                                              // at this line or through the constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="TokenHandler"/> class.
        /// </summary>
        /// <param name="db">The application's user database context.</param>
        /// <param name="tokenAuthService">The injected token authentication service instance through the IoC Container.</param>
        public TokenHandler(DbContext db, ITokenAuthService tokenAuthService) : base(db)
        {
            _tokenAuthService = tokenAuthService;
        }

        /// <summary>
        /// Returns a queryable collection of <see cref="User"/> entities with their associated 
        /// <see cref="UserRole"/> and <see cref="Role"/> navigation properties eagerly included.
        /// Overrides the base method to apply eager loading.
        /// </summary>
        /// <param name="isNoTracking">
        /// If <c>true</c>, disables change tracking for better performance in read-only operations.
        /// If <c>false</c>, enables change tracking to allow updates to the queried entities.
        /// </param>
        /// <returns>
        /// An <see cref="IQueryable{User}"/> with the <see cref="UserRole"/> and <see cref="Role"/> navigation properties eagerly loaded.
        /// </returns>
        protected override IQueryable<User> Query(bool isNoTracking = true)
        {
            // u: User entity delegate, ur: UserRole entity delegate
            return base.Query(isNoTracking).Include(u => u.UserRoles).ThenInclude(ur => ur.Role);
        }

        /// <summary>
        /// Handles the token request by authenticating the user and returning a token response including JWT and refresh token.
        /// </summary>
        /// <param name="request">The token request containing username, password, security key, audience and issuer.</param>
        /// <param name="cancellationToken">Asynchronous method's token to cancel the operation.</param>
        /// <returns>A <see cref="TokenResponse"/> including the JWT and refresh token if successful, otherwise null.</returns>
        public async Task<TokenResponse> Handle(TokenRequest request, CancellationToken cancellationToken)
        {
            // Attempt to retrieve the active user by user name and password
            // isNoTracking is false for being tracked by EF Core to update the entity
            var user = await Query(false).SingleOrDefaultAsync(u => u.UserName == request.UserName && u.Password == request.Password 
                && u.IsActive, cancellationToken);

            // If user not found, return null
            if (user is null)
                return null;

            // Generate refresh token and save it to the Users table for the retrieved user with expiration date and time
            user.RefreshToken = _tokenAuthService.GetRefreshToken();
            user.RefreshTokenExpiration = DateTime.Now.AddDays(7); // the refresh token will expire after 7 days from DateTime.Now's execution value
            await Update(user, cancellationToken);

            // return a token response according to the expiration including the JWT and refresh token.
            var expiration = DateTime.Now.AddMinutes(5); // the JWT will expire after 5 minutes from DateTime.Now's execution value
            return _tokenAuthService.GetTokenResponse(user.Id, user.UserName, user.UserRoles.Select(ur => ur.Role.Name).ToArray(), 
                expiration, request.SecurityKey, request.Issuer, request.Audience, user.RefreshToken);
        }
    }
}