/*
 * Copyright (c) 2024. Das Land Schleswig-Holstein vertreten durch das Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
 * Zentrales IT-Management
 *
 * Lizenziert unter der EUPL, Version 1.2 oder - sobald
 * diese von der Europäischen Kommission genehmigt wurden -
 * Folgeversionen der EUPL ("Lizenz");
 * Sie dürfen dieses Werk ausschließlich gemäß
 * dieser Lizenz nutzen.
 * Eine Kopie der Lizenz finden Sie hier:
 *
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 *
 * Sofern nicht durch anwendbare Rechtsvorschriften
 * gefordert oder in schriftlicher Form vereinbart, wird
 * die unter der Lizenz verbreitete Software "so wie sie
 * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
 * ausdrücklich oder stillschweigend - verbreitet.
 * Die sprachspezifischen Genehmigungen und Beschränkungen
 * unter der Lizenz sind dem Lizenztext zu entnehmen.
 */
package de.ozgcloud.admin.security;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import org.apache.http.HttpHeaders;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.http.HttpStatus;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

import de.ozgcloud.admin.common.user.UserRole;
import de.ozgcloud.common.test.DataITCase;
import lombok.SneakyThrows;

@DataITCase
@AutoConfigureMockMvc
class SecurityConfigurationITCase {

	@Autowired
	private MockMvc mockMvc;

	@DisplayName("without authentication")
	@Nested
	class TestWithoutAuthentication {

		@DisplayName("allow for not found")
		@SneakyThrows
		@ParameterizedTest
		@ValueSource(strings = {
				"/actuator", "/actuator/x", "/actuator/x/y",
				"/configserver", "/configserver/x",
		})
		void shouldAllowForNotFound(String path) {
			var result = doPerform(path);

			result.andExpect(status().isNotFound());
		}

		@DisplayName("allow")
		@SneakyThrows
		@ParameterizedTest
		@ValueSource(strings = {
				"/api/environment",
				"/configserver/name/profile"
		})
		void shouldAllow(String path) {
			var result = doPerform(path);

			result.andExpect(status().isOk());
		}

		@SneakyThrows
		@ParameterizedTest
		@ValueSource(strings = {
				"/api", "/api/configuration", "/api/configuration/settings",
				"/unknown",
		})
		void shouldDeny(String path) {
			var result = doPerform(path);

			result.andExpect(status().isUnauthorized());
		}

		@DisplayName("deny with problem details")
		@Nested
		class TestDenyWithProblemDetails {

			@SneakyThrows
			@Test
			void shouldHaveStatus() {
				var result = doPerform("/api");

				result.andExpect(jsonPath("$.status").value(HttpStatus.UNAUTHORIZED.value()));
			}

			@SneakyThrows
			@Test
			void shouldHaveInstanceURI() {
				var result = doPerform("/api");

				result.andExpect(jsonPath("$.instance").value("/api"));
			}

			@Test
			@SneakyThrows
			void shouldHaveErrorDetailInBody() {
				var result = doPerform("/api");

				result.andExpect(jsonPath("$.detail").value("Full authentication is required to access this resource"));

			}

			@Test
			@SneakyThrows
			void shouldHaveHeader() {
				var result = doPerform("/api");

				result.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\""));
			}
		}

		@SneakyThrows
		private ResultActions doPerform(String path) {
			return mockMvc.perform(get(path).header("Authorization", "invalid"));
		}
	}

	@DisplayName("with authentication")
	@Nested
	class TestWithAuthentication {

		static final String CLAIMS = """
				{
				  "preferredUsername": "testUser",
				  "scope": "openid testscope"
				}""";

		@SneakyThrows
		@ParameterizedTest
		@ValueSource(strings = {
				"/api/environment",
				"/configserver/name/profile",
				"/api", "/api/configuration"
		})
		@WithJwt(CLAIMS)
		void shouldAllow(String path) {
			var result = doPerformAuthenticated(path);

			result.andExpect(status().isOk());
		}

		@Test
		@SneakyThrows
		@WithJwt(CLAIMS)
		void shouldForbid() {
			var result = doPerformAuthenticated("/api/configuration/settings");

			result.andExpect(status().isForbidden());
		}

		@SneakyThrows
		private ResultActions doPerformAuthenticated(String path) {
			return mockMvc.perform(get(path));
		}
	}

	@DisplayName("with admin role")
	@Nested
	class TestWithAdminRole {
		@Test
		@SneakyThrows
		@WithMockUser(roles = UserRole.ADMIN_USER)
		void shouldAllow() {
			var result = mockMvc.perform(get("/api/configuration/settings"));

			result.andExpect(status().isOk());
		}
	}
}