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

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

public class AcceptInvitationCommandHandler : IRequestHandler<AcceptInvitationCommand, Guid>
{
    private readonly IInvitationRepository _invitationRepository;
    private readonly IUserRepository _userRepository;
    private readonly IUserTenantRoleRepository _userTenantRoleRepository;
    private readonly ISecurityTokenService _tokenService;
    private readonly IPasswordHasher _passwordHasher;
    private readonly ILogger<AcceptInvitationCommandHandler> _logger;

    public AcceptInvitationCommandHandler(
        IInvitationRepository invitationRepository,
        IUserRepository userRepository,
        IUserTenantRoleRepository userTenantRoleRepository,
        ISecurityTokenService tokenService,
        IPasswordHasher passwordHasher,
        ILogger<AcceptInvitationCommandHandler> logger)
    {
        _invitationRepository = invitationRepository;
        _userRepository = userRepository;
        _userTenantRoleRepository = userTenantRoleRepository;
        _tokenService = tokenService;
        _passwordHasher = passwordHasher;
        _logger = logger;
    }

    public async Task<Guid> Handle(AcceptInvitationCommand request, CancellationToken cancellationToken)
    {
        // Hash the token to find the invitation
        var tokenHash = _tokenService.HashToken(request.Token);

        // Find invitation by token hash
        var invitation = await _invitationRepository.GetByTokenHashAsync(tokenHash, cancellationToken);
        if (invitation == null)
            throw new InvalidOperationException("Invalid invitation token");

        // Validate invitation is pending
        invitation.ValidateForAcceptance();

        var email = Email.Create(invitation.Email);
        var fullName = FullName.Create(request.FullName);

        // Check if user already exists in this tenant
        var existingUser = await _userRepository.GetByEmailAsync(invitation.TenantId, email, cancellationToken);

        User user;
        if (existingUser != null)
        {
            // User already exists in this tenant
            user = existingUser;
            _logger.LogInformation(
                "User {UserId} already exists in tenant {TenantId}, adding role",
                user.Id,
                invitation.TenantId);
        }
        else
        {
            // Create new user
            var passwordHash = _passwordHasher.HashPassword(request.Password);
            user = User.CreateLocal(
                invitation.TenantId,
                email,
                passwordHash,
                fullName);

            await _userRepository.AddAsync(user, cancellationToken);

            _logger.LogInformation(
                "Created new user {UserId} for invitation acceptance in tenant {TenantId}",
                user.Id,
                invitation.TenantId);
        }

        // Check if user already has a role in this tenant
        var userId = UserId.Create(user.Id);
        var existingRole = await _userTenantRoleRepository.GetByUserAndTenantAsync(
            userId,
            invitation.TenantId,
            cancellationToken);

        if (existingRole != null)
        {
            // User already has a role - update it
            existingRole.UpdateRole(invitation.Role, user.Id);
            await _userTenantRoleRepository.UpdateAsync(existingRole, cancellationToken);

            _logger.LogInformation(
                "Updated role for user {UserId} in tenant {TenantId} to {Role}",
                user.Id,
                invitation.TenantId,
                invitation.Role);
        }
        else
        {
            // Create new UserTenantRole mapping
            var userTenantRole = UserTenantRole.Create(
                userId,
                invitation.TenantId,
                invitation.Role,
                invitation.InvitedBy);

            await _userTenantRoleRepository.AddAsync(userTenantRole, cancellationToken);

            _logger.LogInformation(
                "Created role mapping for user {UserId} in tenant {TenantId} with role {Role}",
                user.Id,
                invitation.TenantId,
                invitation.Role);
        }

        // Mark invitation as accepted
        invitation.Accept();
        await _invitationRepository.UpdateAsync(invitation, cancellationToken);

        _logger.LogInformation(
            "Invitation {InvitationId} accepted by user {UserId}",
            invitation.Id,
            user.Id);

        return user.Id;
    }
}
