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

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

import java.util.*;

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

import nl.tudelft.labracore.api.RoleControllerApi;
import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.labracore.lib.security.user.DefaultRole;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.queue.cache.EditionCacheManager;
import nl.tudelft.queue.cache.EditionRolesCacheManager;
import reactor.core.publisher.Flux;

public class RoleDTOServiceTest {
	private final RoleDTOService roleDTOService;
	private EditionRolesCacheManager eCache;

	private EditionCacheManager cacheManager;

	private SessionService sessionService;

	private final ModelMapper mapper = new ModelMapper();

	private final RoleControllerApi roleControllerApi;
	private PersonSummaryDTO person;
	private PersonSummaryDTO person2;
	private PersonSummaryDTO person3;

	private PersonSummaryDTO person4;
	private PersonSummaryDTO person5;
	private PersonSummaryDTO person6;
	private PersonSummaryDTO person7;

	private RolePersonDetailsDTO roleTeacher1;
	private RolePersonDetailsDTO roleTA1;
	private RolePersonDetailsDTO roleTA2;
	private RolePersonDetailsDTO roleTeacher2;

	private RolePersonDetailsDTO roleStudent1;
	private RolePersonDetailsDTO roleHeadTA;
	private RolePersonDetailsDTO roleTeacherRO;

	public RoleDTOServiceTest() {
		this.eCache = mock(EditionRolesCacheManager.class);
		this.cacheManager = mock(EditionCacheManager.class);
		this.roleControllerApi = mock(RoleControllerApi.class);
		this.sessionService = mock(SessionService.class);
		this.roleDTOService = new RoleDTOService(eCache, roleControllerApi, cacheManager, sessionService);
	}

	@BeforeEach
	void setUp() {
		this.person = new PersonSummaryDTO().id(1L).displayName("person 1");
		this.person2 = new PersonSummaryDTO().id(2L).displayName("person 2");
		this.person3 = new PersonSummaryDTO().id(3L).displayName("person 3");
		this.person4 = new PersonSummaryDTO().id(4L).displayName("person 4");
		this.person5 = new PersonSummaryDTO().id(5L).displayName("person 5");
		this.person6 = new PersonSummaryDTO().id(6L).displayName("person 6");
		this.person7 = new PersonSummaryDTO().id(7L).displayName("person 6");

		this.roleTeacher1 = new RolePersonDetailsDTO().person(person)
				.type(RolePersonDetailsDTO.TypeEnum.TEACHER);
		this.roleTeacher2 = new RolePersonDetailsDTO().person(person3)
				.type(RolePersonDetailsDTO.TypeEnum.TEACHER);
		this.roleTeacherRO = new RolePersonDetailsDTO().person(person7)
				.type(RolePersonDetailsDTO.TypeEnum.TEACHER_RO);
		this.roleHeadTA = new RolePersonDetailsDTO().person(person5)
				.type(RolePersonDetailsDTO.TypeEnum.HEAD_TA);
		this.roleTA1 = new RolePersonDetailsDTO().person(person2).type(RolePersonDetailsDTO.TypeEnum.TA);
		this.roleTA2 = new RolePersonDetailsDTO().person(person6).type(RolePersonDetailsDTO.TypeEnum.TA);
		this.roleStudent1 = new RolePersonDetailsDTO().person(person4)
				.type(RolePersonDetailsDTO.TypeEnum.STUDENT);
	}

	@Test
	void rolesWorksOnLists() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1);
		Set<RolePersonDetailsDTO.TypeEnum> types = Set.of(RolePersonDetailsDTO.TypeEnum.TEACHER);

		assertThat(roleDTOService.roles(roleDetailsList, types))
				.containsExactlyInAnyOrder(roleTeacher1.getPerson(), roleTeacher2.getPerson());
	}

	@Test
	void rolesWorksWithEmptyLists() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2);
		Set<RolePersonDetailsDTO.TypeEnum> types = Set.of(RolePersonDetailsDTO.TypeEnum.STUDENT);

		assertThat(roleDTOService.roles(roleDetailsList, types)).isEmpty();
	}

	@Test
	void rolesL1WorksOnLists() {
		RolePersonLayer1DTO roleDetails = new RolePersonLayer1DTO().person(person)
				.type(RolePersonLayer1DTO.TypeEnum.STUDENT);
		RolePersonLayer1DTO roleDetails2 = new RolePersonLayer1DTO().person(person2)
				.type(RolePersonLayer1DTO.TypeEnum.TA);
		RolePersonLayer1DTO roleDetails3 = new RolePersonLayer1DTO().person(person3)
				.type(RolePersonLayer1DTO.TypeEnum.STUDENT);

		List<RolePersonLayer1DTO> roleList = Arrays.asList(roleDetails2, roleDetails, roleDetails3);
		Set<RolePersonLayer1DTO.TypeEnum> types = Set.of(RolePersonLayer1DTO.TypeEnum.STUDENT);

		assertThat(roleDTOService.rolesL1(roleList, types)).containsExactlyInAnyOrder(person, person3);
	}

	@Test
	void rolesWorksWithEditionDetailsDTO() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));
		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);
		Set<RolePersonDetailsDTO.TypeEnum> roles = Set.of(RolePersonDetailsDTO.TypeEnum.TEACHER,
				RolePersonDetailsDTO.TypeEnum.STUDENT);

		assertThat(roleDTOService.roles(eDto, roles)).containsExactlyInAnyOrder(roleTeacher1.getPerson(),
				roleTeacher2.getPerson(), roleStudent1.getPerson());
	}

	@Test
	void typeDisplayNameWorks() {
		assertThat(roleDTOService.typeDisplayName("TEACHER")).isEqualTo("Teacher");
		assertThat(roleDTOService.typeDisplayName("random stuff")).isEqualTo("No role");
	}

	@Test
	void canGetRoleNamesFromList() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));

		Set<RolePersonDetailsDTO.TypeEnum> roles = Set.of(RolePersonDetailsDTO.TypeEnum.TEACHER,
				RolePersonDetailsDTO.TypeEnum.STUDENT);
		assertThat(roleDTOService.roleNames(roleDetailsList, roles)).containsExactlyInAnyOrder("person 1",
				"person 3", "person 4");
	}

	@Test
	void canGetRoleNamesFromDTO() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA);

		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));

		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		Set<RolePersonDetailsDTO.TypeEnum> roles = Set.of(RolePersonDetailsDTO.TypeEnum.TEACHER);
		assertThat(roleDTOService.roleNames(eDto, roles)).containsExactlyInAnyOrder("person 1", "person 3");
	}

	@Test
	void canGetAssistantNames() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);

		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));

		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.assistantNames(eDto)).containsExactlyInAnyOrder("person 2", "person 6");
	}

	@Test
	void canGetHeadAssistantNames() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);

		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));

		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.headTANames(eDto)).containsExactlyInAnyOrder("person 5");
	}

	@Test
	void canGetTeacherNames() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);

		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));

		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.teacherNames(eDto)).containsExactlyInAnyOrder("person 1", "person 3");
	}

	@Test
	void studentsFromEditionDetailsWorks() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));
		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.students(eDto)).containsExactlyInAnyOrder(roleStudent1.getPerson());
	}

	@Test
	void teachersFromEditionDetailsWorks() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));
		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.teachers(eDto)).containsExactlyInAnyOrder(roleTeacher2.getPerson(),
				roleTeacher1.getPerson());
	}

	@Test
	void assistantsFromEditionDetailsWorks() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));
		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.assistants(eDto)).containsExactlyInAnyOrder(roleTA1.getPerson(),
				roleTA2.getPerson());
	}

	@Test
	void headTAsFromEditionDetailsWorks() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));
		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.headTAs(eDto)).containsExactlyInAnyOrder(roleHeadTA.getPerson());
	}

	@Test
	void studentsFromRoleDetailsWorks() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));

		assertThat(roleDTOService.students(roleDetailsList))
				.containsExactlyInAnyOrder(roleStudent1.getPerson());
	}

	@Test
	void studentsL1FromRoleDetailsWorks() {
		RolePersonLayer1DTO roleDetails = new RolePersonLayer1DTO().person(person)
				.type(RolePersonLayer1DTO.TypeEnum.STUDENT);
		RolePersonLayer1DTO roleDetails2 = new RolePersonLayer1DTO().person(person2)
				.type(RolePersonLayer1DTO.TypeEnum.TA);
		RolePersonLayer1DTO roleDetails3 = new RolePersonLayer1DTO().person(person3)
				.type(RolePersonLayer1DTO.TypeEnum.STUDENT);

		List<RolePersonLayer1DTO> roleList = Arrays.asList(roleDetails2, roleDetails, roleDetails3);

		assertThat(roleDTOService.studentsL1(roleList)).containsExactlyInAnyOrder(person, person3);
	}

	@Test
	void canGetRolesForPersonInEdition() {
		when(roleControllerApi.getRolesById(any(), any()))
				.thenReturn(Flux.just(mapper.map(roleTA1, RoleDetailsDTO.class)));
		EditionSummaryDTO eDto = new EditionSummaryDTO().id(3L);
		var personObject = new Person(2L, "external", "user", "display", "mail", DefaultRole.STUDENT, 456,
				"programId");

		assertThat(roleDTOService.rolesForPersonInEdition(eDto, personObject))
				.contains(mapper.map(roleTA1, RoleDetailsDTO.class));
	}

	@Test
	void namesConverts() {
		when(roleControllerApi.getRolesById(any(), any()))
				.thenReturn(Flux.just(mapper.map(roleTA1, RoleDetailsDTO.class)));

		var studentSummary = new PersonSummaryDTO().id(3L).displayName("person 4");
		var headTASummary = new PersonSummaryDTO().id(45L).displayName("person 5");

		assertThat(roleDTOService.names(List.of(studentSummary, headTASummary)))
				.contains("person 4", "person 5");
	}

	@Test
	void isStaffWorks() {
		assertThat(roleDTOService.isStaff(mapper.map(roleTeacher1, RoleDetailsDTO.class))).isTrue();
		assertThat(roleDTOService.isStaff(mapper.map(roleHeadTA, RoleDetailsDTO.class))).isTrue();
		assertThat(roleDTOService.isStaff(mapper.map(roleTA1, RoleDetailsDTO.class))).isTrue();
		assertThat(roleDTOService.isStaff(mapper.map(roleTeacherRO, RoleDetailsDTO.class))).isTrue();
	}

	@Test
	void isTeacherWorks() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));
		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.isTeacherInEdition(person.getId(), eDto)).isTrue();
		assertThat(roleDTOService.isTeacherInEdition(person5.getId(), eDto)).isFalse();
	}

	@Test
	void isOnlyTeacherWorks() {
		var roleDetailsList = Arrays.asList(roleTeacher1, roleTA1, roleTeacher2, roleStudent1, roleHeadTA,
				roleTA2);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, roleDetailsList));
		EditionDetailsDTO eDto = new EditionDetailsDTO().id(2L);

		assertThat(roleDTOService.isTheOnlyTeacherInEdition(person.getId(), eDto)).isFalse();
		assertThat(roleDTOService.isTheOnlyTeacherInEdition(person5.getId(), eDto)).isFalse();

		var rolesOneTeacher = Arrays.asList(roleTeacher1, roleTA1, roleStudent1, roleHeadTA, roleTA2);
		when(eCache.getRequired(any()))
				.thenReturn(new EditionRolesCacheManager.RoleHolder(25L, rolesOneTeacher));
		assertThat(roleDTOService.isTheOnlyTeacherInEdition(person.getId(), eDto)).isTrue();

	}

}
