/*
 * 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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
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.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.time.LocalDateTime;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
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.AssignmentControllerApi;
import nl.tudelft.labracore.api.dto.*;
import reactor.core.publisher.Mono;
import test.TestDatabaseLoader;
import test.labracore.AssignmentApiMocker;
import test.labracore.EditionApiMocker;
import test.labracore.ModuleApiMocker;
import test.labracore.RoleApiMocker;
import test.test.TestQueueApplication;

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

	@Autowired
	private MockMvc mvc;

	@Autowired
	private AssignmentControllerApi aApi;

	@Autowired
	private AssignmentApiMocker aApiMocker;

	@Autowired
	private EditionApiMocker eApiMocker;

	@Autowired
	private ModuleApiMocker mApiMocker;

	@Autowired
	private RoleApiMocker rApiMocker;

	@Autowired
	private TestDatabaseLoader db;

	private EditionDetailsDTO edition1;
	private ModuleDetailsDTO module1;
	private AssignmentDetailsDTO assignment1;

	@BeforeEach
	void setUp() {
		aApiMocker.mock();
		eApiMocker.mock();
		mApiMocker.mock();
		rApiMocker.mock();

		edition1 = db.getOopNow();
		module1 = db.getOopNowLabsModule();
		assignment1 = db.getOopNowAssignments()[0];
	}

	@Test
	@WithUserDetails("admin")
	void getAssignmentCreatePageLoads() throws Exception {
		mvc.perform(get("/module/{moduleId}/assignment/create", module1.getId()))
				.andExpect(status().isOk())
				.andExpect(view().name("assignment/create"))
				.andExpect(model().attributeExists("dto", "_module", "edition"));
	}

	@Test
	@WithUserDetails("admin")
	void createAssignmentCreatesANewAssignment() throws Exception {
		when(aApi.addAssignment(any())).thenReturn(Mono.just(666L));

		mvc.perform(post("/module/{moduleId}/assignment/create", module1.getId()).with(csrf())
				.queryParam("name", "Assignment 2")
				.queryParam("description", "This is a cool assignment duh")
				.queryParam("deadline", "2022-09-09T12:00")
				.queryParam("moduleId", "53"))
				.andExpect(status().is3xxRedirection())
				.andExpect(redirectedUrl("/edition/" + edition1.getId() + "/modules"));

		verify(aApi).addAssignment(eq(new AssignmentCreateDTO()
				.name("Assignment 2")
				.description(new Description().text("This is a cool assignment duh"))
				.deadline(LocalDateTime.of(2022, 9, 9, 12, 0))
				.module(new ModuleIdDTO().id(module1.getId()))));
	}

	@Test
	@WithUserDetails("admin")
	void getAssignmentEditPageLoads() throws Exception {
		mvc.perform(get("/assignment/{assignmentId}/edit", assignment1.getId()))
				.andExpect(status().isOk())
				.andExpect(view().name("assignment/edit"))
				.andExpect(model().attributeExists("assignment", "edition"));
	}

	@Test
	@WithUserDetails("admin")
	void editAssignmentMakesChanges() throws Exception {
		when(aApi.patchAssignment(any(), any())).thenReturn(Mono.just(666L));

		mvc.perform(patch("/assignment/{assignmentId}/edit", assignment1.getId()).with(csrf())
						.queryParam("name", "Assignment Two")
						.queryParam("description.text", "This is a cool assignment indeed")
						.queryParam("deadline", "2024-09-09T13:00"))
				.andExpect(status().is3xxRedirection())
				.andExpect(redirectedUrl("/edition/" + edition1.getId() + "/modules"));

		verify(aApi).patchAssignment(eq(new AssignmentPatchDTO()
				.name("Assignment Two")
				.description(new Description().text("This is a cool assignment indeed"))
				.deadline(LocalDateTime.of(2024, 9, 9, 13, 0))
				), eq(assignment1.getId()));
	}

	@Test
	@WithUserDetails("admin")
	void getAssignmentRemovePageWorks() throws Exception {
		mvc.perform(get("/assignment/{assignmentId}/remove", assignment1.getId()))
				.andExpect(status().isOk())
				.andExpect(view().name("assignment/remove"))
				.andExpect(model().attributeExists("edition", "assignment"));
	}

	@Test
	@WithUserDetails("admin")
	void removeAssignmentRedirectsBackToModulesOverview() throws Exception {
		when(aApi.deleteAssignment(assignment1.getId())).thenReturn(Mono.empty());

		mvc.perform(post("/assignment/{assignmentId}/remove", assignment1.getId()).with(csrf()))
				.andExpect(redirectedUrl("/edition/" + edition1.getId() + "/modules"));
	}

	@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("/module/{moduleId}/assignment/create", 1),
				post("/module/{mId}/assignment/create", 1),
				get("/assignment/{assignmentId}/edit", 1),
				patch("/assignment/{assignmentId}/edit", 1),
				get("/assignment/{assignmentId}/remove", 1),
				post("/assignment/{assignmentId}/remove", 1));
	}

}
