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

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.time.LocalDateTime;
import java.util.List;

import org.hamcrest.core.IsNot;
import org.hamcrest.core.StringContains;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;

import jakarta.transaction.Transactional;
import nl.tudelft.labracore.api.AuthorizationControllerApi;
import nl.tudelft.queue.model.misc.Announcement;
import nl.tudelft.queue.repository.AnnouncementRepository;
import reactor.core.publisher.Mono;
import test.test.TestQueueApplication;

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

	@Autowired
	private MockMvc mvc;

	@Autowired
	private AuthorizationControllerApi aApi;

	@Autowired
	private AnnouncementRepository ar;

	@BeforeEach
	void setUp() {
		when(aApi.hasStaffRoleInAnyEdition(any())).thenReturn(Mono.just(true));
	}

	@Test
	@WithUserDetails("admin")
	void getAnnouncementsPageContainsPage() throws Exception {
		mvc.perform(get("/admin/announcements"))
				.andExpect(model().attribute("page", "admin"))
				.andExpect(view().name("admin/announcements"));
	}

	@Test
	@WithUserDetails("admin")
	void getAnnouncementCreateContainsDTO() throws Exception {
		mvc.perform(get("/admin/announcement/create"))
				.andExpect(model().attributeExists("dto"));
	}

	@Test
	@WithUserDetails("admin")
	void creatingAnAnnouncementWorks() throws Exception {
		var currentAnnouncements = ar.findEditableAnnouncements();

		mvc.perform(post("/admin/announcement/create").with(csrf())
				.queryParam("message", "Hello world")
				.queryParam("backgroundColourHex", "#abcdef")
				.queryParam("textColourHex", "#abcdee")
				.queryParam("startTime", "2000-08-08T18:08")
				.queryParam("endTime", "")
				.queryParam("isDismissible", "true"))
				.andExpect(status().is3xxRedirection());

		// Check that there is indeed a new announcement
		assertThat(ar.findEditableAnnouncements())
				.hasSizeGreaterThan(currentAnnouncements.size());

		// Check that all fields are as expected
		var added = ar.findEditableAnnouncements().get(ar.findEditableAnnouncements().size() - 1);
		assertThat(added.getMessage()).isEqualTo("Hello world");
		assertThat(added.getBackgroundColour()).isEqualTo(11259375);
		assertThat(added.getTextColour()).isEqualTo(11259374);
		assertThat(added.getStartTime()).isEqualTo(LocalDateTime.of(2000, 8, 8, 18, 8));
		assertThat(added.getEndTime()).isNull();
		assertThat(added.getIsDismissible()).isEqualTo(true);
	}

	@Test
	@WithUserDetails("admin")
	void endingAnAnnouncementCausesItToDisappear() throws Exception {
		var announcement = ar.save(Announcement.builder()
				.startTime(LocalDateTime.now().minusDays(1))
				.message("Heya I am a very special 125463687372914713924 message")
				.build());

		mvc.perform(get("/admin/announcement/create"))
				.andExpect(content().string(new StringContains(announcement.getMessage())));

		mvc.perform(post("/admin/announcement/{id}/end", announcement.getId()).with(csrf()));

		mvc.perform(get("/admin/announcement/create"))
				.andExpect(content().string(new IsNot<>(new StringContains(announcement.getMessage()))));
	}

	@ParameterizedTest
	@MethodSource(value = "protectedEndpoints")
	void testWithoutUserDetailsIsForbidden(MockHttpServletRequestBuilder request) throws Exception {
		mvc.perform(request.with(csrf()))
				.andExpect(status().is3xxRedirection())
				.andExpect(redirectedUrl("http://localhost/login"));
	}

	private static List<MockHttpServletRequestBuilder> protectedEndpoints() {
		return List.of(
				get("/admin/announcements"),
				get("/admin/announcement/create"),
				post("/admin/announcement/create").with(csrf()),
				post("/admin/announcement/1/end").with(csrf()));
	}
}
