/*
 * 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 java.time.LocalDateTime.now;
import static nl.tudelft.queue.model.enums.QueueSessionType.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyIterable;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.transaction.Transactional;

import nl.tudelft.labracore.api.PersonControllerApi;
import nl.tudelft.labracore.api.RoleControllerApi;
import nl.tudelft.labracore.api.dto.EditionDetailsDTO;
import nl.tudelft.labracore.api.dto.Id;
import nl.tudelft.labracore.api.dto.PersonDetailsDTO;
import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
import nl.tudelft.queue.csv.EmptyCsvException;
import nl.tudelft.queue.csv.InvalidCsvException;
import nl.tudelft.queue.dto.view.QueueSessionSummaryDTO;
import nl.tudelft.queue.misc.QueueSessionStatus;
import nl.tudelft.queue.model.embeddables.Slot;
import nl.tudelft.queue.model.labs.ExamLab;
import nl.tudelft.queue.model.labs.RegularLab;
import nl.tudelft.queue.model.labs.SlottedLab;
import nl.tudelft.queue.repository.QueueSessionRepository;

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 org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;

import reactor.core.publisher.Mono;
import test.nodb.NoDbTestQueueApplication;

@Transactional
@SpringBootTest(classes = NoDbTestQueueApplication.class)
public class EditionServiceTest {

	@MockBean
	private QueueSessionRepository lr;

	@Autowired
	private EditionService es;

	@Autowired
	private PersonControllerApi personControllerApi;

	@Autowired
	private RoleControllerApi roleControllerApi;

	private PersonSummaryDTO person1;
	private PersonSummaryDTO person2;
	private PersonSummaryDTO person3;
	private PersonSummaryDTO person4;
	private PersonSummaryDTO person5;

	private List<PersonSummaryDTO> people;

	private QueueSessionSummaryDTO regularLab;
	private QueueSessionSummaryDTO examLab;
	private QueueSessionSummaryDTO slottedLab;

	private List<QueueSessionSummaryDTO> labs;

	private Slot labSlot;

	private EditionDetailsDTO editionDetailsDTO;

	@BeforeEach
	void setUp() {
		person1 = new PersonSummaryDTO()
				.displayName("Christopher Columbus")
				.number(345)
				.username("cruv");
		person2 = new PersonSummaryDTO()
				.displayName("Rubenov Maliciious")
				.number(6482)
				.username("rmaliciious");
		person3 = new PersonSummaryDTO()
				.displayName("Sarin Justice")
				.number(618)
				.username("hippo5");
		person4 = new PersonSummaryDTO()
				.displayName("Karen McKarenVille")
				.number(42)
				.username("karen42");
		person5 = new PersonSummaryDTO()
				.displayName("Ottomano Coronu")
				.number(55)
				.username("maninthecorona");

		people = List.of(person1, person2, person3, person4, person5);

		LocalDateTime labTime = now();
		labSlot = new Slot(labTime.minusMinutes(5), labTime.plusMinutes(10));

		regularLab = new QueueSessionSummaryDTO(0L, REGULAR, null, "lab", "ReadableName", labSlot, 0, "",
				false, false,
				QueueSessionStatus.OPEN, false);
		examLab = new QueueSessionSummaryDTO(1L, EXAM, null, "lab", "ReadableName", labSlot, 0, "",
				false, false,
				QueueSessionStatus.OPEN, false);
		slottedLab = new QueueSessionSummaryDTO(2L, SLOTTED, null, "lab", "ReadableName", labSlot, 0, "",
				false,
				false,
				QueueSessionStatus.OPEN, false);

		labs = List.of(regularLab, examLab, slottedLab);

		RegularLab regularDetailLab = new RegularLab();
		regularDetailLab.setId(0L);
		regularDetailLab.setModules(Set.of(0L, 2L));
		ExamLab examDetailLab = new ExamLab();
		examDetailLab.setId(1L);
		examDetailLab.setModules(Set.of(0L, 1L));
		SlottedLab slottedDetailLab = new SlottedLab();
		slottedDetailLab.setId(2L);
		slottedDetailLab.setModules(Collections.emptySet());

		when(lr.findAllById(anyIterable()))
				.thenReturn(List.of(regularDetailLab, examDetailLab, slottedDetailLab));
		editionDetailsDTO = new EditionDetailsDTO().id(1L).name("testEdition");
	}

	@Test
	void studentsMatchingFilterChecksDisplayName() {
		assertThat(es.studentsMatchingFilter(people, "en"))
				.containsExactly(person2, person4);
	}

	@Test
	void studentsMatchingFilterChecksStudentNumber() {
		assertThat(es.studentsMatchingFilter(people, "5"))
				.containsExactly(person1, person3, person5);
	}

	@Test
	void studentsMatchingFilterChecksUsername() {
		assertThat(es.studentsMatchingFilter(people, "p"))
				.containsExactly(person1, person3);
	}

	@Test
	void sortLabsSortsOpenBeforeClosed() {
		QueueSessionSummaryDTO openLab = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"lab", "ReadableName",
				new Slot(now().minusMinutes(5), now().plusMinutes(10)),
				0, "", false, false, QueueSessionStatus.OPEN, false);
		QueueSessionSummaryDTO closedLab = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"lab", "ReadableName",
				new Slot(now().minusMinutes(10), now().minusMinutes(5)),
				0, "", false, false, QueueSessionStatus.OPEN, false);

		assertThat(es.sortLabs(List.of(closedLab, openLab))).containsExactly(openLab, closedLab);
		assertThat(es.sortLabs(List.of(openLab, closedLab))).containsExactly(openLab, closedLab);
	}

	@Test
	void sortLabsSortsPastLabsAfterFuture() {
		QueueSessionSummaryDTO pastLab = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"lab", "ReadableName",
				new Slot(now().minusMinutes(15), now().minusMinutes(10)),
				0, "", false, false, QueueSessionStatus.OPEN, false);
		QueueSessionSummaryDTO futureLab = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"lab", "ReadableName",
				new Slot(now().plusMinutes(20), now().plusMinutes(100)),
				0, "", false, false, QueueSessionStatus.OPEN, false);

		assertThat(es.sortLabs(List.of(futureLab, pastLab))).containsExactly(futureLab, pastLab);
		assertThat(es.sortLabs(List.of(pastLab, futureLab))).containsExactly(futureLab, pastLab);
	}

	@Test
	void sortLabsSortsOnMinutes() {
		QueueSessionSummaryDTO lab1 = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"lab", "ReadableName",
				new Slot(now().plusMinutes(5), now().plusMinutes(50)),
				0, "", false, false, QueueSessionStatus.OPEN, false);
		QueueSessionSummaryDTO lab2 = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"lab", "ReadableName",
				new Slot(now().plusMinutes(10), now().plusMinutes(15)),
				0, "", false, false, QueueSessionStatus.OPEN, false);
		QueueSessionSummaryDTO lab3 = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"lab", "ReadableName",
				new Slot(now().plusMinutes(70), now().plusMinutes(90)),
				0, "", false, false, QueueSessionStatus.OPEN, false);

		assertThat(es.sortLabs(List.of(lab2, lab3, lab1))).containsExactly(lab1, lab2, lab3);
	}

	@Test
	void sortLabsSortsOnName() {
		QueueSessionSummaryDTO lab1 = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"Alab", "ReadableName",
				labSlot,
				0, "", false, false, QueueSessionStatus.OPEN, false);
		QueueSessionSummaryDTO lab2 = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"Flab", "ReadableName",
				labSlot,
				0, "", false, false, QueueSessionStatus.OPEN, false);
		QueueSessionSummaryDTO lab3 = new QueueSessionSummaryDTO(0L,
				REGULAR,
				null,
				"Zlab", "ReadableName",
				labSlot,
				0, "", false, false, QueueSessionStatus.OPEN, false);
		assertThat(es.sortLabs(List.of(lab3, lab2, lab1))).containsExactly(lab1, lab2, lab3);
	}

	@Test
	void filterLabsFiltersOnOneType() {
		assertThat(es.filterLabs(labs, List.of(REGULAR), Collections.emptyList()))
				.containsExactly(regularLab);
	}

	@Test
	void filterLabsFiltersWithMultipleTypes() {
		assertThat(es.filterLabs(labs, List.of(EXAM, SLOTTED), Collections.emptyList()))
				.containsExactlyInAnyOrder(examLab,
						slottedLab);
	}

	@Test
	void filterLabsFiltersOnOneModule() {
		assertThat(es.filterLabs(labs, Collections.emptyList(), List.of(0L)))
				.containsExactlyInAnyOrder(regularLab, examLab);
	}

	@Test
	void filterLabsFiltersOnMultipleModules() {
		assertThat(es.filterLabs(labs, Collections.emptyList(), List.of(1L, 2L)))
				.containsExactlyInAnyOrder(regularLab, examLab);
	}

	@Test
	void filterLabsDoesNotFilterWithEmptyList() {
		assertThat(es.filterLabs(labs, Collections.emptyList(), Collections.emptyList())).isEqualTo(labs);
	}

	@Test
	void addParticipantsCSVFile() throws EmptyCsvException, InvalidCsvException {
		Mono<PersonDetailsDTO> personDetailsDTOMono = Mono.just(new PersonDetailsDTO().id(100L));
		Mono<Id> idMono = Mono.just(new Id().personId(1L));
		doReturn(personDetailsDTOMono).when(personControllerApi).getPersonByUsername(any());
		doReturn(idMono).when(roleControllerApi).addRole(any());
		MockMultipartFile file = new MockMultipartFile(
				"file",
				"test.csv",
				MediaType.TEXT_PLAIN_VALUE,
				"\"netid\", \"role\"\n\"netid1\",\"TA\"".getBytes());

		es.addCourseParticipants(file, editionDetailsDTO);

		verify(personControllerApi).getPersonByUsername("netid1");
	}
}
