/*
 * 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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import jakarta.transaction.Transactional;
import nl.tudelft.labracore.api.dto.AssignmentDetailsDTO;
import nl.tudelft.labracore.api.dto.EditionDetailsDTO;
import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.model.events.EventWithAssistant;
import nl.tudelft.queue.model.labs.RegularLab;
import test.TestDatabaseLoader;
import test.test.TestQueueApplication;

@Transactional
@SpringBootTest(classes = TestQueueApplication.class)
public class SessionStatusServiceTest {

	@Autowired
	private SessionStatusService sessionStatusService;

	@Autowired
	private TestDatabaseLoader db;

	RegularLab oopNowRegularLab1;

	RegularLab rlOopNowSharedLab;

	EditionDetailsDTO oopNow;

	LabRequest[] oopNowRegularLab1Requests;

	AssignmentDetailsDTO[] oopNowAssignments;

	PersonSummaryDTO oopTeacher1;

	RegularLab lab1;

	@BeforeEach
	void setup() {
		db.mockAll();

		oopNowRegularLab1 = db.getOopNowRegularLab1();
		oopNowRegularLab1Requests = db.getOopNowRegularLab1Requests();
		oopNowAssignments = db.getOopNowAssignments();
		rlOopNowSharedLab = db.getRlOopNowSharedLab();
		oopNow = db.getOopNow();
		oopTeacher1 = db.getOopNowTeacher().getPerson();

		lab1 = new RegularLab();

	}

	@Test
	void gettingTimeSinceLastInteractionWorksAsExpected() {
		int countOfTaken = (int) oopNowRegularLab1.getRequests().stream()
				.flatMap(req -> req.getEventInfo().getEvents().stream()
						.filter(event -> event instanceof EventWithAssistant))
				.map(event -> ((EventWithAssistant) event).getAssistant()).distinct().count();
		sessionStatusService.getTimeSinceLastInteraction(oopNowRegularLab1, Collections.emptySet());
		assertThat(sessionStatusService
				.getTimeSinceLastInteraction(oopNowRegularLab1, Set.of(123L, 13254321L, 34534534L))
				.isEmpty());

		var res = sessionStatusService.getTimeSinceLastInteraction(oopNowRegularLab1, Collections.emptySet());
		assertThat(res).hasSize(countOfTaken);

		var requestsTakenByTeacher1 = oopNowRegularLab1.getRequests().stream()
				.filter(req -> Objects.equals(req.getEventInfo().getAssignedTo(), oopTeacher1.getId()))
				.toList();
		var lastTime = requestsTakenByTeacher1.stream()
				.flatMap(req -> req.getEventInfo().getEvents().stream())
				.map(event -> event.getTimestamp().until(LocalDateTime.now(), ChronoUnit.MILLIS))
				.max(Long::compare);
		assertThat(res.get(oopTeacher1.getId())).isCloseTo(lastTime.get(), Assertions.within(1000L));

	}

	@Test
	void testAssignmentCountDoesNotFailForNoRequests() {
		assertThat(sessionStatusService.countAssignmentFreqs(Collections.emptyList(),
				Arrays.asList(oopNowAssignments))).hasSize(oopNowAssignments.length);
	}

	@Test
	void assignmentCountContainsCorrectInformation() {
		var sut = sessionStatusService.countAssignmentFreqs(oopNowRegularLab1.getRequests(),
				Arrays.asList(oopNowAssignments));
		assertThat(sut).hasSize(oopNowAssignments.length).isNotEmpty();
		assertThat(sut.get(0).getAssignmentName()).isEqualTo("Assignment 0");
	}

	@Test
	void requestDistributionWithRequestsOutsideOfLabTimeAreNotIncluded() {
		var sut = sessionStatusService.createRequestDistribution(oopNowRegularLab1, new HashSet<>(), 5);
		assertThat(sut).hasSize(1);
		var numOfResults = sut.stream()
				.map(bucket -> bucket.getRequestsPerCourse().values().stream().reduce(0L, Long::sum))
				.reduce(0L, Long::sum);
		assertThat(numOfResults).isEqualTo(50);
	}

	@Test
	void requestDistributionWithInvalidBucketsReturnsEmptyList() {
		var sut1 = sessionStatusService.createRequestDistribution(oopNowRegularLab1, new HashSet<>(), -1);
		var sut2 = sessionStatusService.createRequestDistribution(oopNowRegularLab1, new HashSet<>(), 0);
		assertThat(sut1).isEmpty();
		assertThat(sut2).isEmpty();
	}

}
