/*
 * Queue - A Queueing system that can be used to handle labs in higher education
 * Copyright (C) 2016-2021  Delft University of Technology
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package test;

import static java.time.LocalDateTime.now;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.transaction.Transactional;

import lombok.AllArgsConstructor;
import lombok.Data;
import nl.tudelft.labracore.api.*;
import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.queue.dto.create.embeddables.SlottedLabConfigCreateDTO;
import nl.tudelft.queue.dto.create.labs.SlottedLabCreateDTO;
import nl.tudelft.queue.model.*;
import nl.tudelft.queue.model.constraints.ClusterConstraint;
import nl.tudelft.queue.model.constraints.ModuleDivisionConstraint;
import nl.tudelft.queue.model.embeddables.*;
import nl.tudelft.queue.model.enums.CommunicationMethod;
import nl.tudelft.queue.model.enums.OnlineMode;
import nl.tudelft.queue.model.enums.RequestType;
import nl.tudelft.queue.model.enums.SelectionProcedure;
import nl.tudelft.queue.model.events.*;
import nl.tudelft.queue.model.labs.*;
import nl.tudelft.queue.model.misc.Announcement;
import nl.tudelft.queue.repository.*;
import nl.tudelft.queue.service.TimeSlotService;

import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Service;

import test.labracore.*;

@Data
@Service
@Transactional
@ComponentScan(basePackageClasses = LabracoreApiMocker.class)
public class TestDatabaseLoader {

	private static final ModelMapper mapper = new ModelMapper();

	private static final int numberOfTeachers = 20;
	private static final int numberOfStudents = 375;

	private static long sessionId = 0L;
	private static long moduleId = 0L;

	private PersonSummaryDTO admin;

	private PersonSummaryDTO[] teachers;
	private PersonSummaryDTO[] students;

	private ProgramDetailsDTO program;

	private BuildingDetailsDTO buildingEwi;
	private BuildingDetailsDTO buildingDrebbel;

	private RoomDetailsDTO roomAmpere;
	private RoomDetailsDTO roomBoole;
	private RoomDetailsDTO roomPi;

	private RoomDetailsDTO roomPc1;
	private RoomDetailsDTO roomPc2;
	private RoomDetailsDTO roomPc3;
	private RoomDetailsDTO roomCz1;
	private RoomDetailsDTO roomCz2;

	private CohortDetailsDTO cohort19;
	private CohortDetailsDTO cohort20;

	private ClusterDetailsDTO cluster19A;
	private ClusterDetailsDTO cluster19B;
	private ClusterDetailsDTO cluster20A;
	private ClusterDetailsDTO cluster20B;
	private ClusterDetailsDTO cluster20C;

	private CourseDetailsDTO courseOop;
	private CourseDetailsDTO courseRl;

	private EditionDetailsDTO oop19;
	private EditionDetailsDTO oop20;
	private EditionDetailsDTO oopNow;

	private EditionDetailsDTO rl19;
	private EditionDetailsDTO rl20;
	private EditionDetailsDTO rlNow;

	private EditionCollectionDetailsDTO rlOopNowEc;

	private ModuleDetailsDTO oop19Module;
	private ModuleDetailsDTO oop20Module;
	private ModuleDetailsDTO oopNowLecturesModule;
	private ModuleDetailsDTO oopNowLabsModule;

	private ModuleDivisionDetailsDTO[] oopNowModuleDivisions;

	private AssignmentDetailsDTO[] oop19Assignments;
	private AssignmentDetailsDTO[] oop20Assignments;
	private AssignmentDetailsDTO[] oopNowAssignments;

	private ModuleDetailsDTO rl19Module;
	private ModuleDetailsDTO rl20Module;

	private AssignmentDetailsDTO[] rl19Assignments;
	private AssignmentDetailsDTO[] rl20Assignments;

	private SessionDetailsDTO[] oop19LcSessions;
	private SessionDetailsDTO[] oop20LcSessions;
	private SessionDetailsDTO[] oopNowLcLabs;
	private SessionDetailsDTO[] oopNowLcLabsDuplicate;
	private SessionDetailsDTO[] oopNowLcLectures;
	private SessionDetailsDTO oopNowLcSessionNow;
	private SessionDetailsDTO oopNowLcSessionTomorrow;
	private SessionDetailsDTO oopNowLcSessionYesterday;
	private SessionDetailsDTO rlOopSession;

	private RoleDetailsDTO[] oopNowStudents;
	private RoleDetailsDTO[] oopNowTAs;
	private RoleDetailsDTO[] oopNowHeadTAs;
	private RoleDetailsDTO oopNowTeacher;

	private RoleDetailsDTO[] student99WithMultipleRoles;
	private StudentGroupDetailsDTO[] oopNowLabGroups;
	private StudentGroupDetailsDTO[] oopNowLectureGroups;

	@Autowired
	private AssignmentControllerApi aApi;

	@Autowired
	private AuthorizationControllerApi authApi;

	@Autowired
	private ClusterControllerApi clApi;

	@Autowired
	private CohortControllerApi coApi;

	@Autowired
	private CourseControllerApi cApi;

	@Autowired
	private EditionControllerApi eApi;

	@Autowired
	private EditionCollectionControllerApi ecApi;

	@Autowired
	private ModuleControllerApi mApi;

	@Autowired
	private ModuleDivisionControllerApi mdApi;

	@Autowired
	private PersonControllerApi pApi;

	@Autowired
	private ProgramControllerApi prApi;

	@Autowired
	private RoleControllerApi rlApi;

	@Autowired
	private RoomControllerApi rApi;

	@Autowired
	private SessionControllerApi sApi;

	@Autowired
	private StudentGroupControllerApi sgApi;

	@Autowired
	private AssignmentApiMocker aApiMocker;

	@Autowired
	private ClusterApiMocker clApiMocker;

	@Autowired
	private CohortApiMocker coApiMocker;

	@Autowired
	private CourseApiMocker cApiMocker;

	@Autowired
	private EditionApiMocker eApiMocker;

	@Autowired
	private EditionCollectionApiMocker ecApiMocker;

	@Autowired
	private ModuleApiMocker mApiMocker;

	@Autowired
	private ModuleDivisionApiMocker mdApiMocker;

	@Autowired
	private PersonApiMocker pApiMocker;

	@Autowired
	private ProgramApiMocker prApiMocker;

	@Autowired
	private RoleApiMocker rlApiMocker;

	@Autowired
	private RoomApiMocker rApiMocker;

	@Autowired
	private SessionApiMocker sApiMocker;

	@Autowired
	private StudentGroupApiMocker sgApiMocker;

	@Autowired
	private AnnouncementRepository ar;

	@Autowired
	private FeedbackRepository fr;

	@Autowired
	private LabRepository lr;

	@Autowired
	private QueueSessionRepository qsr;

	@Autowired
	private LabRequestConstraintRepository lrcr;

	@Autowired
	private RequestEventRepository rer;

	@Autowired
	private RequestRepository rr;

	@Autowired
	private TimeSlotRepository tsr;

	@Autowired
	private TimeSlotService tss;

	private RegularLab oopNowRegularLab1;
	private LabRequest[] oopNowRegularLab1Requests;

	private RegularLab oopNowRegularLab2;
	private LabRequest[] oopNowRegularLab2Requests;

	private LabRequest[] rlOopNowSharedLabRequests;

	private RegularLab oopNowRegularLab3;
	private RegularLab oopNowRegularLab4;

	private RegularLab oopNowRegularHybridLab1;

	private RegularLab oop20RegLab;
	private RegularLab oop20RegLabDeleted;

	private RegularLab rlOopNowSharedLab;

	private SlottedLab oopNowSlottedLab1;
	private SlottedLab oopNowSlottedLab2;

	private SlottedLab oop20SlottedLab;
	private SlottedLab oop20SlottedLabDeleted;

	private ExamLab oopExamPast;
	private ExamLab oopExamNow;
	private ExamLab oopExamUpcoming;

	private CapacitySession oopPastLecture;
	private SelectionRequest[] oopPastLectureRequests;

	private CapacitySession oopFutureLecture;
	private CapacitySession oopLectureRandom;
	private CapacitySession oopLectureWeightLR;
	private CapacitySession oopLectureWeightCount;
	private CapacitySession oopSessionFcfs;

	public void mockAll() {
		aApiMocker.mock();

		clApiMocker.mock();
		coApiMocker.mock();
		cApiMocker.mock();

		eApiMocker.mock();
		ecApiMocker.mock();

		mApiMocker.mock();
		mdApiMocker.mock();

		pApiMocker.mock();
		prApiMocker.mock();

		rlApiMocker.mock();
		rApiMocker.mock();

		sApiMocker.mock();
		sgApiMocker.mock();
	}

	@Transactional
	@PostConstruct
	public void afterPropertiesSet() {
		initializeLabracore();

		initializeAnnouncements();

		initializeRegularLabs();
		initializeSlottedLabs();
		initializeExamLabs();
		initializeCapacitySessions();

		initializeLabRequestConstraints();

		initializeRequests();
		initializeFeedbacks();
	}

	public void initializeAnnouncements() {
		ar.save(Announcement.builder()
				.id(1L)
				.message("Maintenance weekend coming this weekend!")
				.isDismissible(false)
				.endTime(now().minusMonths(3L))
				.startTime(now().minusMonths(3L).minusDays(5L))
				.build());
		ar.save(Announcement.builder()
				.id(2L)
				.message("This is a test!")
				.isDismissible(true)
				.endTime(null)
				.startTime(now().minusDays(5L))
				.build());
		ar.save(Announcement.builder()
				.id(3L)
				.message("This might become annoying!")
				.isDismissible(false)
				.endTime(now().plusMinutes(10L))
				.startTime(now().minusDays(5L))
				.build());
	}

	public void initializeRegularLabs() {
		oopNowRegularLab1 = lr.save(RegularLab.builder()
				.session(createOopNowSessionForLab(
						"Regular Lab 1",
						List.of(oopNowAssignments[0]),
						List.of(roomPc1, roomCz1)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.allowedRequests(Set.of(
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.QUESTION)))
				.modules(Set.of(oopNowLabsModule.getId()))
				.requests(new ArrayList<>())
				.build());
		oopNowRegularLab2 = lr.save(RegularLab.builder()
				.session(createOopNowSessionForLab(
						"Regular Lab 2",
						List.of(oopNowAssignments[0], oopNowAssignments[1]),
						List.of(roomPc2, roomPc1)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.allowedRequests(Set.of(
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.SUBMISSION),
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.QUESTION),
						AllowedRequest.of(oopNowAssignments[1].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oopNowLabsModule.getId()))
				.requests(new ArrayList<>())
				.build());
		oopNowRegularLab3 = lr.save(RegularLab.builder()
				.session(createOopNowSessionForLab(
						"Regular Lab 3",
						List.of(oopNowAssignments[0], oopNowAssignments[1]),
						List.of(roomPc2, roomPc1)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.allowedRequests(Set.of(
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.SUBMISSION),
						AllowedRequest.of(oopNowAssignments[1].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oopNowLabsModule.getId()))
				.requests(new ArrayList<>())
				.build());
		oopNowRegularLab4 = lr.save(RegularLab.builder()
				.enableExperimental(true)
				.session(createOopNowSessionForLab(
						"Regular Lab 4",
						List.of(oopNowAssignments[0]),
						List.of(roomPc1, roomCz1)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.allowedRequests(Set.of(
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.QUESTION)))
				.modules(Set.of(oopNowLabsModule.getId()))
				.requests(new ArrayList<>())
				.build());

		oop20RegLab = lr.save(RegularLab.builder()
				.session(createOop20SessionForLab(
						"Slotted Lab 2",
						List.of(oopNowAssignments[0], oopNowAssignments[1]),
						List.of(roomPc2, roomPc1)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.allowedRequests(Set.of(
						AllowedRequest.of(oop20Assignments[0].getId(), RequestType.SUBMISSION),
						AllowedRequest.of(oop20Assignments[1].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oop20Module.getId()))
				.requests(new ArrayList<>())
				.build());
		oop20RegLabDeleted = lr.save(RegularLab.builder()
				.session(createOop20SessionForLab(
						"Slotted Lab 2",
						List.of(oopNowAssignments[0], oopNowAssignments[1]),
						List.of(roomPc2, roomPc1)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(LocalDateTime.of(2020, 12, 3, 0, 0, 0))
				.allowedRequests(Set.of(
						AllowedRequest.of(oop20Assignments[0].getId(), RequestType.SUBMISSION),
						AllowedRequest.of(oop20Assignments[1].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oop20Module.getId()))
				.requests(new ArrayList<>())
				.build());

		rlOopNowSharedLab = lr.save(RegularLab.builder()
				.session(rlOopSession.getId())
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(null)
				.allowedRequests(Set.of(
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.SUBMISSION),
						AllowedRequest.of(oopNowAssignments[1].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oopNowLabsModule.getId()))
				.requests(new ArrayList<>())
				.build());

		oopNowRegularHybridLab1 = lr.save(RegularLab.builder()
				.session(createOopNowSessionForLab(
						"Regular Hybrid Lab 1",
						List.of(oopNowAssignments[0]),
						List.of(roomPc1, roomCz1)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.allowedRequests(Set.of(
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.QUESTION)))
				.modules(Set.of(oopNowLabsModule.getId()))
				.requests(new ArrayList<>())
				.onlineModes(Set.of(OnlineMode.JITSI))
				.build());

	}

	public <TS extends TimeSlot, T extends AbstractSlottedLab<TS>> T createSlotsForLab(T lab) {
		tss.createSlotsForLab(SlottedLabCreateDTO.builder()
				.slottedLabConfig(new SlottedLabConfigCreateDTO(lab.getSlottedLabConfig()))
				.slot(Slot.builder()
						.opensAt(sApiMocker.getById(lab.getSession()).get().getStart())
						.closesAt(sApiMocker.getById(lab.getSession()).get().getEndTime())
						.build())
				.build(), lab);

		return lab;
	}

	public void initializeSlottedLabs() {
		oopNowSlottedLab1 = createSlotsForLab(lr.save(SlottedLab.builder()
				.slottedLabConfig(SlottedLabConfig.builder()
						.capacity(5)
						.duration(15L)
						.selectionOpensAt(now().minusDays(1))
						.build())
				.session(createOopNowSessionForLab(
						"Slotted Lab 1",
						List.of(oopNowAssignments[0]),
						List.of(roomCz1, roomCz2)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(null)
				.enqueueClosed(false)
				.allowedRequests(Set.of(
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.QUESTION)))
				.modules(Set.of(oopNowLecturesModule.getId()))
				.requests(new ArrayList<>())
				.timeSlots(new ArrayList<>())
				.build()));
		oopNowSlottedLab2 = createSlotsForLab(lr.save(SlottedLab.builder()
				.slottedLabConfig(SlottedLabConfig.builder()
						.capacity(10)
						.duration(15L)
						.selectionOpensAt(now().minusDays(1))
						.build())
				.session(createOopNowSessionForLab(
						"Slotted Lab 2",
						List.of(oopNowAssignments[0], oopNowAssignments[1]),
						List.of(roomPc2, roomPc1)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(null)
				.enqueueClosed(false)
				.allowedRequests(Set.of(
						AllowedRequest.of(oopNowAssignments[0].getId(), RequestType.SUBMISSION),
						AllowedRequest.of(oopNowAssignments[1].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oopNowLecturesModule.getId()))
				.requests(new ArrayList<>())
				.build()));

		oop20SlottedLab = createSlotsForLab(lr.save(SlottedLab.builder()
				.slottedLabConfig(SlottedLabConfig.builder()
						.capacity(5)
						.duration(20L)
						.selectionOpensAt(now().minusDays(1))
						.build())
				.session(createOop20SessionForLab(
						"Slotted Lab 1",
						List.of(oopNowAssignments[0], oopNowAssignments[1]),
						List.of(roomCz1, roomCz2)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(null)
				.enqueueClosed(false)
				.allowedRequests(Set.of(
						AllowedRequest.of(oop20Assignments[0].getId(), RequestType.QUESTION),
						AllowedRequest.of(oop20Assignments[1].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oop20Module.getId()))
				.requests(new ArrayList<>())
				.build()));
		oop20SlottedLabDeleted = createSlotsForLab(lr.save(SlottedLab.builder()
				.slottedLabConfig(SlottedLabConfig.builder()
						.capacity(5)
						.duration(30L)
						.selectionOpensAt(now().minusDays(1))
						.build())
				.session(createOop20SessionForLab(
						"Slotted Lab 2",
						List.of(oopNowAssignments[0]),
						List.of(roomCz1, roomCz2)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(LocalDateTime.of(2020, 12, 3, 0, 0, 0))
				.enqueueClosed(false)
				.allowedRequests(Set.of(
						AllowedRequest.of(oop20Assignments[0].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oop20Module.getId()))
				.requests(new ArrayList<>())
				.build()));
	}

	public void initializeExamLabs() {
		oopExamPast = createSlotsForLab(lr.save(ExamLab.builder()
				.examLabConfig(ExamLabConfig.builder()
						.percentage(15)
						.build())
				.slottedLabConfig(SlottedLabConfig.builder()
						.capacity(5)
						.duration(30L)
						.selectionOpensAt(now().withYear(2020).minusDays(1))
						.build())
				.session(createOop20SessionForLab(
						"Slotted Lab 2",
						List.of(oopNowAssignments[0]),
						List.of(roomCz1, roomCz2)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(null)
				.enqueueClosed(false)
				.allowedRequests(Set.of(
						AllowedRequest.of(oop20Assignments[0].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oop20Module.getId()))
				.requests(new ArrayList<>())
				.build()));

		oopExamNow = createSlotsForLab(lr.save(ExamLab.builder()
				.examLabConfig(ExamLabConfig.builder()
						.percentage(15)
						.build())
				.slottedLabConfig(SlottedLabConfig.builder()
						.capacity(5)
						.duration(30L)
						.selectionOpensAt(now().minusDays(1))
						.build())
				.session(createOopNowSessionForLab(
						"Exam Session 1",
						List.of(oopNowAssignments[0]),
						List.of(roomCz1, roomCz2)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(null)
				.enqueueClosed(false)
				.allowedRequests(Set.of(
						AllowedRequest.of(oop20Assignments[0].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oopNowLecturesModule.getId()))
				.requests(new ArrayList<>())
				.build()));

		oopExamUpcoming = createSlotsForLab(lr.save(ExamLab.builder()
				.examLabConfig(ExamLabConfig.builder()
						.percentage(15)
						.build())
				.slottedLabConfig(SlottedLabConfig.builder()
						.capacity(5)
						.duration(30L)
						.selectionOpensAt(now().minusDays(1))
						.build())
				.session(createOopNowSessionForLabTomorrow(
						"Exam Session 2",
						List.of(oopNowAssignments[0]),
						List.of(roomCz1, roomCz2)))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.constraints(LabRequestConstraints.builder()
						.build())
				.deletedAt(null)
				.enqueueClosed(false)
				.allowedRequests(Set.of(
						AllowedRequest.of(oop20Assignments[0].getId(), RequestType.SUBMISSION)))
				.modules(Set.of(oopNowLecturesModule.getId()))
				.requests(new ArrayList<>())
				.build()));
	}

	public void initializeCapacitySessions() {
		var builder = CapacitySession.builder()
				.session(oopNowLcLabs[1].getId())
				.enqueueClosed(false)
				.modules(Set.of(oopNowLecturesModule.getId()));
		var configBuilder = CapacitySessionConfig.builder()
				.procedure(SelectionProcedure.RANDOM)
				.enrolmentOpensAt(now().minusDays(3))
				.enrolmentClosesAt(now().plusDays(3))
				.selectionAt(now().plusDays(3).plusMinutes(15));

		oopLectureRandom = qsr.save(builder
				.capacitySessionConfig(configBuilder.procedure(SelectionProcedure.RANDOM).build())
				.build());

		oopLectureWeightLR = qsr.save(builder
				.capacitySessionConfig(
						configBuilder.procedure(SelectionProcedure.WEIGHTED_BY_LEAST_RECENT).build())
				.build());

		oopLectureWeightCount = qsr.save(builder
				.capacitySessionConfig(configBuilder.procedure(SelectionProcedure.WEIGHTED_BY_COUNT).build())
				.build());

		oopSessionFcfs = qsr.save(builder
				.capacitySessionConfig(configBuilder.procedure(SelectionProcedure.FCFS).build())
				.build());

		oopPastLecture = qsr.save(builder
				.session(oopNowLcLabs[0].getId())
				.capacitySessionConfig(configBuilder
						.procedure(SelectionProcedure.RANDOM)
						.enrolmentClosesAt(now().minusDays(1))
						.build())
				.build());

		oopFutureLecture = qsr.save(builder
				.session(oopNowLcLabs[2].getId())
				.capacitySessionConfig(configBuilder
						.enrolmentOpensAt(now().plusDays(3))
						.enrolmentClosesAt(now().plusWeeks(1))
						.selectionAt(now().plusWeeks(1).plusMinutes(15))
						.build())
				.build());
	}

	public void initializeLabRequestConstraints() {
		ClusterConstraint cc2 = lrcr.save(ClusterConstraint.builder()
				.session(oopNowRegularLab2)
				.clusters(Set.of(cluster20B.getId(), cluster20C.getId()))
				.build());

		oopNowRegularLab2.getConstraints().setClusterConstraint(cc2);

		ModuleDivisionConstraint mdc2 = lrcr.save(ModuleDivisionConstraint.builder()
				.session(oopNowSlottedLab2)
				.divisions(Set.of(oopNowModuleDivisions[0].getId()))
				.build());

		oopNowSlottedLab2.getConstraints().setModuleDivisionConstraint(mdc2);
	}

	public LabRequest createLabRequest(Lab lab, PersonSummaryDTO requester, AssignmentDetailsDTO assignment,
			RequestType type, TimeSlot slot) {
		var request = rr.save(LabRequest.builder()
				.timeSlot(slot)
				.assignment(assignment.getId())
				.session(lab)
				.createdAt(now())
				.comment(null)
				.question((type == RequestType.QUESTION) ? "I have a question?" : null)
				.requestType(type)
				.requester(requester.getId())
				.studentGroup(sgApiMocker.getByPersonAndModule(
						requester.getId(), assignment.getModule().getId()).get().getId())
				.room(sApiMocker.getById(lab.getSession()).get().getRooms().iterator().next().getId())
				.build());
		lab.getRequests().add(request);
		return request;
	}

	public SelectionRequest createSelectionRequest(CapacitySession session, PersonSummaryDTO requester) {
		var request = rr.save(SelectionRequest.builder()
				.createdAt(now())
				.requester(requester.getId())
				.session(session)
				.comment("Cool")
				.eventInfo(new RequestEventInfo())
				.room(sApiMocker.getById(session.getSession()).get().getRooms().iterator().next().getId())
				.studentGroup(sgApiMocker.getByPersonAndModule(
						requester.getId(), session.getModules().stream().findFirst().get()).get().getId())
				.build());
		session.getRequests().add(request);
		return request;
	}

	@Transactional
	public void initializeRequests() {
		oopNowRegularLab1Requests = new LabRequest[50];
		oopNowRegularLab2Requests = new LabRequest[50];

		rlOopNowSharedLabRequests = new LabRequest[50];

		oopPastLectureRequests = new SelectionRequest[50];

		for (int i = 50; i < 100; i++) {
			var reqRegLab1 = createLabRequest(oopNowRegularLab1, students[i], oopNowAssignments[0],
					RequestType.QUESTION,
					null);
			var reqRegLab2 = createLabRequest(oopNowRegularLab2, students[i], oopNowAssignments[0],
					RequestType.SUBMISSION,
					null);

			var reqSharedLab1 = createLabRequest(rlOopNowSharedLab, students[i],
					oopNowAssignments[i % oopNowAssignments.length], RequestType.SUBMISSION, null);

			var reqPastLecture = createSelectionRequest(oopPastLecture, students[i]);
			createSelectionRequest(oopLectureRandom, students[i]);
			createSelectionRequest(oopLectureWeightLR, students[i]);
			createSelectionRequest(oopLectureWeightCount, students[i]);

			if (55 <= i && i < 60) {
				requestPathRevoked(reqPastLecture);
				requestPathRejected(reqRegLab1);
			} else if (60 <= i && i < 70) {
				requestPath2(reqRegLab1);
			} else if (70 <= i && i < 74) {
				requestPath3(reqRegLab1);
			} else if (74 <= i && i < 80) {
				requestPath4(reqRegLab1);
			} else if (i == 80) {
				requestPathTaken(reqRegLab1);
			}

			if (i >= 60) {
				requestPathSelected(reqPastLecture);
			}

			oopNowRegularLab1Requests[i - 50] = reqRegLab1;
			oopNowRegularLab2Requests[i - 50] = reqRegLab2;
			oopPastLectureRequests[i - 50] = reqPastLecture;
			rlOopNowSharedLabRequests[i - 50] = reqSharedLab1;
		}
	}

	public void requestPathRejected(LabRequest request) {
		new LabRequestEventsBuilder(request)
				.take(oopNowTAs[0].getPerson().getId())
				.reject(oopNowTAs[0].getPerson().getId())
				.finish();
	}

	public void requestPath2(LabRequest request) {
		new LabRequestEventsBuilder(request)
				.take(oopNowTAs[1].getPerson().getId())
				.forwardToAny(oopNowTAs[1].getPerson().getId())
				.take(oopNowTeacher.getPerson().getId())
				.approve(oopNowTeacher.getPerson().getId())
				.finish();
	}

	public void requestPath3(LabRequest request) {
		new LabRequestEventsBuilder(request)
				.take(oopNowTAs[2].getPerson().getId())
				.notFound(oopNowTAs[2].getPerson().getId())
				.finish();
	}

	public void requestPath4(LabRequest request) {
		new LabRequestEventsBuilder(request)
				.take(oopNowTAs[4].getPerson().getId())
				.forwardToAny(oopNowTAs[4].getPerson().getId())
				.finish();
	}

	public void requestPathTaken(LabRequest request) {
		new LabRequestEventsBuilder(request)
				.take(oopNowTAs[5].getPerson().getId())
				.finish();
	}

	public void requestPathSelected(SelectionRequest request) {
		new SelectionRequestEventsBuilder(request)
				.selected()
				.finish();
	}

	public void requestPathNotSelected(SelectionRequest request) {
		new SelectionRequestEventsBuilder(request)
				.notSelected()
				.finish();
	}

	public void requestPathRevoked(SelectionRequest request) {
		new SelectionRequestEventsBuilder(request)
				.revoked()
				.finish();
	}

	public void initializeFeedbacks() {
		oopNowRegularLab1.getRequests().forEach(request -> {
			if (request.getEventInfo().getStatus().isHandled()) {
				fr.save(Feedback.builder()
						.id(new Feedback.Id(request.getId(), request.getEventInfo().getAssignedTo()))
						.request(request)
						.feedback("Cool")
						.rating((int) (request.getId() % 5) + 1)
						.build());
			}
		});
	}

	private void initializeLabracore() {
		initializePeople();
		initializeProgram();
		initializeBuildings();
		initializeRooms();

		initializeCohorts();
		initializeClusters();

		initializeCourses();
		initializeEditions();
		initializeEditionCollections();

		initializeModules();
		initializeModuleDivisions();
		initializeAssignments();

		initializeSessions();

		initializeRoles();
		initializeStudentGroups();

		program.courses(toView(List.of(courseOop, courseRl), CourseSummaryDTO.class));
		program.cohorts(toView(List.of(cohort19, cohort20), CohortSummaryDTO.class));

		cohort19.clusters(toView(List.of(cluster19A, cluster19B), ClusterSummaryDTO.class));
		cohort20.clusters(toView(List.of(cluster20A, cluster20B, cluster20C), ClusterSummaryDTO.class));

		courseOop.editions(toView(List.of(oop19, oop20, oopNow), EditionSummaryDTO.class));
		courseRl.editions(toView(List.of(rl19, rl20, rlNow), EditionSummaryDTO.class));
	}

	private void initializePeople() {
		admin = pApiMocker.save(new PersonSummaryDTO()
				.id(0L)
				.displayName("Admin")
				.username("admin")
				.email("admin@edumail.com")
				.accountDisabled(false)
				.defaultRole(PersonSummaryDTO.DefaultRoleEnum.ADMIN)
				.externalId("admin")
				.number(-1));

		teachers = initializeTeachers();
		students = initializeStudents();
	}

	private void initializeProgram() {
		program = prApiMocker.save(new ProgramDetailsDTO()
				.id(1L)
				.name("Computer science")
				.director(teachers[0])
				.cohorts(new ArrayList<>())
				.courses(new ArrayList<>()));
	}

	private void initializeBuildings() {
		buildingEwi = new BuildingDetailsDTO()
				.id(1L)
				.name("Electrical Engineering, Mathematics and Computer Science")
				.description(
						"The main faculty for electrical engineering, mathematics and computer computer science students.")
				.abbreviation("EEMCS")
				.address(new Address()
						.number(4)
						.city("Delft")
						.street("Mekelweg")
						.zipCode("2628CD"))
				.rooms(new ArrayList<>());
		buildingDrebbel = new BuildingDetailsDTO()
				.id(2L)
				.name("Drebbelweg")
				.description("Building for labs for Computer Science students.")
				.abbreviation("EEMCS")
				.address(new Address()
						.number(5)
						.city("Delft")
						.street("Cornelis Drebbelweg")
						.zipCode("2628CM"))
				.rooms(new ArrayList<>());
	}

	private void initializeRooms() {
		roomAmpere = rApiMocker.save(new RoomDetailsDTO()
				.id(1L)
				.name("Lecture Hall Ampere")
				.abbreviation("EEMCS-CZ-A")
				.building(toView(buildingEwi, BuildingSummaryDTO.class))
				.capacity(240));
		roomBoole = rApiMocker.save(new RoomDetailsDTO()
				.id(2L)
				.name("Lecture Hall Boole")
				.abbreviation("EEMCS-CZ-B")
				.building(toView(buildingEwi, BuildingSummaryDTO.class))
				.capacity(150));
		roomPi = rApiMocker.save(new RoomDetailsDTO()
				.id(3L)
				.name("Lecture Hall Pi")
				.abbreviation("EEMCS-CZ-PI")
				.building(toView(buildingEwi, BuildingSummaryDTO.class))
				.capacity(30));

		roomPc1 = rApiMocker.save(new RoomDetailsDTO()
				.id(4L)
				.name("PC-Hall 1")
				.abbreviation("DW-PC-1")
				.building(toView(buildingDrebbel, BuildingSummaryDTO.class))
				.capacity(100));
		roomPc2 = rApiMocker.save(new RoomDetailsDTO()
				.id(5L)
				.name("PC-Hall 2")
				.abbreviation("DW-PC-2")
				.building(toView(buildingDrebbel, BuildingSummaryDTO.class))
				.capacity(50));
		roomPc3 = rApiMocker.save(new RoomDetailsDTO()
				.id(6L)
				.name("PC-Hall 3")
				.abbreviation("DW-PC-3")
				.building(toView(buildingDrebbel, BuildingSummaryDTO.class))
				.capacity(20));
		roomCz1 = rApiMocker.save(new RoomDetailsDTO()
				.id(7L)
				.name("Lecture Hall 1")
				.abbreviation("DW-CZ-1")
				.building(toView(buildingDrebbel, BuildingSummaryDTO.class))
				.capacity(230));
		roomCz2 = rApiMocker.save(new RoomDetailsDTO()
				.id(8L)
				.name("Lecture Hall 2")
				.abbreviation("DW-CZ-2")
				.building(toView(buildingDrebbel, BuildingSummaryDTO.class))
				.capacity(230));
	}

	private void initializeCohorts() {
		cohort19 = coApiMocker.save(new CohortDetailsDTO()
				.id(1L)
				.name("CSE 19/20")
				.program(toView(program, ProgramSummaryDTO.class))
				.clusters(new ArrayList<>()));
		cohort20 = coApiMocker.save(new CohortDetailsDTO()
				.id(2L)
				.name("CSE 20/21")
				.program(toView(program, ProgramSummaryDTO.class))
				.clusters(new ArrayList<>()));
	}

	private void initializeCourses() {
		courseOop = cApiMocker.save(new CourseDetailsDTO()
				.id(1L)
				.code("CSE1200")
				.name("Object Oriented Programming")
				.isArchived(false)
				.program(toView(program, ProgramSummaryDTO.class))
				.managers(List.of(teachers[3]))
				.editions(new ArrayList<>()));
		courseRl = cApiMocker.save(new CourseDetailsDTO()
				.id(2L)
				.code("CSE1200")
				.name("Object Oriented Programming")
				.isArchived(false)
				.program(toView(program, ProgramSummaryDTO.class))
				.managers(List.of(teachers[3]))
				.editions(new ArrayList<>()));
	}

	private void initializeClusters() {
		cluster19A = clApiMocker.save(new ClusterDetailsDTO()
				.id(1L)
				.name("19/20 A")
				.people(Arrays.asList(students).subList(0, 75))
				.cohort(toView(cohort19, CohortSummaryDTO.class)));
		cluster19B = clApiMocker.save(new ClusterDetailsDTO()
				.id(2L)
				.name("19/20 A")
				.people(Arrays.asList(students).subList(75, 150))
				.cohort(toView(cohort19, CohortSummaryDTO.class)));
		cluster20A = clApiMocker.save(new ClusterDetailsDTO()
				.id(3L)
				.name("20/21 A")
				.people(Arrays.asList(students).subList(0, 50))
				.cohort(toView(cohort20, CohortSummaryDTO.class)));
		cluster20B = clApiMocker.save(new ClusterDetailsDTO()
				.id(4L)
				.name("20/21 B")
				.people(Arrays.asList(students).subList(50, 100))
				.cohort(toView(cohort20, CohortSummaryDTO.class)));
		cluster20C = clApiMocker.save(new ClusterDetailsDTO()
				.id(5L)
				.name("20/21 C")
				.people(Arrays.asList(students).subList(100, 150))
				.cohort(toView(cohort20, CohortSummaryDTO.class)));
	}

	private void initializeEditions() {
		oop19 = eApiMocker.save(new EditionDetailsDTO()
				.id(1L)
				.name("19/20")
				.cohort(toView(cohort19, CohortSummaryDTO.class))
				.enrollability(EditionDetailsDTO.EnrollabilityEnum.CLOSED)
				.isArchived(false)
				.course(toView(courseOop, CourseSummaryDTO.class))
				.startDate(LocalDateTime.of(2019, 9, 5, 0, 0, 0))
				.endDate(LocalDateTime.of(2019, 12, 5, 0, 0, 0))
				.editionCollections(new ArrayList<>())
				.modules(new ArrayList<>())
				.sessions(new ArrayList<>()));
		oop20 = eApiMocker.save(new EditionDetailsDTO()
				.id(3L)
				.name("20/21")
				.cohort(toView(cohort20, CohortSummaryDTO.class))
				.enrollability(EditionDetailsDTO.EnrollabilityEnum.CLOSED)
				.isArchived(false)
				.course(toView(courseOop, CourseSummaryDTO.class))
				.startDate(LocalDateTime.of(2020, 9, 5, 0, 0, 0))
				.endDate(LocalDateTime.of(2020, 12, 5, 0, 0, 0))
				.editionCollections(new ArrayList<>())
				.modules(new ArrayList<>())
				.sessions(new ArrayList<>()));
		oopNow = eApiMocker.save(new EditionDetailsDTO()
				.id(5L)
				.name("NOW")
				.cohort(toView(cohort20, CohortSummaryDTO.class))
				.enrollability(EditionDetailsDTO.EnrollabilityEnum.CLOSED)
				.isArchived(false)
				.course(toView(courseOop, CourseSummaryDTO.class))
				.startDate(now().minusMinutes(3L))
				.endDate(now().plusMonths(3L))
				.editionCollections(new ArrayList<>())
				.modules(new ArrayList<>())
				.sessions(new ArrayList<>()));

		rl19 = eApiMocker.save(new EditionDetailsDTO()
				.id(2L)
				.name("19/20")
				.cohort(toView(cohort19, CohortSummaryDTO.class))
				.enrollability(EditionDetailsDTO.EnrollabilityEnum.CLOSED)
				.isArchived(false)
				.course(toView(courseOop, CourseSummaryDTO.class))
				.startDate(LocalDateTime.of(2019, 9, 5, 0, 0, 0))
				.endDate(LocalDateTime.of(2019, 12, 5, 0, 0, 0))
				.editionCollections(new ArrayList<>())
				.modules(new ArrayList<>())
				.sessions(new ArrayList<>()));
		rl20 = eApiMocker.save(new EditionDetailsDTO()
				.id(4L)
				.name("20/21")
				.cohort(toView(cohort20, CohortSummaryDTO.class))
				.enrollability(EditionDetailsDTO.EnrollabilityEnum.CLOSED)
				.isArchived(false)
				.course(toView(courseRl, CourseSummaryDTO.class))
				.startDate(LocalDateTime.of(2020, 9, 5, 0, 0, 0))
				.endDate(LocalDateTime.of(2020, 12, 5, 0, 0, 0))
				.editionCollections(new ArrayList<>())
				.modules(new ArrayList<>())
				.sessions(new ArrayList<>()));
		rlNow = eApiMocker.save(new EditionDetailsDTO()
				.id(6L)
				.name("NOW")
				.cohort(toView(cohort20, CohortSummaryDTO.class))
				.enrollability(EditionDetailsDTO.EnrollabilityEnum.OPEN)
				.isArchived(true)
				.course(toView(courseRl, CourseSummaryDTO.class))
				.startDate(now().minusMinutes(3L))
				.endDate(now().plusMonths(3L))
				.editionCollections(new ArrayList<>())
				.modules(new ArrayList<>())
				.sessions(new ArrayList<>()));
	}

	private void initializeEditionCollections() {
		rlOopNowEc = ecApiMocker.save(new EditionCollectionDetailsDTO()
				.id(1L)
				.name("OOP/ADS")
				.editions(List.of(mapper.map(oopNow, EditionSummaryDTO.class),
						mapper.map(rlNow, EditionSummaryDTO.class))));
		oopNow.addEditionCollectionsItem(mapper.map(rlOopNowEc, EditionCollectionSummaryDTO.class));
		rlNow.addEditionCollectionsItem(mapper.map(rlOopNowEc, EditionCollectionSummaryDTO.class));
	}

	private void initializeModules() {
		oop19Module = initializeDefaultModule(oop19);
		oop20Module = initializeDefaultModule(oop20);

		oopNowLecturesModule = initializeModule(oopNow, "Lectures");
		oopNowLabsModule = initializeModule(oopNow, "Labs");

		rl19Module = initializeDefaultModule(rl19);
		rl20Module = initializeDefaultModule(rl20);
	}

	private void initializeModuleDivisions() {
		oopNowModuleDivisions = initializeModuleDivisions(
				oopNowLecturesModule, 2, Arrays.asList(students).subList(250, 350));
	}

	private void initializeAssignments() {
		oop19Assignments = initializeAssignments(oop19Module);
		oop20Assignments = initializeAssignments(oop20Module);
		oopNowAssignments = initializeAssignments(oopNowLecturesModule);

		rl19Assignments = initializeAssignments(rl19Module);
		rl20Assignments = initializeAssignments(rl20Module);
	}

	private void initializeSessions() {
		oop19LcSessions = initializeSessions(
				oop19, 0L,
				new ArrayList<>(List.of(roomCz1, roomPc1, roomPc2)),
				toView(Arrays.asList(oop19Assignments), AssignmentSummaryDTO.class));
		oop20LcSessions = initializeSessions(
				oop20, 0L,
				new ArrayList<>(List.of(roomPc1, roomPc2, roomPc3)),
				toView(Arrays.asList(oop20Assignments), AssignmentSummaryDTO.class));

		oopNowLcLabs = initializeSessions(
				oopNow, 1L,
				new ArrayList<>(List.of(roomPc1, roomPc2, roomPc3, roomCz1, roomCz2)),
				toView(Arrays.asList(oopNowAssignments), AssignmentSummaryDTO.class));
		oopNowLcLabsDuplicate = initializeSessions(
				oopNow, 1L,
				new ArrayList<>(List.of(roomPc1, roomPc2, roomPc3, roomCz1, roomCz2)),
				toView(Arrays.asList(oopNowAssignments), AssignmentSummaryDTO.class));
		oopNowLcLectures = initializeSessions(
				oopNow, 1L,
				new ArrayList<>(List.of(roomAmpere, roomBoole)),
				toView(Arrays.asList(oopNowAssignments), AssignmentSummaryDTO.class));

		oopNowLcSessionNow = initializeSession(
				oopNow, now().minusHours(1),
				new ArrayList<>(List.of(roomPc1)),
				toView(Arrays.asList(oopNowAssignments), AssignmentSummaryDTO.class));
		oopNowLcSessionYesterday = initializeSession(
				oopNow, now().minusHours(1).minusDays(1),
				new ArrayList<>(List.of(roomPc1)),
				toView(Arrays.asList(oopNowAssignments), AssignmentSummaryDTO.class));
		oopNowLcSessionTomorrow = initializeSession(
				oopNow, now().minusHours(1).plusDays(1),
				new ArrayList<>(List.of(roomPc1)),
				toView(Arrays.asList(oopNowAssignments), AssignmentSummaryDTO.class));

		rlOopSession = initializeSharedSession(rlOopNowEc,
				List.of(roomCz1, roomPc2),
				toView(Arrays.asList(oopNowAssignments), AssignmentSummaryDTO.class));
	}

	private void initializeRoles() {
		oopNowStudents = initializeRoles(oopNow, RoleDetailsDTO.TypeEnum.STUDENT, 0, 100);
		oopNowTAs = initializeRoles(oopNow, RoleDetailsDTO.TypeEnum.TA, 200, 220);
		oopNowHeadTAs = initializeRoles(oopNow, RoleDetailsDTO.TypeEnum.HEAD_TA, 100, 103);
		oopNowTeacher = initializeRole(oopNow, RoleDetailsDTO.TypeEnum.TEACHER, teachers[0]);
		Map<EditionDetailsDTO, RoleDetailsDTO.TypeEnum> rolesForStudent99 = new LinkedHashMap<>();
		rolesForStudent99.put(rl19, RoleDetailsDTO.TypeEnum.HEAD_TA);
		rolesForStudent99.put(rl20, RoleDetailsDTO.TypeEnum.TA);
		rolesForStudent99.put(oop20, RoleDetailsDTO.TypeEnum.STUDENT);
		rolesForStudent99.put(oop19, RoleDetailsDTO.TypeEnum.STUDENT);

		student99WithMultipleRoles = rolesForStudent99.entrySet().stream()
				.map(role -> initializeRole(role.getKey(), role.getValue(), students[99]))
				.toArray(RoleDetailsDTO[]::new);
	}

	private void initializeStudentGroups() {
		oopNowLabGroups = initializeIndividualGroups(oopNowLecturesModule, oopNowStudents);
		oopNowLectureGroups = initializeIndividualGroups(oopNowLecturesModule, oopNowStudents);
	}

	private SessionDetailsDTO finalize(SessionDetailsDTO session) {
		session.setId(++sessionId);
		sApiMocker.save(session);
		session.getEditions().forEach(edition -> eApiMocker.getById(edition.getId()).get()
				.addSessionsItem(toView(session, SessionSummaryDTO.class)));
		return session;
	}

	private ModuleDivisionDetailsDTO[] initializeModuleDivisions(ModuleDetailsDTO module,
			int numberOfDivisions, List<PersonSummaryDTO> people) {
		var divisions = new ModuleDivisionDetailsDTO[numberOfDivisions];
		var studentsPerDivision = people.size() / numberOfDivisions;
		for (int i = 0; i < numberOfDivisions; i++) {
			var personStart = studentsPerDivision * i;
			var personEnd = (i == numberOfDivisions - 1) ? people.size() : personStart + studentsPerDivision;

			divisions[i] = mdApiMocker.save(new ModuleDivisionDetailsDTO()
					.id(module.getId() * 1000L + i)
					.module(toView(module, ModuleSummaryDTO.class))
					.people(people.subList(personStart, personEnd)));
			module.addDivisionsItem(toView(divisions[i], ModuleDivisionSummaryDTO.class));
		}
		return divisions;
	}

	private ModuleDetailsDTO initializeModule(EditionDetailsDTO edition, String name) {
		var module = mApiMocker.save(new ModuleDetailsDTO()
				.id(++moduleId)
				.name(name)
				.assignments(new ArrayList<>())
				.edition(toView(oopNow, EditionSummaryDTO.class))
				.divisions(new ArrayList<>())
				.groups(new ArrayList<>()));
		edition.addModulesItem(toView(module, ModuleSummaryDTO.class));
		return module;
	}

	private ModuleDetailsDTO initializeDefaultModule(EditionDetailsDTO edition) {
		return initializeModule(edition, "Default");
	}

	private RoleDetailsDTO initializeRole(EditionDetailsDTO edition, RoleDetailsDTO.TypeEnum type,
			PersonSummaryDTO person) {
		return rlApiMocker.save(new RoleDetailsDTO()
				.id(new Id()
						.editionId(edition.getId())
						.personId(person.getId()))
				.edition(toView(edition, EditionSummaryDTO.class))
				.person(person)
				.type(type));
	}

	private RoleDetailsDTO[] initializeRoles(EditionDetailsDTO edition, RoleDetailsDTO.TypeEnum type,
			int from, int to) {
		var nRoles = to - from;
		var roles = new RoleDetailsDTO[nRoles];
		for (int i = from; i < to; i++) {
			roles[i - from] = initializeRole(edition, type, students[i]);
		}
		return roles;
	}

	private StudentGroupDetailsDTO[] initializeIndividualGroups(ModuleDetailsDTO module,
			RoleDetailsDTO[] roles) {
		var divisionPerPerson = module.getDivisions().stream()
				.flatMap(md -> mdApiMocker.getById(md.getId()).get().getPeople().stream()
						.map(person -> Map.entry(person.getId(), md)))
				.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

		var groups = new StudentGroupDetailsDTO[roles.length];
		for (int i = 0; i < roles.length; i++) {
			groups[i] = sgApiMocker.save(new StudentGroupDetailsDTO()
					.id(module.getId() * 1000L + roles[i].getPerson().getId())
					.capacity(1)
					.division(divisionPerPerson.getOrDefault(roles[i].getPerson().getId(), null))
					.name(roles[i].getPerson().getDisplayName())
					.module(toView(module, ModuleSummaryDTO.class))
					.members(toView(List.of(roles[i]), RolePersonLayer1DTO.class))
					.memberUsernames(Arrays.stream(roles).map(role -> role.getPerson().getUsername())
							.collect(Collectors.toList())));
			module.addGroupsItem(toView(groups[i], StudentGroupSmallSummaryDTO.class));
		}
		return groups;
	}

	private AssignmentDetailsDTO[] initializeAssignments(ModuleDetailsDTO module) {
		var nAssignments = 3;
		var assignments = new AssignmentDetailsDTO[nAssignments];
		for (int i = 0; i < nAssignments; i++) {
			long assignmentId = module.getId() * 1000L + i;
			assignments[i] = aApiMocker.save(new AssignmentDetailsDTO()
					.id(assignmentId)
					.name("Assignment " + i)
					.sessions(new ArrayList<>())
					.description(new Description().text("Nothing special, just an assignment"))
					.acceptLateSubmissions(false)
					.allowedFileTypes(new HashSet<>())
					.approveScript(null)
					.deadline(null)
					.module(toView(module, ModuleSummaryDTO.class))
					.submissions(new ArrayList<>())
					.submissionLimit(null));
			module.addAssignmentsItem(toView(assignments[i], AssignmentSummaryDTO.class));

			aApiMocker.initializeAssignmentWithModule(assignmentId, module);
		}
		return assignments;
	}

	private SessionDetailsDTO initializeSharedSession(EditionCollectionDetailsDTO ec,
			List<RoomDetailsDTO> rooms,
			List<AssignmentSummaryDTO> assignments) {
		return finalize(new SessionDetailsDTO()
				.name("Shared session")
				.start(now().minusHours(1))
				.endTime(now().plusHours(3))
				.description("A shared session")
				.editionCollection(toView(ec, EditionCollectionSummaryDTO.class))
				.editions(new HashSet<>(ec.getEditions()))
				.assignments(new HashSet<>(assignments))
				.rooms(new HashSet<>(rooms)));
	}

	private SessionDetailsDTO initializeSession(EditionDetailsDTO edition, LocalDateTime start,
			List<RoomDetailsDTO> rooms, List<AssignmentSummaryDTO> assignments) {
		return finalize(new SessionDetailsDTO()
				.name("Session " + (sessionId + 1))
				.start(start)
				.endTime(start.plusHours(4))
				.description("Just another session")
				.edition(toView(edition, EditionSummaryDTO.class))
				.editions(new HashSet<>(List.of(toView(edition, EditionSummaryDTO.class))))
				.assignments(new HashSet<>(assignments))
				.rooms(new HashSet<>(rooms)));
	}

	private SessionDetailsDTO[] initializeSessions(EditionDetailsDTO edition, Long offset,
			List<RoomDetailsDTO> rooms, List<AssignmentSummaryDTO> assignments) {
		var nSessions = (int) edition.getStartDate().until(edition.getEndDate(), ChronoUnit.WEEKS);
		var sessions = new SessionDetailsDTO[nSessions];
		for (int i = 0; i < nSessions; i++) {
			sessions[i] = initializeSession(edition, edition.getStartDate().plusWeeks(i).plusDays(offset),
					rooms, assignments);
		}
		return sessions;
	}

	private PersonSummaryDTO[] initializeStudents() {
		var students = new PersonSummaryDTO[numberOfStudents];
		for (int i = 0; i < numberOfStudents; i++) {
			students[i] = pApiMocker.save(new PersonSummaryDTO()
					.id(3000L + i)
					.displayName("Student " + i)
					.username("student" + i)
					.email("student" + i + "@student.edumail.com")
					.accountDisabled(false)
					.defaultRole(PersonSummaryDTO.DefaultRoleEnum.STUDENT)
					.externalId("student" + i)
					.number(8327322 + i));
		}
		return students;
	}

	private PersonSummaryDTO[] initializeTeachers() {
		var students = new PersonSummaryDTO[numberOfStudents];
		for (int i = 0; i < numberOfStudents; i++) {
			students[i] = pApiMocker.save(new PersonSummaryDTO()
					.id(2000L + i)
					.displayName("Teacher " + i)
					.username("teacher" + i)
					.email("teacher" + i + "@edumail.com")
					.accountDisabled(false)
					.defaultRole(PersonSummaryDTO.DefaultRoleEnum.TEACHER)
					.externalId("teacher" + i)
					.number(-1));
		}
		return students;
	}

	private Long createSessionForLab(EditionDetailsDTO edition, String name, LocalDateTime start,
			LocalDateTime end, List<AssignmentDetailsDTO> assignments, List<RoomDetailsDTO> rooms) {
		return finalize(new SessionDetailsDTO()
				.start(start).endTime(end)
				.name(name).description("")
				.edition(toView(edition, EditionSummaryDTO.class))
				.editions(Set.of(toView(edition, EditionSummaryDTO.class)))
				.assignments(new HashSet<>(toView(assignments, AssignmentSummaryDTO.class)))
				.rooms(new HashSet<>(toView(rooms, RoomDetailsDTO.class)))).getId();
	}

	private Long createOopNowSessionForLabTomorrow(String name, List<AssignmentDetailsDTO> assignments,
			List<RoomDetailsDTO> rooms) {
		return createSessionForLab(oopNow, name, now().plusDays(1), now().plusDays(1).plusHours(4L),
				assignments, rooms);
	}

	private Long createOopNowSessionForLab(String name, List<AssignmentDetailsDTO> assignments,
			List<RoomDetailsDTO> rooms) {
		return createSessionForLab(oopNow, name, now(), now().plusHours(4L), assignments, rooms);
	}

	private Long createOop20SessionForLab(String name, List<AssignmentDetailsDTO> assignments,
			List<RoomDetailsDTO> rooms) {
		return createSessionForLab(oop20, name, now().withYear(2020), now().withYear(2020).plusHours(4L),
				assignments, rooms);
	}

	@AllArgsConstructor
	private class LabRequestEventsBuilder {
		private final LabRequest request;

		public LabRequestEventsBuilder take(Long assistantId, LocalDateTime at) {
			var event = new RequestTakenEvent(request, assistantId);
			event.setTimestamp(at);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public LabRequestEventsBuilder take(Long assistantId) {
			return take(assistantId, now());
		}

		public LabRequestEventsBuilder approve(Long assistantId, LocalDateTime at) {
			var event = new RequestApprovedEvent(request, assistantId, "");
			event.setTimestamp(at);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public LabRequestEventsBuilder approve(Long assistantId) {
			return approve(assistantId, now());
		}

		public LabRequestEventsBuilder reject(Long assistantId, LocalDateTime at) {
			var event = new RequestRejectedEvent(request, assistantId, "Rejected!", "");
			event.setTimestamp(at);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public LabRequestEventsBuilder reject(Long assistantId) {
			return reject(assistantId, now());
		}

		public LabRequestEventsBuilder forwardToAny(Long assistantId, LocalDateTime at) {
			var event = new RequestForwardedToAnyEvent(request, assistantId, "Forwarded!");
			event.setTimestamp(at);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public LabRequestEventsBuilder forwardToAny(Long assistantId) {
			return forwardToAny(assistantId, now());
		}

		public LabRequestEventsBuilder forwardToPerson(Long assistantId, Long toId, LocalDateTime at) {
			var event = new RequestForwardedToPersonEvent(request, assistantId, toId, "Forwarded to you");
			event.setTimestamp(at);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public LabRequestEventsBuilder forwardToPerson(Long assistantId, Long toId) {
			return forwardToPerson(assistantId, toId, now());
		}

		public LabRequestEventsBuilder notFound(Long assistantId, LocalDateTime at) {
			var event = new StudentNotFoundEvent(request, assistantId);
			event.setTimestamp(at);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public LabRequestEventsBuilder notFound(Long assistantId) {
			return notFound(assistantId, now());
		}

		public LabRequestEventsBuilder notPicked(LocalDateTime at) {
			var event = new RequestNotPickedEvent(request);
			event.setTimestamp(at);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public LabRequestEventsBuilder notPicked() {
			return notPicked(now());
		}

		public void finish() {
			rr.save(request);
		}
	}

	@AllArgsConstructor
	private class SelectionRequestEventsBuilder {
		private final SelectionRequest request;

		public SelectionRequestEventsBuilder selected() {
			var event = new RequestSelectedEvent(request);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public SelectionRequestEventsBuilder notSelected() {
			var event = new RequestNotSelectedEvent(request);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public SelectionRequestEventsBuilder revoked() {
			var event = new RequestRevokedEvent(request);
			request.getEventInfo().getEvents().add(event);
			rer.applyAndSave(event);
			return this;
		}

		public void finish() {
			rr.save(request);
		}
	}

	private static <T> T toView(Object data, Class<T> clazz) {
		return mapper.map(data, clazz);
	}

	private static <T> List<T> toView(Collection<?> data, Class<T> clazz) {
		return data.stream()
				.map(d -> toView(d, clazz))
				.collect(Collectors.toList());
	}

	@Bean
	public AssignmentApiMocker assignmentApiMocker() {
		return new AssignmentApiMocker(aApi);
	}

	@Bean
	public ClusterApiMocker clusterApiMocker() {
		return new ClusterApiMocker(clApi);
	}

	@Bean
	public CohortApiMocker cohortApiMocker() {
		return new CohortApiMocker(coApi);
	}

	@Bean
	public CourseApiMocker courseApiMocker() {
		return new CourseApiMocker(cApi);
	}

	@Bean
	public EditionApiMocker editionApiMocker() {
		return new EditionApiMocker(eApi);
	}

	@Bean
	public EditionCollectionApiMocker editionCollectionApiMocker() {
		return new EditionCollectionApiMocker(ecApi);
	}

	@Bean
	public ModuleApiMocker moduleApiMocker() {
		return new ModuleApiMocker(mApi);
	}

	@Bean
	public ModuleDivisionApiMocker moduleDivisionApiMocker() {
		return new ModuleDivisionApiMocker(mdApi);
	}

	@Bean
	public PersonApiMocker personApiMocker() {
		return new PersonApiMocker(pApi);
	}

	@Bean
	public ProgramApiMocker programApiMocker() {
		return new ProgramApiMocker(prApi);
	}

	@Bean
	public RoleApiMocker roleApiMocker() {
		return new RoleApiMocker(authApi, pApi, eApi, rlApi);
	}

	@Bean
	public RoomApiMocker roomApiMocker() {
		return new RoomApiMocker(rApi);
	}

	@Bean
	public SessionApiMocker sessionApiMocker() {
		return new SessionApiMocker(sApi);
	}

	@Bean
	public StudentGroupApiMocker studentGroupApiMocker() {
		return new StudentGroupApiMocker(sgApi);
	}
}
