package de.effigenix.application.usermanagement;

import de.effigenix.application.usermanagement.command.RemoveRoleCommand;
import de.effigenix.application.usermanagement.dto.UserDTO;
import de.effigenix.domain.usermanagement.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;
import org.springframework.transaction.annotation.Transactional;

/**
 * Use Case: Remove a role from a user.
 */
@Transactional
public class RemoveRole {

    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final AuditLogger auditLogger;
    private final AuthorizationPort authPort;

    public RemoveRole(
            UserRepository userRepository,
            RoleRepository roleRepository,
            AuditLogger auditLogger,
            AuthorizationPort authPort
    ) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
        this.auditLogger = auditLogger;
        this.authPort = authPort;
    }

    public Result<UserError, UserDTO> execute(RemoveRoleCommand cmd, ActorId performedBy) {
        // 0. Authorization
        if (!authPort.can(performedBy, UserManagementAction.ROLE_REMOVE)) {
            return Result.failure(new UserError.Unauthorized("Not authorized to remove roles"));
        }

        // 1. Input validation
        if (cmd.userId() == null || cmd.userId().isBlank()) {
            return Result.failure(new UserError.InvalidInput("User ID must not be blank"));
        }
        if (cmd.roleName() == null) {
            return Result.failure(new UserError.InvalidInput("Role name must not be null"));
        }

        UserId userId = UserId.of(cmd.userId());
        return findUser(userId).flatMap(user -> findRoleAndRemove(user, cmd, performedBy));
    }

    private Result<UserError, UserDTO> findRoleAndRemove(User user, RemoveRoleCommand cmd, ActorId performedBy) {
        return roleRepository.findByName(cmd.roleName())
                .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
                .flatMap(optRole -> {
                    if (optRole.isEmpty()) {
                        return Result.failure(new UserError.RoleNotFound(cmd.roleName()));
                    }
                    Role role = optRole.get();
                    return user.removeRole(role)
                            .flatMap(updated -> userRepository.save(updated)
                                    .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
                                    .map(ignored -> {
                                        auditLogger.log(AuditEvent.ROLE_REMOVED, updated.id().value(), "Role: " + role.name(), performedBy);
                                        return UserDTO.from(updated);
                                    }));
                });
    }

    private Result<UserError, User> findUser(UserId userId) {
        return userRepository.findById(userId)
                .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
                .flatMap(opt -> opt
                        .map(Result::<UserError, User>success)
                        .orElse(Result.failure(new UserError.UserNotFound(userId))));
    }
}
