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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.transaction.Transactional;

import nl.tudelft.labracore.api.PersonControllerApi;
import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
import nl.tudelft.queue.dto.patch.FeedbackPatchDTO;
import nl.tudelft.queue.model.Feedback;
import nl.tudelft.queue.model.LabRequest;
import nl.tudelft.queue.model.RequestEvent;
import nl.tudelft.queue.model.embeddables.AllowedRequest;
import nl.tudelft.queue.model.embeddables.LabRequestConstraints;
import nl.tudelft.queue.model.enums.CommunicationMethod;
import nl.tudelft.queue.model.enums.RequestType;
import nl.tudelft.queue.model.events.*;
import nl.tudelft.queue.model.labs.Lab;
import nl.tudelft.queue.model.labs.RegularLab;
import nl.tudelft.queue.repository.FeedbackRepository;
import nl.tudelft.queue.repository.LabRepository;
import nl.tudelft.queue.repository.LabRequestRepository;
import nl.tudelft.queue.repository.RequestEventRepository;

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 test.labracore.PersonApiMocker;
import test.test.TestQueueApplication;

@Transactional
@SpringBootTest(classes = TestQueueApplication.class)
public class FeedbackServiceTest {
	@Autowired
	private FeedbackRepository fr;

	@Autowired
	private LabRepository lr;

	@Autowired
	private LabRequestRepository rr;

	@Autowired
	private RequestEventRepository rer;

	@Autowired
	private FeedbackService fs;

	@Autowired
	private PersonControllerApi pApi;

	@Autowired
	private PersonApiMocker pApiMocker;

	private Lab lab;

	private LabRequest request1;
	private LabRequest request2;
	private LabRequest request3;

	private Feedback feedback1;
	private Feedback feedback2;
	private Feedback feedback3;
	private Feedback feedback4;
	private Feedback feedback5;
	private Feedback feedback6;

	private PersonSummaryDTO[] people = {
			new PersonSummaryDTO().id(0L),
			new PersonSummaryDTO().id(1L),
			new PersonSummaryDTO().id(2L),
			new PersonSummaryDTO().id(3L),
			new PersonSummaryDTO().id(4L),
			new PersonSummaryDTO().id(5L),
			new PersonSummaryDTO().id(6L),
			new PersonSummaryDTO().id(7L),
			new PersonSummaryDTO().id(8L)
	};

	@BeforeEach
	void setUp() {
		pApiMocker = new PersonApiMocker(pApi);

		Arrays.asList(people).forEach(pApiMocker::save);
		pApiMocker.mock();

		lab = lr.save(RegularLab.builder()
				.session(34L)
				.modules(Set.of(83L))
				.enqueueClosed(false)
				.deletedAt(null)
				.allowedRequests(Set.of(new AllowedRequest(32L, RequestType.QUESTION)))
				.communicationMethod(CommunicationMethod.STUDENT_VISIT_TA)
				.constraints(LabRequestConstraints.builder().build())
				.requests(new ArrayList<>())
				.build());

		request1 = rr.save(LabRequest.builder()
				.room(32L).assignment(32L).studentGroup(32L).requester(32L).comment("Hello")
				.session(lab)
				.requestType(RequestType.QUESTION)
				.build());
		request1.getEventInfo().getEvents().addAll(List.of(
				new RequestCreatedEvent(request1),
				new RequestTakenEvent(request1, people[0].getId()),
				new RequestForwardedToAnyEvent(request1, people[0].getId(), "Hey"),
				new RequestTakenEvent(request1, people[1].getId()),
				new RequestApprovedEvent(request1, people[1].getId(), "")));
		for (RequestEvent<?> event : request1.getEventInfo().getEvents()) {
			rer.applyAndSave(event);
		}

		request2 = rr.save(LabRequest.builder()
				.room(32L).assignment(32L).studentGroup(32L).requester(32L).comment("Hello")
				.requestType(RequestType.QUESTION)
				.session(lab)
				.build());
		request2.getEventInfo().getEvents().addAll(List.of(
				new RequestCreatedEvent(request2),
				new RequestTakenEvent(request2, people[0].getId()),
				new RequestForwardedToAnyEvent(request2, people[2].getId(), "Hey"),
				new RequestTakenEvent(request2, people[3].getId()),
				new RequestForwardedToPersonEvent(request2, people[3].getId(), people[4].getId(), ""),
				new RequestApprovedEvent(request2, people[4].getId(), "")));
		for (RequestEvent<?> event : request2.getEventInfo().getEvents()) {
			rer.applyAndSave(event);
		}

		feedback1 = fr.save(Feedback.builder()
				.id(new Feedback.Id(request1.getId(), people[0].getId()))
				.request(request1)
				.feedback("").rating(4).build());
		feedback2 = fr.save(Feedback.builder()
				.id(new Feedback.Id(request2.getId(), people[0].getId()))
				.request(request2)
				.feedback("").rating(4).build());

		feedback3 = fr.save(Feedback.builder()
				.id(new Feedback.Id(request1.getId(), people[1].getId()))
				.request(request1)
				.feedback("").rating(2).build());

		feedback4 = fr.save(Feedback.builder()
				.id(new Feedback.Id(request2.getId(), people[2].getId()))
				.request(request2)
				.feedback("Meh").rating(3).build());

		feedback5 = fr.save(Feedback.builder()
				.id(new Feedback.Id(request2.getId(), people[3].getId()))
				.request(request2)
				.feedback("Meh").build());
	}

	@Test
	void assistantsInvolvedInRequestShouldIncludeAllAssistants() {
		assertThat(fs.assistantsInvolvedInRequest(request1.getId()))
				.containsExactlyInAnyOrder(people[1], people[0]);
	}

	@Test
	void assistantsInvolvedInRequestShouldIncludeAllInChain() {
		assertThat(fs.assistantsInvolvedInRequest(request2.getId()))
				.containsExactlyInAnyOrder(people[4], people[3], people[2], people[0]);
	}

	@Test
	void countRatingsWorks() {
		assertThat(fs.countRatings(people[0].getId()))
				.isEqualTo(List.of(0, 0, 0, 2, 0));
		assertThat(fs.countRatings(people[1].getId()))
				.isEqualTo(List.of(0, 1, 0, 0, 0));
		assertThat(fs.countRatings(people[2].getId()))
				.isEqualTo(List.of(0, 0, 1, 0, 0));
		assertThat(fs.countRatings(people[3].getId()))
				.isEqualTo(List.of(0, 0, 0, 0, 0));
		assertThat(fs.countRatings(people[4].getId()))
				.isEqualTo(List.of(0, 0, 0, 0, 0));
	}

	@Test
	void updateFeedbackCreatesNewFeedbackWhenNeeded() {
		assertThat(fr.findAllByAssistant(people[4].getId())).isEmpty();

		fs.updateFeedback(request2, people[4].getId(), FeedbackPatchDTO.builder()
				.feedback("Hello")
				.rating(5)
				.build());

		assertThat(fr.findAllByAssistant(people[4].getId())).isNotEmpty();
	}

	@Test
	void updateFeedbackChangesExistingFeedback() {
		fs.updateFeedback(request2, people[3].getId(), FeedbackPatchDTO.builder()
				.feedback("New feedback")
				.rating(3)
				.build());

		assertThat(fr.findById(feedback5.getId()).get().getRating())
				.isEqualTo(3);
		assertThat(fr.findById(feedback5.getId()).get().getFeedback())
				.isEqualTo("New feedback");
	}

	@Test
	void updateFeedbackDoesNotAllowEmptyFeedbackCreation() {
		assertThatThrownBy(
				() -> fs.updateFeedback(request2, people[4].getId(), FeedbackPatchDTO.builder().build()));
	}

	/**
	 * Simulates the people cache manager by getting the list of people with the given ids from a local array.
	 *
	 * @param  ids The ids of the people to lookup.
	 * @return     The list of {@link PersonSummaryDTO} objects representing the people to lookup.
	 */
	private List<PersonSummaryDTO> getPeople(Stream<Long> ids) {
		return ids
				.map(id -> people[id.intValue()])
				.collect(Collectors.toList());
	}

	/**
	 * Simulates the people cache manager by getting the list of people with the given ids from a local array.
	 *
	 * @param  ids The ids of the people to lookup.
	 * @return     The list of {@link PersonSummaryDTO} objects representing the people to lookup.
	 */
	private List<PersonSummaryDTO> getPeople(List<Long> ids) {
		return getPeople(ids.stream());
	}
}
