using MediatR;
using Microsoft.AspNetCore.Http;
using Modules.User.Application.Errors;
using Modules.User.Application.Gateways;
using Modules.User.Application.Models;

namespace Modules.User.Application.Commands;

public class RefreshTokensCommand : IRequest<AuthenticationResult?>
{
    public string? Ip { get; set; }
    public HttpResponse Response { get; set; } = default!;
    public string? CookiePath { get; set; }
}

public class RefreshTokensCommandHandler(UserContext userContext, IUserRepository userRepository, IAccountAccessQueries accessQueries,
    IUnitOfWork unitOfWork) : IRequestHandler<RefreshTokensCommand, AuthenticationResult?>
{
    public async Task<AuthenticationResult?> Handle(RefreshTokensCommand request, CancellationToken cancellationToken)
    {
        var accountId = userContext.GetAccountId();
        if (!accountId.HasValue) return null;
        var oldRefreshToken = userContext.GetRefreshToken();
        
        var user = await userRepository.GetByAccountIdAsync(accountId.Value, cancellationToken)
                   ?? throw new AccountException();
        
        var now = DateTime.UtcNow;
        var status = user.Account.GetBanStatus(now);
        if (status.IsBanned) throw new AccountBannedException(status.ReleaseDate);
        
        var jwtSettings = userContext.GetJwtSettings();

        var userAgent = userContext.GetUserAgent();
        var location = await userContext.GetLocation(request.Ip);
        var newRefreshToken = TokenGenerator.GenerateRefreshToken();
        var expiresAt = now.Add(jwtSettings.RefreshTokenExpireTime);
        var clientInfo = Domain.Entities.User.ClientInfo.Create(userAgent, location?.Country, location?.Region);

        var session = user.AddOrUpdateSession(oldRefreshToken, newRefreshToken, expiresAt, clientInfo);
        await userRepository.SaveAsync(user, cancellationToken);
        await unitOfWork.SaveChangesAsync(cancellationToken);
        
        var roleNames = await accessQueries.GetRoleNamesAsync(user.Account.Id, cancellationToken);
        var permissionNames = await accessQueries.GetPermissionNamesAsync(user.Account.Id, cancellationToken);
        var permissionCodes = await accessQueries.GetEffectivePermissionCodesAsync(user.Account.Id, cancellationToken);
        
        var newAccessToken = TokenGenerator.GenerateAccessToken(user, session, jwtSettings, 
            roleNames, permissionNames, permissionCodes);
        
        return new AuthenticationResult { AccessToken = newAccessToken, SessionExpireDate = session.ExpiredDateUtc, };
    }
}