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

import static nl.tudelft.queue.model.enums.RequestType.QUESTION;
import static nl.tudelft.queue.model.enums.RequestType.SUBMISSION;
import static org.assertj.core.api.Assertions.assertThat;

import java.time.LocalDateTime;
import java.util.*;

import javax.transaction.Transactional;

import nl.tudelft.librador.SpringContext;
import nl.tudelft.queue.model.embeddables.AllowedRequest;
import nl.tudelft.queue.model.embeddables.RequestEventInfo;
import nl.tudelft.queue.model.enums.CommunicationMethod;
import nl.tudelft.queue.model.enums.RequestStatus;
import nl.tudelft.queue.model.enums.RequestType;
import nl.tudelft.queue.model.labs.Lab;
import nl.tudelft.queue.model.labs.RegularLab;
import nl.tudelft.queue.model.labs.SlottedLab;

import org.assertj.core.data.Offset;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;

import test.TestDatabaseLoader;
import test.labracore.SessionApiMocker;
import test.test.TestQueueApplication;

@Transactional
@AutoConfigureMockMvc
@SpringBootTest(classes = TestQueueApplication.class)
class LabTest {

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

	private Lab lab1;

	@Autowired
	private SessionApiMocker sMock;

	@Autowired
	private ApplicationContext applicationContext;

	@Autowired
	private TestDatabaseLoader db;

	@BeforeEach
	void setUp() {
		var now = LocalDateTime.now();
		request1 = LabRequest.builder()
				.id(7832L)
				.requestType(QUESTION)
				.assignment(8923L)
				.comment("hdsada")
				.studentGroup(8932L)
				.room(8932L)
				.requester(893323L)
				.createdAt(now.minusSeconds(30))
				.eventInfo(RequestEventInfo.builder()
						.status(RequestStatus.PENDING)
						.build())
				.build();

		request2 = LabRequest.builder()
				.id(8932L)
				.requestType(QUESTION)
				.assignment(56582L)
				.comment("hdsada")
				.studentGroup(7562L)
				.room(85612L)
				.requester(8363L)
				.eventInfo(RequestEventInfo.builder()
						.status(RequestStatus.PROCESSING)
						.build())
				.build();

		request3 = LabRequest.builder()
				.id(8932L)
				.requestType(QUESTION)
				.assignment(56582L)
				.comment("hdsada")
				.studentGroup(7562L)
				.room(85612L)
				.requester(8363L)
				.createdAt(now.minusSeconds(120))
				.eventInfo(RequestEventInfo.builder()
						.status(RequestStatus.REJECTED)
						.firstProcessedAt(now.minusSeconds(60))
						.lastEventAt(now.minusSeconds(30))
						.build())
				.build();

		sMock.mock();

		var session = db.getOopNowLcLabs()[0];

		lab1 = RegularLab.builder()
				.id(56723L)
				.session(session.getId())
				.allowedRequests(Set.of())
				.communicationMethod(CommunicationMethod.TA_VISIT_STUDENT)
				.enqueueClosed(false)
				.modules(Set.of())
				.deletedAt(null)
				.requests(List.of(request1, request2, request3))
				.build();
	}

	@Test
	void settingAllowedRequestTypesWorks() {
		lab1.setAllowedRequestsFromMap(Map.of(
				342L, Set.of(QUESTION, SUBMISSION),
				78231L, Set.of(QUESTION),
				7831L, Set.of(SUBMISSION)));

		assertThat(lab1.getAllowedRequests()).isEqualTo(Set.of(
				AllowedRequest.of(342L, QUESTION),
				AllowedRequest.of(342L, SUBMISSION),
				AllowedRequest.of(78231L, QUESTION),
				AllowedRequest.of(7831L, SUBMISSION)));
	}

	@Test
	void settingAllowedRequestTypesOverwritesOldValue() {
		lab1.setAllowedRequests(Set.of(new AllowedRequest(3232L, QUESTION)));

		lab1.setAllowedRequestsFromMap(Map.of());

		assertThat(lab1.getAllowedRequests()).isEmpty();
	}

	@Test
	void nullRequestTypesGetIgnoredInSettingAllowedRequestTypes() {
		Set<RequestType> set1 = new HashSet<>();
		set1.add(QUESTION);
		set1.add(null);
		Set<RequestType> set2 = new HashSet<>();
		set2.add(null);
		Map<Long, Set<RequestType>> map = new HashMap<>();
		map.put(812L, Set.of());
		map.put(831L, set1);
		map.put(88L, set2);
		map.put(85L, null);

		lab1.setAllowedRequestsFromMap(map);

		assertThat(lab1.getAllowedRequests()).isEqualTo(Set.of(
				AllowedRequest.of(831L, QUESTION)));
	}

	@Test
	void getOpenRequestForPersonIfPresent() {
		assertThat(lab1.getOpenRequestForPerson(request1.getRequester()))
				.isPresent().contains(request1);
	}

	@Test
	void getOpenRequestForPersonIfNotPresent() {
		assertThat(lab1.getOpenRequestForPerson(78327837283L))
				.isNotPresent();
	}

	@Test
	void getOpenRequestForPersonIfProcessing() {
		assertThat(lab1.getOpenRequestForPerson(request2.getRequester()))
				.isPresent().contains(request2);
	}

	@Test
	void getPendingRequestForPersonIfPending() {
		assertThat(lab1.getPendingRequestForPerson(request1.getRequester()))
				.isPresent().contains(request1);
	}

	@Test
	void getPendingRequestForPersonIfProcessing() {
		assertThat(lab1.getPendingRequestForPerson(request2.getRequester()))
				.isNotPresent();
	}

	@Test
	void getPendingRequestForPersonIfPersonDoesNotExist() {
		assertThat(lab1.getPendingRequestForPerson(78323L))
				.isNotPresent();
	}

	@Test
	void getAllRequestsForPersonWorks() {
		assertThat(lab1.getAllRequestsForPerson(request1.getRequester()))
				.isEqualTo(List.of(request1));
	}

	@Test
	void hasOpenRequestForPersonIfExists() {
		assertThat(lab1.hasOpenRequestForPerson(request1.getRequester()))
				.isTrue();
	}

	@Test
	void hasOpenRequestForPersonIfNotExists() {
		assertThat(lab1.hasOpenRequestForPerson(78327L))
				.isFalse();
	}

	@Test
	void averageWaitingTimeAveragesOverHandled() {
		SpringContext.useContext(applicationContext);
		assertThat(lab1.averageWaitingTime()).isPresent()
				.hasValueCloseTo(60.0, Offset.offset(0.005));
	}

	@Test
	void currentWaitingTimeWorksOnLastPending() {
		SpringContext.useContext(applicationContext);
		assertThat(lab1.currentWaitingTime()).isPresent()
				.hasValueCloseTo(30.0, Offset.offset(0.005));
	}

	@Test
	void positionCalculatesThePositionOfPerson() {
		assertThat(lab1.position(request1.getRequester()))
				.isEqualTo(1);
	}

	@Test
	void positionOfNonExistingPersonIs0() {
		assertThat(lab1.position(678325L))
				.isEqualTo(0);
	}

	@Test
	void getHandledShowsAllHandledRequests() {
		assertThat(lab1.getHandled())
				.containsExactly(request3);
	}

	@Test
	void slottedLabStatusIsCorrectlyDetermined() {
		var sut = SlottedLab.builder().build();
		assertThat(sut.isConsecutive()).isFalse();
		sut.getSlottedLabConfig().setConsideredFullPercentage(1);
		assertThat(sut.isConsecutive()).isTrue();
		sut.getSlottedLabConfig().setConsideredFullPercentage(99);
		assertThat(sut.isConsecutive()).isTrue();
		sut.getSlottedLabConfig().setConsideredFullPercentage(100);
		assertThat(sut.isConsecutive()).isTrue();
	}
}
