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

import static org.assertj.core.api.Assertions.assertThat;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import nl.tudelft.queue.model.embeddables.RequestEventInfo;
import nl.tudelft.queue.model.embeddables.Slot;
import nl.tudelft.queue.model.embeddables.SlottedLabConfig;
import nl.tudelft.queue.model.enums.CommunicationMethod;
import nl.tudelft.queue.model.enums.RequestStatus;
import nl.tudelft.queue.model.labs.SlottedLab;

public class TimeSlotTest {

	private TimeSlot timeSlot1;
	private TimeSlot timeSlot2;
	private TimeSlot timeSlot3;

	private TimeSlot consecutiveTimeSlot1;
	private TimeSlot consecutiveTimeSlot2;
	private TimeSlot consecutiveTimeSlot3;
	private TimeSlot consecutiveTimeSlot4;

	private LabRequest request1;
	private LabRequest request2;
	private LabRequest request3;

	private SlottedLab slottedLabWithoutConsecutiveFilling;

	private SlottedLab consecutiveSlottedLab;

	@BeforeEach
	void setUp() {
		slottedLabWithoutConsecutiveFilling = SlottedLab.builder()
				.id(453242L)
				.session(342324523L)
				.slottedLabConfig(SlottedLabConfig.builder()
						.selectionOpensAt(LocalDateTime.now())
						.build())
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.build();

		consecutiveSlottedLab = SlottedLab.builder()
				.id(4542L)
				.session(342323L)
				.slottedLabConfig(SlottedLabConfig.builder()
						.selectionOpensAt(LocalDateTime.now()).consideredFullPercentage(100)
						.previousEmptyAllowedThreshold(0).build())
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.build();

		request1 = LabRequest.builder()
				.eventInfo(RequestEventInfo.builder()
						.status(RequestStatus.REVOKED)
						.build())
				.build();

		request2 = LabRequest.builder()
				.eventInfo(RequestEventInfo.builder()
						.status(RequestStatus.PENDING)
						.build())
				.build();

		request3 = LabRequest.builder()
				.eventInfo(RequestEventInfo.builder()
						.status(RequestStatus.REJECTED)
						.build())
				.build();

		timeSlot1 = new TimeSlot();
		timeSlot1.setCapacity(2);
		timeSlot1.setRequests(Set.of(request1, request2, request3));

		timeSlot2 = new TimeSlot();
		timeSlot2.setCapacity(3);
		timeSlot2.setRequests(Set.of(request1, request2, request3));

		timeSlot3 = new TimeSlot();
		timeSlot3.setCapacity(3);
		timeSlot3.setRequests(Set.of(request1));

		initializeConsecutiveTimeSlots();
	}

	private void initializeConsecutiveTimeSlots() {
		Slot[] slots = Stream
				.iterate(consecutiveSlottedLab.getSlottedLabConfig().getSelectionOpensAt().plusMinutes(5L),
						dateTime -> dateTime.plusMinutes(15))
				.limit(4)
				.map(dateTime -> Slot.builder()
						.opensAt(dateTime)
						.closesAt(dateTime.plusMinutes(15))
						.build())
				.toArray(Slot[]::new);

		consecutiveTimeSlot1 = new TimeSlot();
		consecutiveTimeSlot1.setCapacity(5);
		consecutiveTimeSlot1.setRequests(Set.of(request1));
		consecutiveTimeSlot1.setSlot(slots[0]);
		consecutiveTimeSlot1.setLab(consecutiveSlottedLab);

		consecutiveTimeSlot2 = new TimeSlot();
		consecutiveTimeSlot2.setCapacity(5);
		consecutiveTimeSlot2.setRequests(Set.of(request1));
		consecutiveTimeSlot2.setSlot(slots[1]);
		consecutiveTimeSlot2.setLab(consecutiveSlottedLab);

		consecutiveTimeSlot3 = new TimeSlot();
		consecutiveTimeSlot3.setCapacity(5);
		consecutiveTimeSlot3.setRequests(Set.of(request1));
		consecutiveTimeSlot3.setSlot(slots[2]);
		consecutiveTimeSlot3.setLab(consecutiveSlottedLab);

		consecutiveTimeSlot4 = new TimeSlot();
		consecutiveTimeSlot4.setCapacity(5);
		consecutiveTimeSlot4.setRequests(Set.of(request1));
		consecutiveTimeSlot4.setSlot(slots[3]);
		consecutiveTimeSlot4.setLab(consecutiveSlottedLab);

		consecutiveSlottedLab.getTimeSlots().addAll(List.of(consecutiveTimeSlot1, consecutiveTimeSlot2,
				consecutiveTimeSlot3, consecutiveTimeSlot4));

	}

	@Test
	void countSlotsTakenDoesNotIncludeRevoked() {
		assertThat(timeSlot3.countSlotsOccupied())
				.isEqualTo(0);
	}

	@Test
	void countSlotsTakenDoesIncludeOtherRequestStatuses() {
		assertThat(timeSlot1.countSlotsOccupied())
				.isEqualTo(2);
	}

	@Test
	void isFullChecksAgainstCapacityNegative() {
		assertThat(timeSlot1.isFull()).isTrue();
	}

	@Test
	void isFullChecksAgainstCapacityPositive() {
		assertThat(timeSlot2.isFull()).isFalse();
	}

	@Test
	void nonConsecutiveLabReturnsExpectedResultsForAllFunctions() {
		assertThat(consecutiveTimeSlot1.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot4.canTakeSlot()).isFalse();
	}

	@Test
	void nonConsecutiveSlotEverythingCanBeTaken() {
		consecutiveSlottedLab.getSlottedLabConfig().setConsideredFullPercentage(0);
		assertThat(consecutiveTimeSlot1.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot4.canTakeSlot()).isTrue();
	}

	@Test
	void slidingWindowConsecutiveFillingWorksAsIntended() {
		consecutiveSlottedLab.getSlottedLabConfig().setPreviousEmptyAllowedThreshold(1);
		assertThat(consecutiveTimeSlot1.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot4.canTakeSlot()).isFalse();
	}

	@Test
	void slidingWindowWithZeroCapacityInTheMiddleWorksAsIntended() {
		consecutiveSlottedLab.getSlottedLabConfig().setPreviousEmptyAllowedThreshold(1);
		consecutiveTimeSlot2.setCapacity(0);

		assertThat(consecutiveTimeSlot1.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot4.canTakeSlot()).isFalse();
	}

	@Test
	void labWith50PercentCapacityOpensAsPeopleEnque() {
		consecutiveSlottedLab.getSlottedLabConfig().setConsideredFullPercentage(50);
		consecutiveTimeSlot1.setRequests(Set.of(new LabRequest(), new LabRequest()));
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isFalse();
		consecutiveTimeSlot1.setRequests(Set.of(new LabRequest(), new LabRequest(), new LabRequest()));
		assertThat(consecutiveTimeSlot1.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isFalse();
	}

	@Test
	void labWith60PercentCapacityAndSlidingWindow() {
		consecutiveSlottedLab.getSlottedLabConfig().setConsideredFullPercentage(60);
		consecutiveSlottedLab.getSlottedLabConfig().setPreviousEmptyAllowedThreshold(1);
		assertThat(consecutiveTimeSlot1.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot4.canTakeSlot()).isFalse();

		consecutiveTimeSlot1.setRequests(Set.of(new LabRequest(), new LabRequest(), new LabRequest()));

		assertThat(consecutiveTimeSlot1.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot4.canTakeSlot()).isFalse();

	}

	@Test
	void enrollmentIsDisabledForSlotsThatHaveAlreadyStartedWorkingCorrectly() {
		consecutiveSlottedLab.getSlottedLabConfig().setConsideredFullPercentage(0);
		consecutiveSlottedLab.getSlottedLabConfig().setPreviousEmptyAllowedThreshold(0);
		consecutiveSlottedLab.getSlottedLabConfig().setDisableLateEnrollment(true);
		consecutiveTimeSlot1.setSlot(
				new Slot(LocalDateTime.now().minusMinutes(10L), LocalDateTime.now().plusMinutes(5L)));
		assertThat(consecutiveTimeSlot1.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isTrue();
	}

	@Test
	void consecutiveSlotsWithLateEnrollmentDisabledWorksTogetherCorrectlyOne() {
		consecutiveSlottedLab.getSlottedLabConfig().setConsideredFullPercentage(100);
		consecutiveSlottedLab.getSlottedLabConfig().setPreviousEmptyAllowedThreshold(0);
		consecutiveSlottedLab.getSlottedLabConfig().setDisableLateEnrollment(true);
		consecutiveTimeSlot1.setSlot(
				new Slot(LocalDateTime.now().minusMinutes(5L), consecutiveTimeSlot1.getSlot().getClosesAt()));
		assertThat(consecutiveTimeSlot1.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot4.canTakeSlot()).isFalse();
	}

	@Test
	void consecutiveSlotsWithLateEnrollmentDisabledWorksTogetherCorrectlyTwo() {
		consecutiveSlottedLab.getSlottedLabConfig().setConsideredFullPercentage(60);
		consecutiveSlottedLab.getSlottedLabConfig().setPreviousEmptyAllowedThreshold(1);
		consecutiveSlottedLab.getSlottedLabConfig().setDisableLateEnrollment(true);
		consecutiveTimeSlot1.setSlot(
				new Slot(LocalDateTime.now().minusMinutes(5L), consecutiveTimeSlot1.getSlot().getClosesAt()));
		consecutiveTimeSlot2.setRequests(Set.of(new LabRequest(), new LabRequest(), new LabRequest()));
		assertThat(consecutiveTimeSlot1.canTakeSlot()).isFalse();
		assertThat(consecutiveTimeSlot2.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot3.canTakeSlot()).isTrue();
		assertThat(consecutiveTimeSlot4.canTakeSlot()).isTrue();
	}

}
