using Lactose.Repositories;
using Lactose.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;

namespace Lactose.Authorization;

/*
 * Technical note:
 * 
 * The RefreshTokenTransformation is used to add a claim to the user's claims principal that indicates if
 * the user's login is still valid. while UnexpiredLoginRequirement is used to check the claim.
 *
 * More Claim Transformation and Requirements can be added with IClaimsTransformation IAuthorizationRequirement and
 * added to Program.cs
 */

public class UnexpiredLoginRequirement : IAuthorizationRequirement {

    public class RefreshTokenValidityHandler : AuthorizationHandler<UnexpiredLoginRequirement> {
        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            UnexpiredLoginRequirement requirement
        ) {
            Claim? firstOrDefault
                = context.User.Claims.FirstOrDefault(claim => claim.Type == RefreshTokenTransformation.ClaimType);

            if (firstOrDefault is not { Value: "true" }) {
                context.Fail();
                return Task.CompletedTask;
            }

            context.Succeed(requirement);
            return Task.CompletedTask;
        }
    }
}

public class RefreshTokenTransformation(
    IUserRepository userRepository,
    LactoseAuthService authService
    ) : IClaimsTransformation
{
    public const string ClaimType = "LoginValid"; 
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        ClaimsIdentity claimsIdentity = new ClaimsIdentity();
        if (principal.HasClaim(claim => claim.Type == ClaimType)) {
            foreach (ClaimsIdentity identity in principal.Identities) {
                if (identity.HasClaim(claim => claim.Type == ClaimType)) {
                    identity.RemoveClaim(identity.FindFirst(ClaimType)!);
                }
            }
        }
        var userData = authService.GetUserData(principal);
        if (userData == null) return Task.FromResult(principal);
        var user = userRepository.Find(userData.Id);
        if (user == null) return Task.FromResult(principal);
        if (string.IsNullOrEmpty(user.RefreshToken) || user.RefreshTokenExpires == null || user.RefreshTokenExpires < DateTime.Now) return Task.FromResult(principal);
        claimsIdentity.AddClaim(new Claim(ClaimType, "true", ClaimValueTypes.Boolean));
        principal.AddIdentity(claimsIdentity);
        return Task.FromResult(principal);
    }
}
