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

import static org.hamcrest.Matchers.emptyIterable;
import static org.hamcrest.Matchers.not;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.modelmapper.ModelMapper;
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.boot.test.mock.mockito.SpyBean;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;

import jakarta.transaction.Transactional;
import nl.tudelft.labracore.api.EditionControllerApi;
import nl.tudelft.labracore.api.StudentGroupControllerApi;
import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.queue.service.RequestTableService;
import reactor.core.publisher.Flux;
import test.TestDatabaseLoader;
import test.test.TestQueueApplication;

@Transactional
@AutoConfigureMockMvc
@SpringBootTest(classes = TestQueueApplication.class)
public class HistoryControllerTest {
	@Autowired
	private MockMvc mvc;

	@Autowired
	private EditionControllerApi eApi;

	@Autowired
	private StudentGroupControllerApi sgApi;

	@SpyBean
	private RequestTableService rts;

	@Autowired
	private TestDatabaseLoader db;

	private EditionDetailsDTO oopNow;

	private PersonSummaryDTO student50;
	private PersonSummaryDTO student55;
	private PersonSummaryDTO student100;

	private StudentGroupDetailsDTO sg55Lab;
	private StudentGroupDetailsDTO sg55Lecture;

	@BeforeEach
	void setup() {
		oopNow = db.getOopNow();

		student50 = db.getStudents()[50];
		student55 = db.getStudents()[55];
		student100 = db.getStudents()[100];

		sg55Lab = db.getOopNowLabGroups()[55];
		sg55Lecture = db.getOopNowLectureGroups()[55];

		when(sgApi.getGroupsForPersonAndCourse(student55.getId(), oopNow.getCourse().getId()))
				.thenReturn(Flux.just(new ModelMapper().map(sg55Lab, StudentGroupSummaryDTO.class),
						new ModelMapper().map(sg55Lecture, StudentGroupSummaryDTO.class)));

		db.mockAll();
	}

	@Test
	@WithUserDetails("student50")
	void getOwnHistoryLoads() throws Exception {
		when(eApi.getAllEditionsCurrentlyAssistedBy()).thenReturn(Flux.just());

		mvc.perform(get("/history"))
				.andExpect(status().isOk())
				.andExpect(view().name("history/index"))
				.andExpect(model().attributeExists("filter", "requests"));
	}

	@Test
	@WithUserDetails("student50")
	void getOwnHistoryStoresFilterUnderHistory() throws Exception {
		when(eApi.getAllEditionsCurrentlyAssistedBy()).thenReturn(Flux.just());

		mvc.perform(get("/history"));

		verify(rts).checkAndStoreFilterDTO(null, "/history");
	}

	@Test
	@WithUserDetails("student50")
	void getOwnHistoryContainsPreviousRequests() throws Exception {
		when(eApi.getAllEditionsCurrentlyAssistedBy()).thenReturn(Flux.just());

		mvc.perform(get("/history"))
				.andExpect(model().attribute("requests", not(emptyIterable())));
	}

	@Test
	@WithUserDetails("student50")
	void getOtherStudentHistoryIsNotAllowed() throws Exception {
		mvc.perform(get("/history/edition/{eId}/student/{sId}", oopNow.getId(), student55.getId()))
				.andExpect(status().isForbidden());
	}

	@Test
	@WithUserDetails("admin")
	void getStudentHistoryIsAllowedForAdmin() throws Exception {
		when(eApi.getAllEditionsCurrentlyAssistedBy()).thenReturn(Flux.just());

		mvc.perform(get("/history/edition/{eId}/student/{sId}", oopNow.getId(), student55.getId()))
				.andExpect(status().isOk())
				.andExpect(view().name("history/student"));
	}

	@Test
	@WithUserDetails("teacher1")
	void getStudentHistoryIsAllowedForTeacherOfCourse() throws Exception {
		when(eApi.getAllEditionsCurrentlyAssistedBy())
				.thenReturn(Flux.just(new ModelMapper().map(oopNow, EditionSummaryDTO.class)));

		mvc.perform(get("/history/edition/{eId}/student/{sId}", oopNow.getId(), student55.getId()))
				.andExpect(status().isOk());
	}

	@Test
	@WithUserDetails("teacher5")
	void getStudentHistoryIsNotAllowedForTeacherOfOtherCourse() throws Exception {
		mvc.perform(get("/history/edition/{eId}/student/{sId}", oopNow.getId(), student55.getId()))
				.andExpect(status().isOk());
	}

	@Test
	@WithUserDetails("admin")
	void getStudentHistoryChecksForFilter() throws Exception {
		when(eApi.getAllEditionsCurrentlyAssistedBy()).thenReturn(Flux.just());

		mvc.perform(get("/history/edition/{eId}/student/{sId}", oopNow.getId(), student55.getId()));

		verify(rts).checkAndStoreFilterDTO(null,
				"/history/edition/" + oopNow.getId() + "/student/" + student55.getId());
	}

	@Test
	@WithUserDetails("admin")
	void getStudentHistoryIsNotEmpty() throws Exception {
		when(eApi.getAllEditionsCurrentlyAssistedBy()).thenReturn(Flux.just());

		mvc.perform(get("/history/edition/{eId}/student/{sId}", oopNow.getId(), student55.getId()))
				.andExpect(model().attribute("requests", not(emptyIterable())));
	}

	@ParameterizedTest
	@MethodSource(value = "protectedEndpoints")
	void testWithoutUserDetailsIsForbidden(MockHttpServletRequestBuilder request) throws Exception {
		mvc.perform(request.with(csrf()))
				.andExpect(status().is3xxRedirection())
				.andExpect(redirectedUrl("http://localhost/login"));
	}

	private static List<MockHttpServletRequestBuilder> protectedEndpoints() {
		return List.of(
				get("/history"),
				get("/history/edition/1/student/1"));
	}

}
