using ColaFlow.Modules.Identity.Application.Services;
using ColaFlow.Modules.Identity.Domain.Aggregates.Users.Events;
using ColaFlow.Modules.Identity.Domain.Repositories;
using MediatR;
using Microsoft.Extensions.Logging;

namespace ColaFlow.Modules.Identity.Application.Commands.ResetPassword;

public class ResetPasswordCommandHandler : IRequestHandler<ResetPasswordCommand, bool>
{
    private readonly IPasswordResetTokenRepository _tokenRepository;
    private readonly IUserRepository _userRepository;
    private readonly IRefreshTokenRepository _refreshTokenRepository;
    private readonly ISecurityTokenService _tokenService;
    private readonly IPasswordHasher _passwordHasher;
    private readonly ILogger<ResetPasswordCommandHandler> _logger;
    private readonly IPublisher _publisher;

    public ResetPasswordCommandHandler(
        IPasswordResetTokenRepository tokenRepository,
        IUserRepository userRepository,
        IRefreshTokenRepository refreshTokenRepository,
        ISecurityTokenService tokenService,
        IPasswordHasher passwordHasher,
        ILogger<ResetPasswordCommandHandler> logger,
        IPublisher publisher)
    {
        _tokenRepository = tokenRepository;
        _userRepository = userRepository;
        _refreshTokenRepository = refreshTokenRepository;
        _tokenService = tokenService;
        _passwordHasher = passwordHasher;
        _logger = logger;
        _publisher = publisher;
    }

    public async Task<bool> Handle(ResetPasswordCommand request, CancellationToken cancellationToken)
    {
        // Validate new password
        if (string.IsNullOrWhiteSpace(request.NewPassword) || request.NewPassword.Length < 8)
        {
            _logger.LogWarning("Invalid password provided for reset");
            return false;
        }

        // Hash the token to look it up
        var tokenHash = _tokenService.HashToken(request.Token);
        var resetToken = await _tokenRepository.GetByTokenHashAsync(tokenHash, cancellationToken);

        if (resetToken == null)
        {
            _logger.LogWarning("Password reset token not found");
            return false;
        }

        if (!resetToken.IsValid)
        {
            _logger.LogWarning(
                "Password reset token is invalid. IsExpired: {IsExpired}, IsUsed: {IsUsed}",
                resetToken.IsExpired,
                resetToken.IsUsed);
            return false;
        }

        // Get user
        var user = await _userRepository.GetByIdAsync(resetToken.UserId, cancellationToken);
        if (user == null)
        {
            _logger.LogError("User {UserId} not found for password reset", resetToken.UserId);
            return false;
        }

        // Hash the new password
        var newPasswordHash = _passwordHasher.HashPassword(request.NewPassword);

        // Update user password (will emit UserPasswordChangedEvent)
        user.UpdatePassword(newPasswordHash);
        await _userRepository.UpdateAsync(user, cancellationToken);

        // Mark token as used
        resetToken.MarkAsUsed();
        await _tokenRepository.UpdateAsync(resetToken, cancellationToken);

        // Revoke all refresh tokens for security (force re-login on all devices)
        await _refreshTokenRepository.RevokeAllUserTokensAsync(
            (Guid)user.Id,
            "Password reset",
            cancellationToken);

        // Publish domain event for audit logging
        await _publisher.Publish(
            new PasswordResetCompletedEvent((Guid)user.Id, resetToken.IpAddress),
            cancellationToken);

        _logger.LogInformation(
            "Password reset successfully completed for user {UserId}. All refresh tokens revoked.",
            (Guid)user.Id);

        return true;
    }
}
