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

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import nl.tudelft.queue.cqsr.Aggregate;
import nl.tudelft.queue.model.RequestEvent;
import nl.tudelft.queue.model.enums.RequestStatus;
import nl.tudelft.queue.model.events.EventWithAssistant;
import nl.tudelft.queue.model.events.StudentNotFoundEvent;

@Data
@Builder
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class RequestEventInfo extends Aggregate<RequestEventInfo, RequestEvent<?>> {
	/**
	 * The status of the request, filled in by event application.
	 */
	@NotNull
	@Builder.Default
	@Enumerated(EnumType.STRING)
	private RequestStatus status = RequestStatus.PENDING;

	/**
	 * The reason for the current status, filled in by event application.
	 */
	private String reasonForAssistant;

	/**
	 * The reason for the current status visible for a student, filled in by event application.
	 */
	private String reasonForStudent;

	/**
	 * The last time an event occurred to this request, filled in by event application.
	 */
	private LocalDateTime lastEventAt;

	/**
	 * The last time that the associated request was assigned to a TA.
	 */
	private LocalDateTime lastAssignedAt;

	/**
	 * The first time that this request was to be processed.
	 */
	private LocalDateTime firstProcessedAt;

	/**
	 * The time that the associated request was handled (approved, rejected or not found).
	 */
	private LocalDateTime handledAt;

	/**
	 * The time that the associated request was finished (approved, rejected, revoked or not found).
	 */
	private LocalDateTime finishedAt;

	/**
	 * The Assistant that this request is currently forwarded to, filled in by event application.
	 */
	private Long forwardedTo;

	/**
	 * The Assistant that this request is currently forwarded by, filled in by event application.
	 */
	private Long forwardedBy;

	/**
	 * The Assistant currently helping with this request, filled in by event application.
	 */
	private Long assignedTo;

	/**
	 * The events that occurred on this Request.
	 */
	@Builder.Default
	@ToString.Exclude
	@OrderBy("timestamp asc")
	@EqualsAndHashCode.Exclude
	@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "request")
	private List<RequestEvent<?>> events = new ArrayList<>();

	@Override
	public List<RequestEvent<?>> events() {
		return getEvents();
	}

	/**
	 * Collects a list of ids of the assistants that are involved with this request. The assistants are given
	 * in order of most-recently involved to least-recently involved. It also excludes assistants which could
	 * not find the student. This is done to ensure that students do not give feedback to a TA who never
	 * interacted with them.
	 *
	 * @return The list of assistant ids.
	 */
	public List<Long> involvedAssistants() {
		Set<Long> disqualifiedAssistants = events.stream()
				.filter(event -> event instanceof StudentNotFoundEvent)
				.map(event -> ((StudentNotFoundEvent) event).getAssistant()).collect(Collectors.toSet());

		return events.stream()
				.sorted(Comparator.comparing((RequestEvent<?> re) -> re.getTimestamp()).reversed())
				.filter(event -> event instanceof EventWithAssistant)
				.map(event -> ((EventWithAssistant) event).getAssistant())
				.filter(assistant -> !disqualifiedAssistants.contains(assistant))
				.distinct().collect(Collectors.toList());
	}
}
