/*
 * 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.Objects;
import java.util.Set;

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

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import nl.tudelft.librador.resolver.annotations.PathEntity;
import nl.tudelft.queue.cache.AssignmentCacheManager;
import nl.tudelft.queue.cache.ModuleCacheManager;
import nl.tudelft.queue.dto.view.statistics.AssignmentCountStatisticsViewDto;
import nl.tudelft.queue.dto.view.statistics.BucketStatisticsViewDto;
import nl.tudelft.queue.dto.view.statistics.session.AssistantSessionStatisticsViewDto;
import nl.tudelft.queue.dto.view.statistics.session.GeneralSessionStatisticsViewDto;
import nl.tudelft.queue.model.embeddables.AllowedRequest;
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;
import nl.tudelft.queue.service.RequestService;
import nl.tudelft.queue.service.RoleDTOService;
import nl.tudelft.queue.service.SessionStatusService;

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SessionStatusController {

	private final SessionStatusService sessionStatusService;

	private final RequestService requestService;

	private final EditionStatusService editionStatusService;

	private final RoleDTOService roleDTOService;

	private final AssignmentCacheManager aCache;

	private final ModuleCacheManager mCache;

	/**
	 * Endpoint for general information displayed at the top of the lab statistics page.
	 *
	 * @param  qSession The session to consider
	 * @param  editions The editions to consider (useful for shared sessions)
	 * @return          A DTO containing the aforementioned information
	 */
	@GetMapping("/lab/{qSession}/status/general")
	@PreAuthorize("@permissionService.canManageSession(#qSession)")
	public GeneralSessionStatisticsViewDto generalStatistics(@PathEntity Lab qSession,
			@RequestParam(required = false, defaultValue = "") Set<Long> editions) {

		var requests = requestService.getLabRequestsForEditions(qSession.getRequests(), editions);

		return new GeneralSessionStatisticsViewDto(
				new RequestCountRecord(
						requests.stream().filter(rq -> rq.getRequestType() == RequestType.QUESTION).count(),
						requests.stream().filter(rq -> rq.getRequestType() == RequestType.SUBMISSION)
								.count()),
				(long) qSession.getQueue().size(),
				editionStatusService.averageWaitingTime(requests),
				editionStatusService.averageProcessingTime(requests));
	}

	/**
	 * Gets a DTO containing information about assistants, how many requests they have taken and the time
	 * since their last request interaction.
	 *
	 * @param  qSession The session to consider
	 * @param  editions The editions to consider (useful for shared sessions)
	 * @return          A DTO containing the relevant information
	 */
	@GetMapping("/lab/{qSession}/status/assistant/freq")
	@PreAuthorize("@permissionService.canManageSession(#qSession)")
	public AssistantSessionStatisticsViewDto getRequestFrequency(@PathEntity Lab qSession,
			@RequestParam(required = false, defaultValue = "") Set<Long> editions) {

		Map<Long, String> staffNames = roleDTOService.staffNames(qSession, editions);

		var requests = requestService.getLabRequestsForEditions(qSession.getRequests(), editions);

		return new AssistantSessionStatisticsViewDto(staffNames,
				editionStatusService.countRequestsPerAssistant(requests),
				sessionStatusService.getTimeSinceLastInteraction(qSession, editions));

	}

	/**
	 * Endpoint for session assignment frequent statistic. Retrieves a list of dtos corresponding to each
	 * assignment with submission and question counts.
	 *
	 * @param  qSession The session to be considered
	 * @param  editions The editions to be considered
	 * @return          A list of dtos which contain the aforementioned information
	 */
	@GetMapping("/lab/{qSession}/status/assignment/freq")
	@PreAuthorize("@permissionService.canManageSession(#qSession)")
	@Transactional
	public List<AssignmentCountStatisticsViewDto> getAssignmentFrequency(@PathEntity Lab qSession,
			@RequestParam(required = false, defaultValue = "") Set<Long> editions) {

		var requests = requestService.getLabRequestsForEditions(qSession.getRequests(), editions);

		var assignments = aCache
				.getAndHandle(
						qSession.getAllowedRequests().stream().map(AllowedRequest::getAssignment).distinct(),
						id -> qSession.getAllowedRequests()
								.removeIf(a -> Objects.equals(id, a.getAssignment())))
				.stream()
				.filter(a -> editions.isEmpty() || editions.contains(mCache
						.getRequired(a.getModule().getId(), id -> qSession.getModules().remove(id))
						.getEdition().getId()))
				.toList();

		return editionStatusService.countAssignmentFreqs(requests, assignments);
	}

	/**
	 * Endpoint for session building frequency statistic. Retrieves a list of dtos which corresponds to each
	 * discretized ranges of time, which contains request info.
	 *
	 * @param  qSession The session to be considered
	 * @param  editions The editions to be considered
	 * @return          A list of dtos which contain the aforementioned information
	 */
	@GetMapping("/lab/{qSession}/status/request/distribution")
	@PreAuthorize("@permissionService.canManageSession(#qSession)")
	public List<BucketStatisticsViewDto> requestDistributionOverTime(@PathEntity Lab qSession,
			@RequestParam(required = false, defaultValue = "") Set<Long> editions,
			@RequestParam(required = false, defaultValue = "15") long nMinutes) {
		return sessionStatusService.createRequestDistribution(qSession, editions, nMinutes);

	}

	/**
	 * Endpoint for session building frequency statistic. Retrieves a list of dtos which corresponds to each
	 * discretised ranges of time, which contains building info.
	 *
	 * @param  qSession The session to be considered
	 * @param  editions The editions to be considered
	 * @return          A list of dtos which contain the aforementioned information
	 */
	@GetMapping("/lab/{qSession}/status/building/distribution")
	@PreAuthorize("@permissionService.canManageSession(#qSession)")
	public List<BucketStatisticsViewDto> buildingDistributionOverTime(@PathEntity Lab qSession,
			@RequestParam(required = false, defaultValue = "") Set<Long> editions,
			@RequestParam(required = false, defaultValue = "15") long nMinutes) {

		return sessionStatusService.createBuildingDistribution(qSession, editions, nMinutes);
	}

	/**
	 * Endpoint for session building frequency statistic. Retrieves a list of dtos which corresponds to each
	 * discretised ranges of time, which contains building info.
	 *
	 * @param  qSession The session to be considered
	 * @param  editions The editions to be considered
	 * @return          A list of dtos which contain the aforementioned information
	 */
	@GetMapping("/lab/{qSession}/status/time/distribution")
	@PreAuthorize("@permissionService.canManageSession(#qSession)")
	public List<BucketStatisticsViewDto> timeDistributionOverTime(@PathEntity Lab qSession,
			@RequestParam(required = false, defaultValue = "") Set<Long> editions,
			@RequestParam(required = false, defaultValue = "15") long nMinutes) {

		return sessionStatusService.createWaitProcessingTimeDistribution(qSession, editions, nMinutes);
	}

}
