package de.effigenix.application.usermanagement;

import de.effigenix.application.usermanagement.command.UpdateUserCommand;
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: Update user details (email, branch).
 */
public class UpdateUser {

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

    public UpdateUser(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(UpdateUserCommand cmd, ActorId performedBy) {
        // 0. Authorization
        if (!authPort.can(performedBy, UserManagementAction.USER_UPDATE)) {
            return Result.failure(new UserError.Unauthorized("Not authorized to update users"));
        }

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

    private Result<UserError, UserDTO> updateUser(User user, UpdateUserCommand cmd, ActorId performedBy) {
        Result<UserError, User> current = Result.success(user);

        // Update email if provided
        if (cmd.email() != null && !cmd.email().equals(user.email())) {
            // Check email uniqueness
            var emailExists = userRepository.existsByEmail(cmd.email())
                    .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()));
            if (emailExists.isFailure()) return Result.failure(emailExists.unsafeGetError());
            if (emailExists.unsafeGetValue()) {
                return Result.failure(new UserError.EmailAlreadyExists(cmd.email()));
            }
            current = current.flatMap(u -> u.updateEmail(cmd.email()));
        }

        // Update branch if provided
        if (cmd.branchId() != null && !cmd.branchId().equals(user.branchId())) {
            current = current.flatMap(u -> u.updateBranch(cmd.branchId()));
        }

        return current.flatMap(updated -> unitOfWork.executeAtomically(() ->
                userRepository.save(updated)
                        .mapError(err -> (UserError) new UserError.RepositoryFailure(err.message()))
                        .map(ignored -> {
                            auditLogger.log(AuditEvent.USER_UPDATED, 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))));
    }
}
