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

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import nl.tudelft.labracore.api.dto.AssignmentDetailsDTO;
import nl.tudelft.labracore.api.dto.SessionDetailsDTO;
import nl.tudelft.queue.cache.EditionCacheManager;
import nl.tudelft.queue.dto.view.statistics.session.AssignmentSessionCountViewDto;
import nl.tudelft.queue.dto.view.statistics.session.RequestDistributionBucketViewDto;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.model.RequestEvent;
import nl.tudelft.queue.model.enums.RequestType;
import nl.tudelft.queue.model.events.EventWithAssistant;
import nl.tudelft.queue.model.labs.Lab;

@Service
@RequiredArgsConstructor
public class SessionStatusService {

	private final RequestService requestService;

	private final EditionCacheManager eCache;

	private final SessionService sessionService;

	/**
	 * Gets the time in milliseconds since the last interaction for all assistants who have <b>ONLY
	 * ALREADY</b> interacted with something in the lab.
	 *
	 * @param  qs       The session to consider
	 * @param  editions The editions to consider
	 * @return          A mapping of (already participating) assistant IDs to the time in milliseconds since
	 *                  their last request interaction.
	 */
	public Map<Long, Long> getTimeSinceLastInteraction(Lab qs, Set<Long> editions) {

		return requestService.getLabRequestsForEditions(qs.getRequests(), editions)
				.stream()
				.flatMap(request -> request.getEventInfo().getEvents().stream())
				.filter(event -> event instanceof EventWithAssistant)
				.map(event -> (EventWithAssistant) event)
				.collect(Collectors.toMap(
						EventWithAssistant::getAssistant,
						event -> ((RequestEvent<?>) event).getTimestamp().until(LocalDateTime.now(),
								ChronoUnit.MILLIS),
						Long::min));

	}

	/**
	 * Maps the assignments and counts the question and submission frequency of each assignments among a
	 * collection of requests.
	 *
	 * @param  requests    The requests to consider
	 * @param  assignments The assignments you want to count the question and submission frequencies of
	 * @return             A list of DTOs which capture this information for each assignment
	 */
	public List<AssignmentSessionCountViewDto> countAssignmentFreqs(List<LabRequest> requests,
			List<AssignmentDetailsDTO> assignments) {
		return assignments.stream()
				.sorted(Comparator.comparing(AssignmentDetailsDTO::getId))
				.map(assignment -> {
					var assignmentName = assignment.getName();
					var assignmentQuestionCount = requests.stream()
							.filter(rq -> Objects.equals(rq.getAssignment(), assignment.getId())
									&& rq.getRequestType() == RequestType.QUESTION)
							.count();
					var assignmentSubmissionCount = requests.stream()
							.filter(rq -> Objects.equals(rq.getAssignment(), assignment.getId())
									&& rq.getRequestType() == RequestType.SUBMISSION)
							.count();
					return new AssignmentSessionCountViewDto(assignment.getId(), assignmentName,
							assignmentQuestionCount, assignmentSubmissionCount);
				}).toList();
	}

	/**
	 * Method responsible for creating a list of dtos where each one represents a bucket containing
	 * information about how many requests occured within that timespan. It also segregates between the
	 * courses they took place in.
	 *
	 * @param  qSession            The lab to consider
	 * @param  editions            The editions that should be filtered for
	 * @param  bucketSizeInMinutes The number of minutes within a bucket.
	 * @return                     A list of dtos, each one representing a bucket
	 */
	public List<RequestDistributionBucketViewDto> createRequestDistribution(Lab qSession, Set<Long> editions,
			long bucketSizeInMinutes) {

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

		if (requests.isEmpty() || bucketSizeInMinutes <= 0L) {
			return new ArrayList<>();
		}

		DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("HH:mm");
		SessionDetailsDTO sessionDTO = sessionService.getSessionDTOFromSession(qSession);
		LocalDateTime minTime = sessionDTO.getStart();
		LocalDateTime maxTime = sessionDTO.getEndTime().isAfter(now) ? now
				: sessionDTO.getEndTime();

		if (minTime.isAfter(maxTime)) {
			return new ArrayList<>(); // this occurs when lab statistics are viewed before the session starts.
		}

		return Stream.iterate(minTime,
				time -> time.isEqual(minTime) || !time.plusMinutes(bucketSizeInMinutes).isAfter(maxTime),
				time -> time.plusMinutes(bucketSizeInMinutes)).map(bucketStart -> {
					LocalDateTime tempEnd = bucketStart.plusMinutes(bucketSizeInMinutes);
					LocalDateTime bucketEnd = tempEnd.isAfter(maxTime) ? maxTime : tempEnd;
					Map<String, Long> requestsPerBucket = requests.stream()
							.filter(r -> (r.getCreatedAt().isEqual(bucketStart)
									|| r.getCreatedAt().isAfter(bucketStart))
									&& (r.getCreatedAt().isBefore(bucketEnd)))
							.collect(Collectors.toMap(
									rq -> eCache
											.getRequired(requestService.getEditionForLabRequest(rq).getId())
											.getCourse().getName(),
									rq -> 1L,
									Long::sum));
					String bucketLabel = bucketStart.format(dateFormat) +
							" - " +
							bucketEnd.format(dateFormat);
					return new RequestDistributionBucketViewDto(requestsPerBucket, bucketLabel);
				}).toList();
	}

}
