package de.tum.in.www1.artemis.config.migration.entries;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;

import de.tum.in.www1.artemis.config.migration.MigrationEntry;
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.service.user.LegacyPasswordService;
import de.tum.in.www1.artemis.service.user.PasswordService;

@Component
public class MigrationEntry20220302_164200 extends MigrationEntry {

    private static final Logger LOGGER = LoggerFactory.getLogger(MigrationEntry20220302_164200.class);

    private final UserRepository userRepository;

    private final LegacyPasswordService legacyPasswordService;

    private final PasswordService passwordService;

    public MigrationEntry20220302_164200(UserRepository userRepository, LegacyPasswordService legacyPasswordService, PasswordService passwordService) {
        this.userRepository = userRepository;
        this.legacyPasswordService = legacyPasswordService;
        this.passwordService = passwordService;
    }

    @Override
    public void execute() {
        List<User> users = userRepository.findAll();
        LOGGER.info("Found {} users to process.", users.size());
        Lists.partition(users, 100).forEach(list -> {
            list.forEach(this::processUser);
            userRepository.saveAll(list);
        });
    }

    private void processUser(User user) {
        if (user.getPassword() == null) {
            // If the password is null at this point, it has to be a proper external user we don't need to handle anymore.
            // Just to be sure, we set the correct internal state
            user.setInternal(false);
            return;
        }
        // The admin gets created beforehand if the account doesn't exist yet. This would be captured here.
        if (user.getPassword().matches("^\\$2[abxy]\\$\\d{2}\\$.*$")) {
            // If the password is a Bcrypt password at this point, it has to be a proper internal user we don't need to handle anymore.
            // Just to be sure, we set the correct internal state
            user.setInternal(true);
            return;
        }

        // In a previous migration we encrypted all relevant password so we don't need a fallback here
        String password = legacyPasswordService.decryptPassword(user);

        if (!user.isInternal()) {
            // Set internal state again due to an issue with setting the correct status during registration (PR #4806)
            // As the passwords of all external users were set to the encrypted empty string in Migration Entry 20211214_184200
            user.setInternal(!"".equals(password));
        }

        if (user.isInternal()) {
            String hash = passwordService.hashPassword(password);
            user.setPassword(hash);
        }
        else {
            user.setPassword(null);
        }
    }

    /**
     * @return Author of the entry. Either full name or GitHub name.
     */
    @Override
    public String author() {
        return "julian-christl";
    }

    /**
     * Format YYYYMMDD_HHmmss
     *
     * @return Current time in given format
     */
    @Override
    public String date() {
        return "20220302_164200";
    }
}

@@ -513,16 +513,271 @@
 rvice;%0A%0A
+/**%0A * This migration does the following for all internal users:%0A * 1) Decrypt the password in the database using the legacy password service (which will not be used any more in the future)%0A * 2) Hashes the password with the new BCryptPasswordEncoder%0A */%0A
 @Compone
@@ -1566,18 +1566,147 @@
 ers 
-to
+in total.%22, users.size());%0A        LOGGER.info(%22Found %7B%7D internal users in total for the password migration (which are likely to be
  process
 .%22, 
@@ -1701,16 +1701,19 @@
  process
+ed)
 .%22, user
@@ -1707,35 +1707,70 @@
 ssed).%22, users.s
-ize
+tream().filter(User::isInternal).count
 ());%0A        Lis
@@ -1802,17 +1802,21 @@
 forEach(
-l
+userL
 ist -%3E %7B
@@ -1828,17 +1828,108 @@
         
-l
+LOGGER.info(%22Process (next) 100 users for the migration in one batch...%22);%0A            userL
 ist.forE
@@ -1987,17 +1987,21 @@
 saveAll(
-l
+userL
 ist);%0A  
@@ -2798,32 +2798,125 @@
 urn;%0A        %7D%0A%0A
+        LOGGER.info(%22Process internal user %22 + user.getLogin() + %22 for password migration%22);%0A
         // In a 

Improve logs and documentation of password migration