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

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

public class ForgotPasswordCommandHandler : IRequestHandler<ForgotPasswordCommand, Unit>
{
    private readonly IUserRepository _userRepository;
    private readonly ITenantRepository _tenantRepository;
    private readonly IPasswordResetTokenRepository _tokenRepository;
    private readonly ISecurityTokenService _tokenService;
    private readonly IEmailService _emailService;
    private readonly IEmailTemplateService _emailTemplateService;
    private readonly IRateLimitService _rateLimitService;
    private readonly ILogger<ForgotPasswordCommandHandler> _logger;

    public ForgotPasswordCommandHandler(
        IUserRepository userRepository,
        ITenantRepository tenantRepository,
        IPasswordResetTokenRepository tokenRepository,
        ISecurityTokenService tokenService,
        IEmailService emailService,
        IEmailTemplateService emailTemplateService,
        IRateLimitService rateLimitService,
        ILogger<ForgotPasswordCommandHandler> logger)
    {
        _userRepository = userRepository;
        _tenantRepository = tenantRepository;
        _tokenRepository = tokenRepository;
        _tokenService = tokenService;
        _emailService = emailService;
        _emailTemplateService = emailTemplateService;
        _rateLimitService = rateLimitService;
        _logger = logger;
    }

    public async Task<Unit> Handle(ForgotPasswordCommand request, CancellationToken cancellationToken)
    {
        // Rate limiting: 3 requests per hour per email
        var rateLimitKey = $"forgot-password:{request.Email.ToLowerInvariant()}";
        var isAllowed = await _rateLimitService.IsAllowedAsync(
            rateLimitKey,
            3,
            TimeSpan.FromHours(1),
            cancellationToken);

        if (!isAllowed)
        {
            _logger.LogWarning(
                "Rate limit exceeded for forgot password. Email: {Email}, IP: {IpAddress}",
                request.Email,
                request.IpAddress);

            // Still return success to prevent email enumeration
            return Unit.Value;
        }

        // Get tenant by slug
        TenantSlug tenantSlug;
        try
        {
            tenantSlug = TenantSlug.Create(request.TenantSlug);
        }
        catch (ArgumentException ex)
        {
            _logger.LogWarning("Invalid tenant slug: {TenantSlug} - {Error}", request.TenantSlug, ex.Message);
            // Return success to prevent enumeration
            return Unit.Value;
        }

        var tenant = await _tenantRepository.GetBySlugAsync(tenantSlug, cancellationToken);
        if (tenant == null)
        {
            _logger.LogWarning("Tenant not found: {TenantSlug}", request.TenantSlug);
            // Return success to prevent enumeration
            return Unit.Value;
        }

        // Get user by email
        Email email;
        try
        {
            email = Email.Create(request.Email);
        }
        catch (ArgumentException ex)
        {
            _logger.LogWarning("Invalid email: {Email} - {Error}", request.Email, ex.Message);
            // Return success to prevent enumeration
            return Unit.Value;
        }

        var user = await _userRepository.GetByEmailAsync(TenantId.Create(tenant.Id), email, cancellationToken);
        if (user == null)
        {
            _logger.LogWarning(
                "User not found for password reset. Email: {Email}, Tenant: {TenantSlug}",
                request.Email,
                request.TenantSlug);

            // Return success to prevent email enumeration attack
            return Unit.Value;
        }

        // Invalidate all existing password reset tokens for this user
        await _tokenRepository.InvalidateAllForUserAsync(UserId.Create(user.Id), cancellationToken);

        // Generate new password reset token (1-hour expiration)
        var token = _tokenService.GenerateToken();
        var tokenHash = _tokenService.HashToken(token);
        var expiresAt = DateTime.UtcNow.AddHours(1);

        var resetToken = PasswordResetToken.Create(
            UserId.Create(user.Id),
            tokenHash,
            expiresAt,
            request.IpAddress,
            request.UserAgent);

        await _tokenRepository.AddAsync(resetToken, cancellationToken);

        // Construct reset URL
        var resetUrl = $"{request.BaseUrl}/reset-password?token={token}";

        // Send password reset email
        var emailBody = _emailTemplateService.RenderPasswordResetEmail(
            user.FullName.ToString(),
            resetUrl);

        var emailMessage = new EmailMessage(
            To: user.Email,
            Subject: "Reset Your Password - ColaFlow",
            HtmlBody: emailBody
        );

        var emailSent = await _emailService.SendEmailAsync(emailMessage, cancellationToken);

        if (emailSent)
        {
            _logger.LogInformation(
                "Password reset email sent. UserId: {UserId}, Email: {Email}",
                user.Id,
                user.Email);
        }
        else
        {
            _logger.LogError(
                "Failed to send password reset email. UserId: {UserId}, Email: {Email}",
                user.Id,
                user.Email);
        }

        // Always return success to prevent email enumeration
        return Unit.Value;
    }
}
