package de.effigenix.application.usermanagement;

import de.effigenix.application.usermanagement.command.LockUserCommand;
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.persistence.UnitOfWork;
import de.effigenix.shared.security.AuthorizationPort;

/**
 * Use Case: Lock a user account (prevent login).
 */
public class LockUser {

    private final UserRepository userRepository;
    private final AuditLogger auditLogger;
    private final AuthorizationPort authPort;
    private final UnitOfWork unitOfWork;

    public LockUser(UserRepository userRepository, AuditLogger auditLogger, AuthorizationPort authPort, UnitOfWork unitOfWork) {
        this.userRepository = userRepository;
        this.auditLogger = auditLogger;
        this.authPort = authPort;
        this.unitOfWork = unitOfWork;
    }

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

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

        UserId userId = UserId.of(cmd.userId());
        return findUser(userId)
                .flatMap(User::lock)
                .flatMap(updated -> unitOfWork.executeAtomically(() ->
                        userRepository.save(updated)
                                .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
                                .map(ignored -> {
                                    auditLogger.log(AuditEvent.USER_LOCKED, updated.id().value(), 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))));
    }
}
