/*
 * 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 nl.tudelft.queue;

import static nl.tudelft.queue.model.enums.RequestType.QUESTION;
import static nl.tudelft.queue.model.enums.RequestType.SUBMISSION;
import static nl.tudelft.queue.service.LabService.SessionType.REGULAR;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.transaction.Transactional;

import nl.tudelft.labracore.api.*;
import nl.tudelft.labracore.api.DbLoaderControllerApi;
import nl.tudelft.labracore.api.EditionControllerApi;
import nl.tudelft.labracore.api.ModuleControllerApi;
import nl.tudelft.labracore.api.RoomControllerApi;
import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.queue.dto.create.embeddables.ExamLabConfigCreateDTO;
import nl.tudelft.queue.dto.create.embeddables.SlottedLabConfigCreateDTO;
import nl.tudelft.queue.dto.create.labs.ExamLabCreateDTO;
import nl.tudelft.queue.dto.create.labs.RegularLabCreateDTO;
import nl.tudelft.queue.dto.patch.FeedbackPatchDTO;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.model.embeddables.Slot;
import nl.tudelft.queue.model.enums.CommunicationMethod;
import nl.tudelft.queue.model.events.RequestApprovedEvent;
import nl.tudelft.queue.model.events.RequestCreatedEvent;
import nl.tudelft.queue.model.events.RequestForwardedToAnyEvent;
import nl.tudelft.queue.model.events.RequestTakenEvent;
import nl.tudelft.queue.model.labs.Lab;
import nl.tudelft.queue.model.misc.Announcement;
import nl.tudelft.queue.repository.*;
import nl.tudelft.queue.service.FeedbackService;
import nl.tudelft.queue.service.LabService;
import nl.tudelft.queue.service.RequestService;
import nl.tudelft.queue.service.RoleDTOService;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Service
@Profile("development")
@DependsOn({
		"nl.tudelft.librador.SpringContext"
})
public class DevDatabaseLoader implements InitializingBean {
	@Autowired
	private DbLoaderControllerApi dbApi;

	@Autowired
	private EditionControllerApi eApi;

	@Autowired
	private RoomControllerApi rApi;

	@Autowired
	private ModuleControllerApi mApi;

	@Autowired
	private FeedbackService fs;

	@Autowired
	private LabService ls;

	@Autowired
	private RequestService rs;

	@Autowired
	private RoleDTOService rds;

	@Autowired
	private AnnouncementRepository ar;

	@Autowired
	private RequestRepository rr;

	@Autowired
	private LabRequestRepository lrr;

	@Autowired
	private RequestEventRepository rer;

	private PersonSummaryDTO assistant1;
	private PersonSummaryDTO assistant2;
	private Person assistant1P;

	private List<RoomSummaryDTO> rooms;
	private List<PersonSummaryDTO> people;
	private List<EditionDetailsDTO> editions;

	private EditionDetailsDTO oopOld;
	private EditionDetailsDTO oopNow;
	private EditionDetailsDTO adsNow;

	private ModuleDetailsDTO oopOldModuleA;

	private ModuleDetailsDTO oopNowModuleA;
	private ModuleDetailsDTO adsNowModuleA;
	private ModuleDetailsDTO adsNowModuleI;

	private List<PersonSummaryDTO> oopStudents;
	private List<PersonSummaryDTO> adsStudents;

	private Lab oopOldL0;
	private Lab oopL1;
	private Lab oopL2;
	private Lab oopE;

	private Lab adsL1I;
	private Lab adsL1A;
	private Lab adsL2I;

	private Announcement announcement;

	private LabRequest request;

	@Autowired
	private QuestionControllerApi qApi;

	@Override
	@Transactional
	public synchronized void afterPropertiesSet() {
		fetch();

		initLabs();

		initRequests();
		initRequestEvents();
		initFeedback();

		initAnnouncements();
	}

	private void fetch() {
		rooms = rApi.getAllRooms().collectList().block();
		people = dbApi.getAllPeopleInDB().collectList().block();
		editions = eApi.getAllEditions().collectList().block();

		oopOld = eApi.getEditionById(editions.stream()
				.filter(e -> "19/20".equals(e.getName())
						&& e.getCourse().getName().equals("Object-Oriented Programming"))
				.findFirst().get().getId()).block();
		oopNow = eApi.getEditionById(editions.stream()
				.filter(e -> "NOW".equals(e.getName())
						&& e.getCourse().getName().equals("Object-Oriented Programming"))
				.findFirst().get().getId()).block();
		adsNow = eApi.getEditionById(editions.stream()
				.filter(e -> "NOW".equals(e.getName())
						&& e.getCourse().getName().equals("Algorithms & Datastructures"))
				.findFirst().get().getId()).block();

		oopOldModuleA = mApi.getModuleById(oopOld.getModules().get(0).getId()).block();

		oopNowModuleA = mApi.getModuleById(oopNow.getModules().get(0).getId()).block();

		adsNowModuleA = mApi.getModuleById(adsNow.getModules().get(0).getId()).block();
		adsNowModuleI = mApi.getModuleById(adsNow.getModules().get(1).getId()).block();

		oopStudents = rds.students(eApi.getEditionParticipants(oopNow.getId()).collectList().block());
		adsStudents = rds.students(eApi.getEditionParticipants(adsNow.getId()).collectList().block());

		assistant1 = people.stream().filter(p -> "cseteacher1".equals(p.getUsername())).findFirst().get();
		assistant2 = people.stream().filter(p -> "csestudent1".equals(p.getUsername())).findFirst().get();
		assistant1P = Person.builder().id(assistant1.getId()).username(assistant1.getUsername()).build();
	}

	private void initLabs() {
		oopOldL0 = ls.createSessions(RegularLabCreateDTO.builder()
				.name("OOP Lab 0ld")
				.eolGracePeriod(15)
				.modules(Set.of(oopOldModuleA.getId()))
				.requestTypes(oopOldModuleA.getAssignments().stream()
						.collect(Collectors.toMap(AssignmentSummaryDTO::getId, a -> Set.of(QUESTION))))
				.rooms(rooms.stream()
						.map(RoomSummaryDTO::getId).collect(Collectors.toSet()))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.slot(new Slot(LocalDateTime.now(), LocalDateTime.now().plusMinutes(225).minusYears(1)))
				.build(), oopNow.getId(), REGULAR).get(0);

		oopL1 = ls.createSessions(RegularLabCreateDTO.builder()
				.name("OOP Lab 1")
				.eolGracePeriod(15)
				.modules(Set.of(oopNowModuleA.getId()))
				.requestTypes(oopNowModuleA.getAssignments().stream()
						.collect(Collectors.toMap(AssignmentSummaryDTO::getId, a -> Set.of(QUESTION))))
				.rooms(rooms.stream().map(RoomSummaryDTO::getId).collect(Collectors.toSet()))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.slot(new Slot(LocalDateTime.now().minusYears(1),
						LocalDateTime.now().plusMinutes(225)))
				.build(), oopNow.getId(), REGULAR).get(0);

		oopL2 = ls.createSessions(RegularLabCreateDTO.builder()
				.name("OOP Lab 2")
				.eolGracePeriod(15)
				.modules(Set.of(oopNowModuleA.getId()))
				.requestTypes(oopNowModuleA.getAssignments().stream()
						.collect(Collectors.toMap(AssignmentSummaryDTO::getId,
								a -> Set.of(QUESTION, SUBMISSION))))
				.rooms(rooms.stream()
						.filter(r -> !r.getName().contains("a"))
						.map(RoomSummaryDTO::getId).collect(Collectors.toSet()))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.slot(new Slot(LocalDateTime.now().plusWeeks(1),
						LocalDateTime.now().plusWeeks(1).plusMinutes(225)))
				.build(), oopNow.getId(), REGULAR).get(0);

		oopE = ls.createSessions(ExamLabCreateDTO.builder()
				.name("Exam")
				.eolGracePeriod(15)
				.slottedLabConfig(SlottedLabConfigCreateDTO.builder()
						.capacity(3)
						.duration(15L)
						.selectionOpensAt(LocalDateTime.now().minusMinutes(5L)).build())
				.examLabConfig(ExamLabConfigCreateDTO.builder()
						.percentage(15).build())
				.modules(Set.of(oopNowModuleA.getId()))
				.requestTypes(Map.of(oopNowModuleA.getAssignments().get(0).getId(), Set.of(SUBMISSION)))
				.rooms(Set.of(rooms.get(0).getId()))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.slot(new Slot(LocalDateTime.now(),
						LocalDateTime.now().plusMinutes(225)))
				.build(), oopNow.getId(), REGULAR).get(0);

		adsL1I = ls.createSessions(RegularLabCreateDTO.builder()
				.name("ADS Lab 1")
				.eolGracePeriod(15)
				.modules(Set.of(adsNowModuleI.getId()))
				.requestTypes(adsNowModuleI.getAssignments().stream()
						.collect(Collectors.toMap(AssignmentSummaryDTO::getId,
								a -> Set.of(QUESTION, SUBMISSION))))
				.rooms(rooms.stream()
						.map(RoomSummaryDTO::getId).collect(Collectors.toSet()))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.slot(new Slot(LocalDateTime.now(), LocalDateTime.now().plusMinutes(225)))
				.build(), adsNow.getId(), REGULAR).get(0);

		adsL1A = ls.createSessions(RegularLabCreateDTO.builder()
				.name("Analysis Lab 1")
				.eolGracePeriod(15)
				.modules(Set.of(adsNowModuleA.getId()))
				.requestTypes(adsNowModuleA.getAssignments().stream()
						.collect(Collectors.toMap(AssignmentSummaryDTO::getId, a -> Set.of(QUESTION))))
				.rooms(rooms.stream()
						.map(RoomSummaryDTO::getId).collect(Collectors.toSet()))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.slot(new Slot(LocalDateTime.now(), LocalDateTime.now().plusMinutes(225)))
				.build(), adsNow.getId(), REGULAR).get(0);

		adsL2I = ls.createSessions(RegularLabCreateDTO.builder()
				.name("ADS Lab 2")
				.eolGracePeriod(15)
				.modules(Set.of(adsNowModuleI.getId()))
				.requestTypes(adsNowModuleI.getAssignments().stream()
						.collect(Collectors.toMap(AssignmentSummaryDTO::getId,
								a -> Set.of(QUESTION, SUBMISSION))))
				.rooms(rooms.stream()
						.filter(r -> !r.getName().contains("A"))
						.map(RoomSummaryDTO::getId).collect(Collectors.toSet()))
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.slot(new Slot(LocalDateTime.now().plusWeeks(1),
						LocalDateTime.now().plusWeeks(1).plusMinutes(225)))
				.build(), adsNow.getId(), REGULAR).get(0);
	}

	private void initRequests() {
		request = rr.save(LabRequest.builder()
				.requestType(QUESTION)
				.comment(null)
				.question("I have problem and will give feedback to whomever solves it")
				.timeSlot(null)
				.assignment(oopNowModuleA.getAssignments().get(0).getId())
				.room(rooms.get(0).getId())
				.session(oopL1)
				.requester(oopStudents.get(0).getId())
				.studentGroup(rs.getOrCreateIndividualStudentGroup(
						oopL1.getId(), oopStudents.get(0).getId(), oopNowModuleA.getId()).getId())
				.build());
		rer.applyAndSave(new RequestCreatedEvent(request));

		oopStudents.forEach(s -> {
			var r1 = rr.save(LabRequest.builder()
					.requestType(QUESTION)
					.comment("What should I do here?")
					.question("Ah well")
					.timeSlot(null)
					.assignment(oopNowModuleA.getAssignments().get(0).getId())
					.room(rooms.get(0).getId())
					.session(oopL1)
					.requester(s.getId())
					.studentGroup(rs.getOrCreateIndividualStudentGroup(
							oopL1.getId(), s.getId(), oopNowModuleA.getId()).getId())
					.build());
			rer.applyAndSave(new RequestCreatedEvent(r1));

			var r2 = rr.save(LabRequest.builder()
					.requestType(QUESTION)
					.comment("What should I do here?")
					.question("Ah well")
					.timeSlot(null)
					.assignment(oopNowModuleA.getAssignments().get(0).getId())
					.room(rooms.get(0).getId())
					.session(oopL2)
					.requester(s.getId())
					.studentGroup(rs.getOrCreateIndividualStudentGroup(
							oopL2.getId(), s.getId(), oopNowModuleA.getId()).getId())
					.build());
			rer.applyAndSave(new RequestCreatedEvent(r2));
		});
	}

	private void initRequestEvents() {
		rer.applyAndSave(new RequestTakenEvent(request, assistant1.getId()));
		rer.applyAndSave(new RequestForwardedToAnyEvent(request, assistant1.getId(), ""));
		rer.applyAndSave(new RequestTakenEvent(request, assistant2.getId()));
		rer.applyAndSave(new RequestApprovedEvent(request, assistant2.getId(), ""));
		lrr.save(request);
	}

	private void initFeedback() {
		fs.updateFeedback(request, assistant1.getId(), FeedbackPatchDTO.builder()
				.rating(4).feedback(null).build());
		fs.updateFeedback(request, assistant2.getId(), FeedbackPatchDTO.builder()
				.rating(null).feedback("Cool").build());
	}

	@Value("${queue.dev.show-banner:true}")
	private boolean showBanner;

	private void initAnnouncements() {
		if (!showBanner)
			return;

		announcement = ar.save(Announcement.builder()
				.isDismissible(false)
				.message("This is a development version of the Queue.")
				.startTime(LocalDateTime.now())
				.build());
	}

}
