/*
 * 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 java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import nl.tudelft.queue.cache.AssignmentCacheManager;
import nl.tudelft.queue.dto.view.statistics.AssignmentCountStatisticsViewDto;
import nl.tudelft.queue.dto.view.statistics.LabStatisticsViewDto;
import nl.tudelft.queue.dto.view.statistics.RequestFrequencyViewDto;
import nl.tudelft.queue.dto.view.statistics.RequestStatusViewDto;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.model.enums.RequestStatus;
import nl.tudelft.queue.model.enums.RequestType;
import nl.tudelft.queue.model.labs.Lab;
import nl.tudelft.queue.model.records.RequestCountRecord;
import nl.tudelft.queue.service.EditionStatusService;

@Validated
@RestController
public class EditionStatusController {

	@Autowired
	private EditionStatusService ess;

	@Autowired
	private AssignmentCacheManager aCache;

	/**
	 * Creates and returns the data for a histogram regarding the status of requests over time. The data for
	 * requests can be filtered on labs, assignments and rooms to only selectively count.
	 *
	 * @param  editionId   The id of the course to find status on.
	 * @param  labs        The labs to filter on.
	 * @param  assignments The assignments to filter on.
	 * @param  rooms       The rooms to filter on.
	 * @param  type        The request types to filter on.
	 * @param  nBuckets    The number of buckets to create. 4 by default (for 4 hours of lab).
	 * @return             A RequestFrequencyViewDto object representing the buckets into which the lab was
	 *                     divided and the number of requests created, open, and handled during each of the
	 *                     created time frames.
	 */
	@GetMapping("/edition/{editionId}/status/freq/request")
	@PreAuthorize("@permissionService.canViewEditionStatus(#editionId)")
	public RequestFrequencyViewDto getRequestFrequencies(@PathVariable Long editionId,
			@RequestParam(required = false, defaultValue = "") List<Lab> labs,
			@RequestParam(required = false, defaultValue = "") List<Long> assignments,
			@RequestParam(required = false, defaultValue = "") List<Long> rooms,
			@RequestParam(required = false, defaultValue = "") List<RequestType> type,
			@RequestParam(required = false, defaultValue = "5") int nBuckets) {
		List<LabRequest> requests = ess.getFilteredRequests(labs, assignments, rooms, type);
		TreeSet<Long> buckets = ess.createBucketsOverCourse(labs, nBuckets);

		return new RequestFrequencyViewDto(
				buckets.stream().sorted().collect(Collectors.toList()),
				ess.countCreatedRequestsInBuckets(buckets, requests),
				ess.countOpenRequestsInBuckets(buckets, requests),
				ess.countHandledRequestsInBuckets(buckets, requests));
	}

	/**
	 * Creates and returns the data for a histogram regarding the count of requests statuses. The data for
	 * requests can be filtered on labs, assignments and rooms to only selectively count.
	 *
	 * @param  editionId   The id of the edition to find status on.
	 * @param  labs        The labs to filter on.
	 * @param  assignments The assignments to filter on.
	 * @param  rooms       The rooms to filter on.
	 * @param  type        The request types to filter on.
	 * @return             The data in the form of a StatusViewDto.
	 */
	@GetMapping("/edition/{editionId}/status/freq/status")
	@PreAuthorize("@permissionService.canViewEditionStatus(#editionId)")
	public RequestStatusViewDto getRequestStatusFrequencies(@PathVariable Long editionId,
			@RequestParam(required = false, defaultValue = "") List<Lab> labs,
			@RequestParam(required = false, defaultValue = "") List<Long> assignments,
			@RequestParam(required = false, defaultValue = "") List<Long> rooms,
			@RequestParam(required = false, defaultValue = "") List<RequestType> type) {
		List<LabRequest> requests = ess.getFilteredRequests(labs, assignments, rooms, type);

		return new RequestStatusViewDto(
				ess.countWhere(requests, r -> r.getEventInfo().getStatus().isPending()),
				ess.countWhere(requests, r -> r.getEventInfo().getStatus().isProcessing()),
				ess.countWhere(requests, r -> r.getEventInfo().getStatus() == RequestStatus.APPROVED),
				ess.countWhere(requests, r -> r.getEventInfo().getStatus() == RequestStatus.REJECTED),
				ess.countWhere(requests, r -> r.getEventInfo().getStatus() == RequestStatus.NOTFOUND),
				ess.countWhere(requests, r -> r.getEventInfo().getStatus() == RequestStatus.REVOKED));
	}

	/**
	 * Gets generic and simple information about the currently filtered labs, assignments and rooms. This
	 * information includes the number of students/assistants participating in the labs, the number of
	 * open/handled requests, the most popular assignment and room, and the average waiting time and
	 * processing time.
	 *
	 * @param  editionId   The id of the edition to find status on.
	 * @param  labs        The labs to filter on.
	 * @param  assignments The assignments to filter on.
	 * @param  rooms       The rooms to filter on.
	 * @param  type        The request types to filter on.
	 * @return             The data in the form of a LabStatisticsViewDto.
	 */
	@GetMapping("/edition/{editionId}/status/lab/info")
	@PreAuthorize("@permissionService.canViewEditionStatus(#editionId)")
	public LabStatisticsViewDto getGenericLabInformation(@PathVariable Long editionId,
			@RequestParam(required = false, defaultValue = "") List<Lab> labs,
			@RequestParam(required = false, defaultValue = "") List<Long> assignments,
			@RequestParam(required = false, defaultValue = "") List<Long> rooms,
			@RequestParam(required = false, defaultValue = "") List<RequestType> type) {
		List<LabRequest> requests = ess.getFilteredRequests(labs, assignments, rooms, type);

		return new LabStatisticsViewDto(
				ess.countDistinctUsers(requests),
				ess.countDistinctAssistants(requests),
				ess.countWhere(requests, r -> r.getEventInfo().getStatus().isPending()),
				ess.countWhere(requests, r -> r.getEventInfo().getStatus().isHandled()),
				ess.countAssignmentFreqs(requests, aCache.getAndIgnoreMissing(assignments)).stream()
						.reduce((a,
								b) -> a.getRequestCounts().questionCount() + a.getRequestCounts()
										.submissionCount() < b.getRequestCounts().questionCount()
												+ b.getRequestCounts().submissionCount() ? b : a)
						.map(AssignmentCountStatisticsViewDto::getAssignmentName)
						.orElse("N/A"), // order preserving in case of tie
				ess.mostCountedName(ess.countRequestsPerRoom(rooms, requests)),
				ess.averageWaitingTime(requests),
				ess.averageProcessingTime(requests));
	}

	/**
	 * Gets a mapping of the filtered assignments to the number of occurrences of those assignments in the
	 * list of requests filtered by labs and rooms.
	 *
	 * @param  editionId   The id of the edition to find status on.
	 * @param  labs        The labs to filter on.
	 * @param  assignments The assignments to filter on.
	 * @param  rooms       The rooms to filter on.
	 * @param  type        The request types to filter on.
	 * @return             The names of assignments mapped to the number of times that assignment occurs.
	 */
	@GetMapping("/edition/{editionId}/status/freq/assignment")
	@PreAuthorize("@permissionService.canViewEditionStatus(#editionId)")
	public List<AssignmentCountStatisticsViewDto> getAssignmentCounts(@PathVariable Long editionId,
			@RequestParam(required = false, defaultValue = "") List<Lab> labs,
			@RequestParam(required = false, defaultValue = "") List<Long> assignments,
			@RequestParam(required = false, defaultValue = "") List<Long> rooms,
			@RequestParam(required = false, defaultValue = "") List<RequestType> type) {
		List<LabRequest> requests = ess.getFilteredRequests(labs, assignments, rooms, type);

		return ess.countAssignmentFreqs(requests, aCache.getAndIgnoreMissing(assignments));

	}

	/**
	 * Gets a mapping of the filtered rooms to the number of occurrences of those rooms in the list of
	 * requests filtered by labs and assignments.
	 *
	 * @param  editionId   The id of the edition to find status on.
	 * @param  labs        The labs to filter on.
	 * @param  assignments The assignments to filter on.
	 * @param  rooms       The rooms to filter on.
	 * @param  type        The request types to filter on.
	 * @return             The names of assignments mapped to the number of times that room occurs.
	 */
	@GetMapping("/edition/{editionId}/status/freq/room")
	@PreAuthorize("@permissionService.canViewEditionStatus(#editionId)")
	public Map<String, Long> getRoomCounts(@PathVariable Long editionId,
			@RequestParam(required = false, defaultValue = "") List<Lab> labs,
			@RequestParam(required = false, defaultValue = "") List<Long> assignments,
			@RequestParam(required = false, defaultValue = "") List<Long> rooms,
			@RequestParam(required = false, defaultValue = "") List<RequestType> type) {
		List<LabRequest> requests = ess.getFilteredRequests(labs, assignments, rooms, type);

		return ess.countRequestsPerRoom(rooms, requests);
	}

	/**
	 * Gets a mapping of labs to the number of questions and submission requests there were in those labs
	 * considering the provided filters.
	 *
	 * @param  editionId   The edition to consider
	 * @param  labs        The labs to consider
	 * @param  assignments The assignments to consider
	 * @param  rooms       The rooms to consider
	 * @param  type        The types of requests to consider
	 * @return             The mapping as described above
	 */
	@GetMapping("/edition/{editionId}/status/lab/freq")
	@PreAuthorize("@permissionService.canViewEditionStatus(#editionId)")
	public Map<String, RequestCountRecord> getRequestCounts(@PathVariable Long editionId,
			@RequestParam(required = false, defaultValue = "") List<Lab> labs,
			@RequestParam(required = false, defaultValue = "") List<Long> assignments,
			@RequestParam(required = false, defaultValue = "") List<Long> rooms,
			@RequestParam(required = false, defaultValue = "") List<RequestType> type) {
		List<LabRequest> requests = ess.getFilteredRequests(labs, assignments, rooms, type);

		return ess.countRequestsPerLab(requests);
	}

	/**
	 * Gets a mapping of labs to the average wait and processing times there were in those labs considering
	 * the provided filters.
	 *
	 * @param  editionId   The edition to consider
	 * @param  labs        The labs to consider
	 * @param  assignments The assignments to consider
	 * @param  rooms       The rooms to consider
	 * @param  type        The request types to consider
	 * @return             A map containing the mapping as described above to be used in the statistic.
	 */
	@GetMapping("/edition/{editionId}/status/lab/time")
	@PreAuthorize("@permissionService.canViewEditionStatus(#editionId)")
	public Map<String, LabStatisticsViewDto> getWaitProcessingTime(@PathVariable Long editionId,
			@RequestParam(required = false, defaultValue = "") List<Lab> labs,
			@RequestParam(required = false, defaultValue = "") List<Long> assignments,
			@RequestParam(required = false, defaultValue = "") List<Long> rooms,
			@RequestParam(required = false, defaultValue = "") List<RequestType> type) {
		List<LabRequest> requests = ess.getFilteredRequests(labs, assignments, rooms, type);

		return ess.countWaitProcessingTimePerLab(requests);
	}

}
