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

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.validation.Valid;

import nl.tudelft.labracore.api.CourseControllerApi;
import nl.tudelft.labracore.api.EditionControllerApi;
import nl.tudelft.labracore.api.ProgramControllerApi;
import nl.tudelft.labracore.api.dto.*;
import nl.tudelft.labracore.api.dto.CourseCreateDTO;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.librador.dto.view.View;
import nl.tudelft.queue.cache.EditionCacheManager;
import nl.tudelft.queue.cache.RoomCacheManager;
import nl.tudelft.queue.cache.SessionCacheManager;
import nl.tudelft.queue.dto.create.QueueCourseCreateDTO;
import nl.tudelft.queue.dto.view.QueueSessionSummaryDTO;
import nl.tudelft.queue.model.labs.Lab;
import nl.tudelft.queue.repository.LabRepository;
import nl.tudelft.queue.service.AdminService;
import nl.tudelft.queue.service.EditionService;
import nl.tudelft.queue.service.LabService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

@Controller
@PreAuthorize("@permissionService.isAdmin()")
@RequestMapping("/admin")
public class AdminController {

	@Autowired
	private CourseControllerApi cApi;

	@Autowired
	private ProgramControllerApi programApi;

	@Autowired
	private EditionControllerApi eApi;

	@Autowired
	private EditionCacheManager eCache;
	@Autowired
	private EditionService es;
	@Autowired
	private LabRepository lr;

	@Autowired
	@Lazy
	private LabService ls;

	@Autowired
	private SessionCacheManager sCache;

	@Autowired
	private AdminService adminService;

	@Autowired
	private RoomCacheManager rCache;

	/**
	 * Adds a model attribute to every template resolution for recognizing the type of page we are in. For
	 * this controller all pages will be in the "admin"-area.
	 *
	 * @return The generic page name: "admin".
	 */
	@ModelAttribute("page")
	public static String page() {
		return "admin";
	}

	/**
	 * Return the main admin page.
	 *
	 * @return The Thymeleaf view to resolve.
	 */
	@GetMapping
	public String getAdminPage(Model model) {
		return "redirect:/admin/running";
	}

	/**
	 * Get a calendar view with all the labs.
	 *
	 * @param  model The model to fill out for Thymeleaf template resolution.
	 * @return       The Thymeleaf template to resolve.
	 */
	@GetMapping("/calendar")
	public String getAdminCoursesPage(Model model) {
		List<Lab> labs = ls.getAllLabsWithinPeriod(LocalDateTime.now().minusWeeks(1),
				LocalDateTime.now().plusMonths(1));
		model.addAttribute("sessions", ls.convertToCalendarEntries(labs));
		return "admin/view/calendar";
	}

	@GetMapping("/running")
	public String getRunningLabs(Model model) {
		LocalDateTime now = LocalDateTime.now();
		var runningEditions = eCache.getAndIgnoreMissing(Objects.requireNonNull(
				eApi.getAllEditionsActiveAtDate(LocalDateTime.now())
						.map(EditionSummaryDTO::getId).collectList().block()));

		var runningSessions = sCache.getAndIgnoreMissing(runningEditions.stream()
				.flatMap(e -> e.getSessions().stream())
				.filter(s -> s.getEndTime().isAfter(now))
				.map(SessionSummaryDTO::getId));
		var runningLabs = es.sortLabs(lr
				.findAllBySessions(runningSessions.stream().map(SessionDetailsDTO::getId)
						.collect(Collectors.toList()))
				.stream().map(l -> View.convert(l, QueueSessionSummaryDTO.class))
				.filter(qs -> qs.getSlot().today())
				.sorted(Comparator.comparing(qs -> qs.getSlot().getClosesAt()))
				.collect(Collectors.toList()));
		model.addAttribute("runningLabs", runningLabs);

		List<Lab> labs = ls.getAllLabsWithinPeriod(LocalDateTime.now().minusDays(1),
				LocalDateTime.now().plusDays(1));
		long labCount = ls.countOngoingLabs(labs);
		if (labCount > 0) {
			model.addAttribute("labsOngoing", String.format("Currently %d lab(s) " +
					"ongoing", labCount));
		}
		long slotCount = ls.countLabsWithOpenSlotSelection(labs);
		if (slotCount > 0) {
			model.addAttribute("slotSelectionOpen", String.format("Currently %d lab(s) with slot " +
					"selection open", slotCount));
		}
		return "admin/view/running";
	}

	/**
	 * Get the list of courses in Queue.
	 *
	 * @param  model The model to fill out for Thymeleaf template resolution.
	 * @return       The thymeleaf template to resolve.
	 */
	@GetMapping("/courses")
	public String getAllCourses(Model model) {
		var courses = cApi.getAllCourses().collectList().block();
		model.addAttribute("courses", courses);
		return "admin/view/courseList";
	}

	/**
	 * Get the admin overview for a specific course.
	 *
	 * @param  courseId The id of the course to retrieve.
	 * @param  model    The model to fill out for Thymeleaf template resolution.
	 * @return          The thymeleaf template to resolve.
	 */
	@GetMapping("/course/{courseId}")
	public String viewCourse(@PathVariable Long courseId, Model model) {
		var course = cApi.getCourseById(courseId).block();
		model.addAttribute("course", course);
		return "admin/view/course";
	}

	/**
	 * @param  model The model to fill out for Thymeleaf template resolution.
	 * @return       The thymeleaf template to resolve.
	 */
	@GetMapping("/course/add")
	public String getCreateCoursePage(@AuthenticatedPerson Person person,
			@RequestParam(name = "name", required = false) String name,
			@RequestParam(name = "code", required = false) String code,
			@RequestParam(name = "manager", required = false) String managerUsername,
			Model model) {
		QueueCourseCreateDTO create = QueueCourseCreateDTO.builder()
				.name(name).code(code).courseManager(managerUsername).build();

		model.addAttribute("course", create);
		model.addAttribute("programs",
				programApi.getAllRelevantPrograms(person.getId()).collectList().block());

		return "admin/create/course";
	}

	/**
	 * Creates a new course based on the infromation filled in in the {@link QueueCourseCreateDTO} object.
	 *
	 * @param  create The DTO used to create the course
	 * @return        A redirect to the admin page
	 */
	@PostMapping("/course/add")
	public String createCourse(@Valid @ModelAttribute("course") QueueCourseCreateDTO create) {
		CourseCreateDTO course = create.apply();
		cApi.addCourse(course).block();

		return "redirect:/admin";
	}

	/**
	 * Gets a list of all currently available rooms.
	 *
	 * @param  model The model to fill out for Thymeleaf template resolution.
	 * @return       The thymeleaf template to resolve.
	 */
	@GetMapping("/rooms")
	public String getAllRooms(Model model) {
		model.addAttribute("rooms", rCache.getAll());
		return "admin/view/rooms";
	}

	/**
	 * Saves a room image for a specific room
	 *
	 * @param  id          The id of the room for which to save the image
	 * @param  map         The file to be saved for this rooom
	 * @return             A redirect to the admin page
	 * @throws IOException
	 */
	@PostMapping("/room/{id}")
	public String saveRoomImage(@PathVariable("id") Long id,
			@RequestParam("map") MultipartFile map) throws IOException {
		var extension = map.getOriginalFilename().split("\\.")[map.getOriginalFilename().split("\\.").length
				- 1];
		String fileName = "room-" + id + "." + extension;
		adminService.uploadFile(map, fileName);
		return "redirect:/admin";
	}

	/**
	 * Gets the number of ongoing sessions for the admin user to see.
	 *
	 * @param  sessions The sessions to find ongoing sessions in.
	 * @return          The number of sessions currently ongoing.
	 */
	private long countOngoingSessions(List<SessionSummaryDTO> sessions) {
		LocalDateTime now = LocalDateTime.now();
		return sessions.stream().filter(s -> s.getStart().isBefore(now) && s.getEndTime().isAfter(now))
				.count();
	}

}
