/*
 * 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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;
import nl.tudelft.labracore.api.AssignmentControllerApi;
import nl.tudelft.labracore.api.StudentGroupControllerApi;
import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.librador.dto.DTOConverter;
import nl.tudelft.queue.cache.AssignmentCacheManager;
import nl.tudelft.queue.cache.EditionCacheManager;
import nl.tudelft.queue.cache.ModuleCacheManager;
import nl.tudelft.queue.dto.create.QueueAssignmentCreateDTO;
import nl.tudelft.queue.dto.patch.QueueAssignmentPatchDTO;

@Controller
public class AssignmentController {

	@Autowired
	private AssignmentCacheManager aCache;

	@Autowired
	private ModuleCacheManager mCache;

	@Autowired
	private EditionCacheManager eCache;

	@Autowired
	private AssignmentControllerApi aApi;

	@Autowired
	private StudentGroupControllerApi sgApi;

	@Autowired
	private DTOConverter converter;

	/**
	 * Gets the page for creating an assignment. This is a basic page with only the most basic of properties
	 * for an assignment. One can edit the basic properties in Queue or all properties in Portal.
	 *
	 * @param  moduleId The id of the module to add the assignment to.
	 * @param  model    The model to fill out for Thymeleaf template resolution.
	 * @return          The Thymeleaf template to resolve.
	 */
	@GetMapping("/module/{moduleId}/assignment/create")
	@PreAuthorize("@permissionService.canManageModule(#moduleId)")
	public String getAssignmentCreatePage(@PathVariable Long moduleId,
			Model model) {
		return addCreateAssignmentAttributes(moduleId, new QueueAssignmentCreateDTO(moduleId), model);
	}

	/**
	 * Handles a Post request to add a new assignment. The assignment is passed through a
	 * {@link QueueAssignmentCreateDTO}. After adding the assignment successfully, the user is redirected back
	 * to the edition/modules page. If something goes wrong during conversion, the user is instead directed
	 * back to the assignment create page.
	 *
	 * @param  mId   The id of the module to add the assignment to.
	 * @param  dto   The dto containing information for creating the assignment.
	 * @param  model The model to fill out for Thymeleaf template resolution.
	 * @return       A redirect back to the edition/modules page.
	 */
	@PostMapping("/module/{mId}/assignment/create")
	@PreAuthorize("@permissionService.canManageModule(#mId)")
	public String createAssignment(@PathVariable Long mId,
			QueueAssignmentCreateDTO dto,
			Model model) {

		ModuleDetailsDTO module = mCache.getRequired(mId);

		dto.setModuleId(mId);

		AssignmentCreateDTO create = dto.apply(converter);
		if (dto.hasErrors()) {
			return addCreateAssignmentAttributes(mId, dto, model);
		}

		aApi.addAssignment(create).block();

		return "redirect:/edition/" + module.getEdition().getId() + "/modules";
	}

	/**
	 * Gets the page for editing an assignment. This page only allows to edit the basic properties of an
	 * assignment. Editing all properties is possible in Portal.
	 *
	 * @param  assignmentId The id of an assignment to be edited.
	 * @param  model        The model to fill out for Thymeleaf template resolution.
	 * @return              The Thymeleaf template to resolve.
	 */
	@GetMapping("/assignment/{assignmentId}/edit")
	@PreAuthorize("@permissionService.canManageAssignment(#assignmentId)")
	public String getAssignmentEditPage(@PathVariable Long assignmentId,
			Model model) {
		var assignment = aCache.getRequired(assignmentId);
		var module = mCache.getRequired(assignment.getModule().getId());
		var edition = eCache.getRequired(module.getEdition().getId());

		model.addAttribute("assignment", assignment);
		model.addAttribute("edition", edition);

		return "assignment/edit";
	}

	/**
	 * Handles a Patch request to edit an assignment. The assignment is passed through a
	 * {@link QueueAssignmentPatchDTO}. After editing the assignment successfully, the user is redirected back
	 * to the edition/modules page.
	 *
	 * @param  assignmentId The id of the assignment to edit.
	 * @param  dto          The dto containing information for editing the assignment.
	 * @return              A redirect back to the edition/modules page.
	 */
	@PatchMapping("/assignment/{assignmentId}/edit")
	@PreAuthorize("@permissionService.canManageAssignment(#assignmentId)")
	public String editAssignment(@PathVariable Long assignmentId,
			@Valid QueueAssignmentPatchDTO dto) {
		aApi.patchAssignment(converter.getModelMapper().map(dto, AssignmentPatchDTO.class), assignmentId)
				.block();

		AssignmentDetailsDTO assignment = aCache.getRequired(assignmentId);
		ModuleDetailsDTO module = mCache.getRequired(assignment.getModule().getId());
		return "redirect:/edition/" + module.getEdition().getId() + "/modules";
	}

	/**
	 * Gets the assignment removal page. This page is simply to confirm whether the user really wants to
	 * delete the assignment with the given id.
	 *
	 * @param  assignmentId The id of the assignment that the user might want to delete.
	 * @param  model        The model to fill out for Thymeleaf template resolution.
	 * @return              The Thymeleaf template to resolve.
	 */
	@GetMapping("/assignment/{assignmentId}/remove")
	@PreAuthorize("@permissionService.canManageAssignment(#assignmentId)")
	public String getAssignmentRemovePage(@PathVariable Long assignmentId,
			Model model) {
		var assignment = aCache.getRequired(assignmentId);
		var module = mCache.getRequired(assignment.getModule().getId());
		var edition = eCache.getRequired(module.getEdition().getId());

		model.addAttribute("edition", edition);
		model.addAttribute("assignments",
				mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
						.stream()
						.flatMap(m -> m.getAssignments().stream()).toList());
		model.addAttribute("assignment", assignment);

		return "assignment/remove";
	}

	/**
	 * Deletes the assignment with the given id and redirects back to the modules page for the relevant
	 * edition.
	 *
	 * @param  assignmentId The id of the assignment to delete.
	 * @return              A redirect back to the edition/modules page.
	 */
	@PostMapping("/assignment/{assignmentId}/remove")
	@PreAuthorize("@permissionService.canManageAssignment(#assignmentId)")
	public String removeAssignment(@PathVariable Long assignmentId) {
		var assignment = aCache.getRequired(assignmentId);
		var module = mCache.getRequired(assignment.getModule().getId());

		aApi.deleteAssignment(assignmentId).block();

		return "redirect:/edition/" + module.getEdition().getId() + "/modules";
	}

	/**
	 * Get the page with a list of groups to join for an assignment.
	 *
	 * @param  assignmentId The id of the assignment
	 * @return              The group page
	 */
	@GetMapping("/assignment/{assignmentId}/groups")
	public String getAssignmentGroups(@PathVariable Long assignmentId, Model model) {
		AssignmentDetailsDTO assignment = aCache.getRequired(assignmentId);
		ModuleDetailsDTO module = mCache.getRequired(assignment.getModule().getId());

		model.addAttribute("module", module);
		model.addAttribute("edition", eCache.getRequired(module.getEdition().getId()));
		model.addAttribute("groups", sgApi.getAllGroupsInModule(module.getId()).collectList().block());

		return "module/groups";
	}

	/**
	 * Adds attributes for the create assignment page to the given model.
	 *
	 * @param  moduleId The id of the module the assignment will be a part of.
	 * @param  dto      The DTO to add to the model for the page form to fill out.
	 * @param  model    The model to fill with attributes for the create-assignment page.
	 * @return          The template to load for the create assignments page.
	 */
	private String addCreateAssignmentAttributes(Long moduleId, QueueAssignmentCreateDTO dto,
			Model model) {
		ModuleDetailsDTO module = mCache.getRequired(moduleId);
		EditionDetailsDTO edition = eCache.getRequired(module.getEdition().getId());

		model.addAttribute("edition", edition);
		model.addAttribute("assignments",
				mCache.getAndIgnoreMissing(edition.getModules().stream().map(ModuleSummaryDTO::getId))
						.stream()
						.flatMap(m -> m.getAssignments().stream()).toList());
		model.addAttribute("_module", module);

		model.addAttribute("dto", dto);

		return "assignment/create";
	}

}
