/*
 * 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.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;

import javax.transaction.Transactional;

import nl.tudelft.labracore.api.SessionControllerApi;
import nl.tudelft.labracore.api.dto.SessionDetailsDTO;
import nl.tudelft.queue.dto.patch.SlottedLabConfigPatchDTO;
import nl.tudelft.queue.dto.patch.labs.SlottedLabPatchDTO;
import nl.tudelft.queue.model.TimeSlot;
import nl.tudelft.queue.model.embeddables.Slot;
import nl.tudelft.queue.model.embeddables.SlottedLabConfig;
import nl.tudelft.queue.model.labs.SlottedLab;
import nl.tudelft.queue.repository.LabRepository;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import reactor.core.publisher.Mono;
import test.TestDatabaseLoader;
import test.test.TestQueueApplication;

@Transactional
@SpringBootTest(classes = TestQueueApplication.class)
class TimeSlotServiceTest {

	@Autowired
	private TestDatabaseLoader db;

	@Autowired
	private LabService labService;

	@Autowired
	private LabRepository labRepository;

	@Autowired
	private SessionControllerApi sessionControllerApi;

	private Long oopNowSlottedLab1Id;

	private SessionDetailsDTO oopNowSlottedLab1Session;

	@BeforeEach
	void setup() {
		db.mockAll();
		when(sessionControllerApi.patchSession(any(), any()))
				.thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
	}

	@Test
	void shiftingSlotsWithHighUpperYieldsExpectedSlots() {
		LocalDateTime testTime = LocalDateTime.of(2021, 1, 1, 0, 0);
		SlottedLab testSlottedLab = new SlottedLab();
		testSlottedLab.setSession(1L);
		List<TimeSlot> testTimeSlots = List.of(
				new TimeSlot(testSlottedLab, new Slot(testTime, testTime.plusMinutes(10L)), 5, 0),
				new TimeSlot(testSlottedLab, new Slot(testTime.plusMinutes(10L), testTime.plusMinutes(20L)),
						5, 1),
				new TimeSlot(testSlottedLab, new Slot(testTime.plusMinutes(20L), testTime.plusMinutes(30L)),
						5, 2));
		testSlottedLab.setTimeSlots(testTimeSlots);
		testSlottedLab.setSlottedLabConfig(new SlottedLabConfig(10L, 5, testTime, 0, 0));

		var changeTimePatch = SlottedLabPatchDTO
				.builder()
				.slottedLabConfig(new SlottedLabConfigPatchDTO())
				.name("changing stuff")

				.slot(new Slot(testTime.plusHours(1L),
						testTime.plusHours(10L)))
				.build();

		labService.updateSession(changeTimePatch, testSlottedLab);

		assertThat(testSlottedLab.getTimeSlots()).hasSize(3)
				.isSortedAccordingTo(Comparator.comparing(TimeSlot::getOffsetSequenceNumber));
		assertThat(testTimeSlots.get(0).getSlot())
				.isEqualTo(new Slot(testTime.plusHours(1L), testTime.plusHours(1L).plusMinutes(10L)));
		assertThat(testTimeSlots.get(1).getSlot()).isEqualTo(
				new Slot(testTime.plusHours(1L).plusMinutes(10L), testTime.plusHours(1L).plusMinutes(20L)));
		assertThat(testTimeSlots.get(2).getSlot()).isEqualTo(
				new Slot(testTime.plusHours(1L).plusMinutes(20L), testTime.plusHours(1L).plusMinutes(30L)));

	}

	@Test
	void shiftingSlotsWithLowUpperYieldsExpectedSlots() {
		// this test is to illustrate the fact that the upper bound is not respected. This is because, we don't want slots being deleted.
		LocalDateTime testTime = LocalDateTime.of(2021, 1, 1, 0, 0);
		SlottedLab testSlottedLab = new SlottedLab();
		testSlottedLab.setSession(1L);
		List<TimeSlot> testTimeSlots = List.of(
				new TimeSlot(testSlottedLab, new Slot(testTime, testTime.plusMinutes(10L)), 5, 0),
				new TimeSlot(testSlottedLab, new Slot(testTime.plusMinutes(10L), testTime.plusMinutes(20L)),
						5, 1),
				new TimeSlot(testSlottedLab, new Slot(testTime.plusMinutes(20L), testTime.plusMinutes(30L)),
						5, 2));
		testSlottedLab.setTimeSlots(testTimeSlots);
		testSlottedLab.setSlottedLabConfig(new SlottedLabConfig(10L, 5, testTime));

		var changeTimePatch = SlottedLabPatchDTO
				.builder()
				.slottedLabConfig(new SlottedLabConfigPatchDTO())
				.name("changing stuff")

				.slot(new Slot(testTime.minusHours(1L),
						testTime.plusMinutes(30L)))
				.build();

		labService.updateSession(changeTimePatch, testSlottedLab);

		assertThat(testSlottedLab.getTimeSlots()).hasSize(3)
				.isSortedAccordingTo(Comparator.comparing(TimeSlot::getOffsetSequenceNumber));
		assertThat(testTimeSlots.get(0).getSlot())
				.isEqualTo(new Slot(testTime.minusHours(1L), testTime.minusHours(1L).plusMinutes(10L)));
		assertThat(testTimeSlots.get(1).getSlot()).isEqualTo(
				new Slot(testTime.minusHours(1L).plusMinutes(10L), testTime.minusHours(1L).plusMinutes(20L)));
		assertThat(testTimeSlots.get(2).getSlot()).isEqualTo(
				new Slot(testTime.minusHours(1L).plusMinutes(20L), testTime.minusHours(1L).plusMinutes(30L)));
	}

	@Test
	void shiftingSlotsWithNoTimeslotsYieldsExpectedResult() {
		LocalDateTime testTime = LocalDateTime.of(2021, 1, 1, 0, 0);
		SlottedLab testSlottedLab = new SlottedLab();
		testSlottedLab.setSession(1L);
		testSlottedLab.setSlottedLabConfig(new SlottedLabConfig(10L, 5, testTime));

		var changeTimePatch = SlottedLabPatchDTO
				.builder()
				.slottedLabConfig(new SlottedLabConfigPatchDTO())
				.name("changing stuff")

				.slot(new Slot(testTime.minusHours(1L),
						testTime.plusMinutes(30L)))
				.build();

		labService.updateSession(changeTimePatch, testSlottedLab);

		assertThat(testSlottedLab.getTimeSlots()).isEmpty();
	}

	@Test
	void shiftingSlotsWithLowDurationDoesNotCauseOverlapIssues() {
		LocalDateTime testTime = LocalDateTime.of(2021, 1, 1, 0, 0);
		SlottedLab testSlottedLab = new SlottedLab();
		testSlottedLab.setSession(1L);
		List<TimeSlot> testTimeSlots = List.of(
				new TimeSlot(testSlottedLab, new Slot(testTime, testTime.plusMinutes(2L)), 5, 0),
				new TimeSlot(testSlottedLab, new Slot(testTime.plusMinutes(2L), testTime.plusMinutes(4L)), 5,
						1),
				new TimeSlot(testSlottedLab, new Slot(testTime.plusMinutes(4L), testTime.plusMinutes(6L)), 5,
						2));
		testSlottedLab.setTimeSlots(testTimeSlots);
		testSlottedLab.setSlottedLabConfig(new SlottedLabConfig(2L, 5, testTime));

		var changeTimePatch = SlottedLabPatchDTO
				.builder()
				.slottedLabConfig(new SlottedLabConfigPatchDTO())
				.name("changing stuff")

				.slot(new Slot(testTime.plusMinutes(2L),
						testTime.plusMinutes(8L)))
				.build();

		labService.updateSession(changeTimePatch, testSlottedLab);

		assertThat(testSlottedLab.getTimeSlots()).hasSize(3)
				.isSortedAccordingTo(Comparator.comparing(TimeSlot::getOffsetSequenceNumber));
		assertThat(testTimeSlots.get(0).getSlot())
				.isEqualTo(new Slot(testTime.plusMinutes(2L), testTime.plusMinutes(4L)));
		assertThat(testTimeSlots.get(1).getSlot())
				.isEqualTo(new Slot(testTime.plusMinutes(4L), testTime.plusMinutes(6L)));
		assertThat(testTimeSlots.get(2).getSlot())
				.isEqualTo(new Slot(testTime.plusMinutes(6L), testTime.plusMinutes(8L)));

	}

}
