/*
 * 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.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import jakarta.transaction.Transactional;
import nl.tudelft.labracore.api.SessionControllerApi;
import nl.tudelft.labracore.api.dto.SessionDetailsDTO;
import nl.tudelft.labracore.api.dto.SessionPatchDTO;
import nl.tudelft.librador.dto.DTOConverter;
import nl.tudelft.queue.dto.patch.TimeSlotPatchDTO;
import nl.tudelft.queue.model.ClosableTimeSlot;
import nl.tudelft.queue.model.TimeSlot;
import nl.tudelft.queue.model.labs.AbstractSlottedLab;
import nl.tudelft.queue.realtime.messages.RequestAvailableMessage;
import nl.tudelft.queue.repository.ClosableTimeSlotRepository;
import nl.tudelft.queue.repository.TimeSlotRepository;

@Service
@DependsOnDatabaseInitialization
public class TimeSlotService {

	@Autowired
	private RequestService rs;

	@Autowired
	private TimeSlotRepository tsr;

	@Autowired
	private ClosableTimeSlotRepository ctsr;

	@Autowired
	private SessionControllerApi sessionApi;

	@Autowired
	private WebSocketService webSocketService;

	@Autowired
	private DTOConverter converter;

	@Transactional
	public void patchTimeSlot(TimeSlot timeSlot, TimeSlotPatchDTO patch) {
		tsr.save(patch.apply(timeSlot, converter));
		fixLabSlot((AbstractSlottedLab<?>) timeSlot.getLab());
	}

	@Transactional
	public void deleteTimeSlot(TimeSlot timeSlot) {
		tsr.delete(timeSlot);
		fixLabSlot((AbstractSlottedLab<?>) timeSlot.getLab());
	}

	private void fixLabSlot(AbstractSlottedLab<?> lab) {
		SessionDetailsDTO session = sessionApi.getSessionsById(List.of(lab.getSession())).collectList()
				.block().get(0);
		SessionPatchDTO patch = new SessionPatchDTO();
		patch.setStart(lab.getTimeSlots().stream().map(ts -> ts.getSlot().getOpensAt())
				.min(Comparator.naturalOrder()).orElse(null));
		patch.setEnd(lab.getTimeSlots().stream().map(ts -> ts.getSlot().getClosesAt())
				.max(Comparator.naturalOrder()).orElse(null));
		sessionApi.patchSession(patch, session.getId()).block();
	}

	/**
	 * Closes a time slot for active use.
	 *
	 * @param timeSlot The TimeSlot to close.
	 */
	@Transactional
	public void closeTimeSlot(ClosableTimeSlot timeSlot) {
		timeSlot.setActive(false);

		timeSlot.getRequests().stream()
				.filter(r -> r.getEventInfo().getStatus().isPending())
				.forEach(rs::notPickedRequest);
	}

	/**
	 * A scheduled job which checks all the closable time slots in Queue for slots that should now be
	 * automagically closed. This job should run every 5 minutes on the 5th second of the minute. The 5th
	 * second is added because time slots will likely be created on the full minute. 5 seconds is more than
	 * enough time to make sure that time slots only slightly more than the allowed grace period are closed.
	 */
	@Transactional
	@Scheduled(cron = "5 */5 * * * ?")
	public void checkClosableSlots() {
		ctsr.findAllClosablyActive().forEach(this::closeTimeSlot);
	}

	@Transactional
	@Scheduled(cron = "1 * * * * ?")
	public void notifyOpenedSlots() {
		List<TimeSlot> justOpened = tsr
				.findAllBySlotOpensAt(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES));
		justOpened.forEach(t -> t.getRequests().forEach(r -> webSocketService.sendRequestTableMessage(r,
				converter.convert(r, RequestAvailableMessage.class))));
	}
}
