/*
 * Queue - A Queueing system that can be used to handle labs in higher education
 * Copyright (C) 2016-2024  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.Set;
import java.util.stream.Collectors;

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.Profile;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;
import jakarta.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.labs.RegularLabCreateDTO;
import nl.tudelft.queue.dto.patch.FeedbackPatchDTO;
import nl.tudelft.queue.model.Feedback;
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;

@Service
@Profile("development")
public class DevDatabaseLoader implements InitializingBean {
	@Autowired
	private DbLoaderControllerApi dbApi;

	@Autowired
	private EditionControllerApi eApi;

	@Autowired
	private RoomControllerApi rApi;

	@Autowired
	private ModuleControllerApi mApi;

	@Autowired
	private RoleControllerApi roleControllerApi;

	@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;

	@Autowired
	private FeedbackRepository fr;

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

	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 List<PersonSummaryDTO> oopOldStudents;

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

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

	private Announcement announcement;

	private LabRequest request;
	private LabRequest oldRequest;
	@Autowired
	private QuestionControllerApi qApi;
	@Autowired
	private LabRepository labRepository;

	@Override
	@Transactional
	@PostConstruct
	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());
		oopOldStudents = rds.students(eApi.getEditionParticipants(oopOld.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();
		assistant3 = people.stream().filter(p -> "csestudent5".equals(p.getUsername())).findFirst().get();
		assistant1P = Person.builder().id(assistant1.getId()).username(assistant1.getUsername()).build();
	}

	private void initLabs() {
		roleControllerApi.addRole(new RoleCreateDTO().edition(new EditionIdDTO().id(oopOld.getId()))
				.person(new PersonIdDTO().id(assistant3.getId())).type(RoleCreateDTO.TypeEnum.HEAD_TA))
				.block();

		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().minusYears(1),
						LocalDateTime.now().plusMinutes(225).minusYears(1)))
				.build(), oopOld.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);

		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));
		});

		oldRequest = rr.save(LabRequest.builder().requestType(QUESTION)
				.comment("What should I do?")
				.question("help I don't seem to be able to add feedback")
				.timeSlot(null)
				.assignment(oopOldModuleA.getAssignments().get(0).getId())
				.room(rooms.get(0).getId())
				.session(oopOldL0)
				.requester(oopOldStudents.get(0).getId())
				.studentGroup(rs.getOrCreateIndividualStudentGroup(
						oopOldL0.getId(), oopOldStudents.get(0).getId(), oopOldModuleA.getId()).getId())
				.build());
		var event = new RequestCreatedEvent(oldRequest);
		event.setTimestamp(LocalDateTime.now().minusYears(1));
		rer.applyAndSave(event);
	}

	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);

		var eventTaken = new RequestTakenEvent(oldRequest, assistant3.getId());
		eventTaken.apply();
		eventTaken.setTimestamp(LocalDateTime.now().minusYears(1).plusMinutes(1));
		rer.save(eventTaken);
		var eventApproved = new RequestApprovedEvent(oldRequest, assistant3.getId(), "");
		eventApproved.apply();
		eventApproved.setTimestamp(LocalDateTime.now().minusYears(1).plusMinutes(2));
		rer.save(eventApproved);
	}

	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());
		var dto = FeedbackPatchDTO.builder()
				.rating(4).feedback("Some not so nice feedback?").build();

		// Manually save old feedback so that it is visible in queue
		fr.save(Feedback.builder()
				.id(new Feedback.Id(oldRequest.getId(), assistant3.getId()))
				.request(oldRequest)
				.feedback(dto.getFeedback())
				.rating(dto.getRating())
				.createdAt(LocalDateTime.now().minusYears(1).plusMinutes(4))
				.lastUpdatedAt(LocalDateTime.now().minusYears(1).plusMinutes(4))
				.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());
	}

}
