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

import static java.time.LocalDateTime.now;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.transaction.Transactional;
import javax.validation.ValidationException;

import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
import nl.tudelft.labracore.api.dto.SessionDetailsDTO;
import nl.tudelft.queue.PageUtil;
import nl.tudelft.queue.cache.PersonCacheManager;
import nl.tudelft.queue.cache.SessionCacheManager;
import nl.tudelft.queue.dto.patch.FeedbackPatchDTO;
import nl.tudelft.queue.model.Feedback;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.repository.FeedbackRepository;
import nl.tudelft.queue.repository.LabRequestRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
public class FeedbackService {

	@Autowired
	private PersonCacheManager pCache;

	@Autowired
	private FeedbackRepository fr;

	@Autowired
	private LabRequestRepository rr;

	@Autowired
	private PermissionService ps;

	@Autowired
	@Lazy
	private LabService ls;

	@Autowired
	private SessionCacheManager sessionCacheManager;

	/**
	 * Finds all assistant that are involved in the given request. This list of people is generated for
	 * students to be able to give feedback on the one TA they want to give feedback on. This list is ordered
	 * by event occurrence: the last occurred event will see its assistant at the top of the list.
	 *
	 * @param  requestId The id of the request to find all assistants for.
	 * @return           A list of person summaries representing the various assistants helping out with the
	 *                   given request.
	 */
	public List<PersonSummaryDTO> assistantsInvolvedInRequest(Long requestId) {
		return pCache.getAndIgnoreMissing(rr.findById(requestId).stream()
				.flatMap(request -> request.getEventInfo().involvedAssistants().stream()));
	}

	/**
	 * Counts the occurrences of each of the five star ratings and returns the counts of ratings for the
	 * assistant with the given id.
	 *
	 * @param  assistantId The id of the assistant for which to count ratings.
	 * @return             The counts of feedbacks with each of the five ratings.
	 */
	public List<Integer> countRatings(Long assistantId) {
		List<Integer> counts = new ArrayList<>(List.of(0, 0, 0, 0, 0));

		for (var feedback : fr.findAllWithRatingByAssistant(assistantId)) {
			counts.set(feedback.getRating() - 1, counts.get(feedback.getRating() - 1) + 1);
		}

		return counts;
	}

	/**
	 * Updates feedback by the given request id and assistant id, but only if a feedback is already in the
	 * database by these ids. If none is found, a new feedback object is saved to the database and the given
	 * rating and feedback string are filled in in it.
	 *
	 * @param request     The request the feedback is on.
	 * @param assistantId The id of the assistant the feedback is on.
	 * @param dto         The transferred Patch DTO representing the feedback.
	 */
	@Transactional
	public void updateFeedback(LabRequest request, Long assistantId, FeedbackPatchDTO dto) {
		fr.findById(request.getId(), assistantId)
				.map(dto::apply)
				.orElseGet(() -> {
					if ((dto.getFeedback() == null || dto.getFeedback().isEmpty())
							&& dto.getRating() == null) {
						throw new ValidationException("Cannot have empty feedback and empty rating.");
					}

					return fr.save(Feedback.builder()
							.id(new Feedback.Id(request.getId(), assistantId))
							.request(request)
							.feedback(dto.getFeedback())
							.rating(dto.getRating())
							.createdAt(now())
							.lastUpdatedAt(now())
							.build());
				});
	}

	/**
	 * Filters feedback for a manager, such that they can only see feedback for TAs in courses that they
	 * managed.
	 *
	 * @param  feedback The list of feedback to be filtered.
	 * @param  pageable The pageable used for paging.
	 * @return          A Page of feedback that the manager can see.
	 */
	public Page<Feedback> filterFeedbackForManagerCourses(List<Feedback> feedback, Pageable pageable) {
		List<Long> sessionIds = feedback.stream()
				.map(fb -> fb.getRequest().getSession().getSession())
				.distinct()
				.toList();

		Map<Long, Boolean> canManage = sessionCacheManager
				.getAndHandleAll(sessionIds, ls.deleteSessionsByIds()).stream()
				.collect(Collectors.toMap(
						SessionDetailsDTO::getId,
						session -> ps
								.canManageInAnyEdition(new ArrayList<>(session.getEditions()))));

		List<Feedback> filteredFeedback = feedback.stream()
				.filter(fb -> canManage.getOrDefault(fb.getRequest().getSession().getSession(), false))
				.toList();

		return PageUtil.toPage(pageable, filteredFeedback);

	}
}
