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

import static org.assertj.core.api.Assertions.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestProperties;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

import com.fasterxml.jackson.databind.ObjectMapper;

import de.ozgcloud.admin.common.user.UserRole;
import de.ozgcloud.admin.setting.postfach.Absender;
import de.ozgcloud.admin.setting.postfach.AbsenderTestFactory;
import de.ozgcloud.admin.setting.postfach.PostfachSettingBody;
import de.ozgcloud.admin.setting.postfach.PostfachSettingBodyTestFactory;
import de.ozgcloud.common.test.DataITCase;
import lombok.SneakyThrows;

@DataITCase
@AutoConfigureMockMvc
@WithMockUser(roles = UserRole.ADMIN_ADMIN)
class SettingITCase {

	@Autowired
	private MockMvc mockMvc;

	@Autowired
	private MongoOperations mongoOperations;

	@Autowired
	private RepositoryRestProperties restProperties;

	@Nested
	class TestRestRepo {

		private String id;

		@BeforeEach
		void init() {
			mongoOperations.dropCollection(Setting.class);
			id = mongoOperations.save(SettingTestFactory.create()).getId();
		}

		@Nested
		class TestGet {

			@Test
			@SneakyThrows
			void shouldHaveStatusOkForExisting() {
				var result = performGet(id);

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

			@Test
			@SneakyThrows
			void shouldHaveStatusNotFoundForNonExisting() {
				var result = performGet("unknown");

				result.andExpect(status().is(HttpStatus.NOT_FOUND.value()));
			}

			@SneakyThrows
			private ResultActions performGet(String id) {
				return mockMvc.perform(get(String.join("/", restProperties.getBasePath(), SettingConstants.PATH, id)));
			}
		}
	}

	@Nested
	class TestForSettingWithPostfach {
		private static final String POSTFACH_NAME = "Postfach";

		private final Setting settingWithPostfach = SettingTestFactory.createBuilder()
				.name(POSTFACH_NAME)
				.settingBody(PostfachSettingBodyTestFactory.create())
				.build();

		@BeforeEach
		void clear() {
			mongoOperations.dropCollection(Setting.class);
		}

		@Nested
		class TestSave {

			@Test
			@SneakyThrows
			void shouldHaveResponseStatusCreated() {
				var result = performPost(settingWithPostfach);

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

			@Test
			@SneakyThrows
			void shouldCreateSettingItem() {
				performPost(settingWithPostfach);

				assertThat(getSettingWithPostfachFromDb())
						.usingRecursiveComparison().ignoringFields("id").isEqualTo(settingWithPostfach);
			}

			@Nested
			class TestPostfachSetting {

				@Test
				@SneakyThrows
				void shouldBeInstanceOfPostfach() {
					performPost(settingWithPostfach);

					assertThat(getSettingWithPostfachFromDb()
							.getSettingBody()).isInstanceOf(PostfachSettingBody.class);
				}

				@Test
				@SneakyThrows
				void shouldCreateEmptySignatur() {
					var postfachWithEmptySignatur = PostfachSettingBodyTestFactory.createBuilder()
							.signatur(StringUtils.EMPTY).build();
					var settingWithPostfachEmptySignatur = createSettingWithPostfach(postfachWithEmptySignatur);

					performPost(settingWithPostfachEmptySignatur);

					assertThat(getPostfachFromDb().getSignatur()).isEmpty();
				}

				private PostfachSettingBody getPostfachFromDb() {
					return (PostfachSettingBody) getSettingWithPostfachFromDb().getSettingBody();
				}

				@Nested
				class TestAbsenderValidation {
					@Test
					@SneakyThrows
					void shouldReturnUnprocessableEntityOnEmptyName() {
						var absenderWithEmptyName = AbsenderTestFactory.createBuilder().name(StringUtils.EMPTY).build();
						var settingWithPostfachEmptyAbsenderName = createSettingWithPostfachAbsender(absenderWithEmptyName);

						var result = performPost(settingWithPostfachEmptyAbsenderName);

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

					@Test
					@SneakyThrows
					void shouldReturnUnprocessableEntityOnEmptyAnschrift() {
						var absenderWithEmptyAnschrift = AbsenderTestFactory.createBuilder().anschrift(StringUtils.EMPTY).build();
						var settingWithPostfachEmptyAnschrift = createSettingWithPostfachAbsender(absenderWithEmptyAnschrift);

						var result = performPost(settingWithPostfachEmptyAnschrift);

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

					@Test
					@SneakyThrows
					void shouldReturnUnprocessableEntityOnEmptyDienst() {
						var absenderWithEmptyDienst = AbsenderTestFactory.createBuilder().dienst(StringUtils.EMPTY).build();
						var settingWithPostfachEmptyDienst = createSettingWithPostfachAbsender(absenderWithEmptyDienst);

						var result = performPost(settingWithPostfachEmptyDienst);

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

					@Test
					@SneakyThrows
					void shouldReturnUnprocessableEntityOnEmptyMandant() {
						var absenderWithEmptyMandant = AbsenderTestFactory.createBuilder().mandant(StringUtils.EMPTY).build();
						var settingWithPostfachEmptyMandant = createSettingWithPostfachAbsender(absenderWithEmptyMandant);

						var result = performPost(settingWithPostfachEmptyMandant);

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

					@Test
					@SneakyThrows
					void shouldReturnUnprocessableEntityOnEmptyGemeindeSchluessel() {
						var absenderWithEmptyGemeindeschluessel = AbsenderTestFactory.createBuilder().gemeindeschluessel(StringUtils.EMPTY).build();
						var settingWithPostfachEmptyGemeindeschluessel = createSettingWithPostfachAbsender(absenderWithEmptyGemeindeschluessel);

						var result = performPost(settingWithPostfachEmptyGemeindeschluessel);

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

					private Setting createSettingWithPostfachAbsender(Absender absender) {
						var postfach = PostfachSettingBodyTestFactory.createBuilder().absender(absender).build();
						return createSettingWithPostfach(postfach);
					}

				}

				private Setting createSettingWithPostfach(PostfachSettingBody postfach) {
					return SettingTestFactory.createBuilder().name(POSTFACH_NAME).settingBody(postfach).build();
				}
			}

			@SneakyThrows
			private ResultActions performPost(Setting setting) {
				var postBody = convertSettingToString(setting);
				return mockMvc.perform(post(String.join("/", restProperties.getBasePath(),
						SettingConstants.PATH))
								.with(csrf())
								.contentType(MediaType.APPLICATION_JSON)
								.content(postBody));
			}
		}

		@Nested
		class TestGet {
			private String id;
			@Autowired
			private ObjectMapper mapper;

			@BeforeEach
			void init() {
				id = mongoOperations.save(settingWithPostfach).getId();
			}

			@Test
			@SneakyThrows
			void shouldHaveStatusOkForExisting() {
				var result = performGet();

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

			@Test
			@SneakyThrows
			void shouldHaveInstanceOfPostfach() {

				var result = performGet();

				assertThat(mapper.readValue(result.andReturn().getResponse().getContentAsString(), Setting.class).getSettingBody())
						.isInstanceOf(PostfachSettingBody.class);
			}

			@SneakyThrows
			private ResultActions performGet() {
				return mockMvc.perform(get(String.join("/", restProperties.getBasePath(), SettingConstants.PATH, id)));
			}
		}

		@Nested
		class TestPut {
			private String id;
			private final PostfachSettingBody updatedPostfach = PostfachSettingBodyTestFactory.createBuilder()
					.absender(AbsenderTestFactory.createBuilder()
							.name("Neuer Name")
							.anschrift("Neue Anschrift")
							.build())
					.build();
			private final Setting updatedSetting = SettingTestFactory.createBuilder()
					.name(POSTFACH_NAME)
					.settingBody(updatedPostfach)
					.build();

			@BeforeEach
			void init() {
				id = mongoOperations.save(settingWithPostfach).getId();
			}

			@Test
			@SneakyThrows
			void shouldHaveStatusNoContent() {
				var result = performPut();

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

			@Test
			@SneakyThrows
			void shouldHaveUpdatedSetting() {
				performPut();

				assertThat(getSettingWithPostfachFromDb())
						.usingRecursiveComparison().ignoringFields("id").isEqualTo(updatedSetting);
			}

			@Test
			@SneakyThrows
			void shouldHaveExactlyOnePostfachSetting() {
				performPut();

				List<Setting> postfachSettings = mongoOperations.find(createQueryForPostfach(), Setting.class);

				assertThat(postfachSettings).hasSize(1);
			}

			@SneakyThrows
			private ResultActions performPut() {
				var body = convertSettingToString(updatedSetting);
				return mockMvc.perform(put(String.join("/", restProperties.getBasePath(), SettingConstants.PATH, id))
						.with(csrf())
						.contentType(MediaType.APPLICATION_JSON)
						.content(body));
			}

		}

		private Setting getSettingWithPostfachFromDb() {
			return mongoOperations.findOne(createQueryForPostfach(), Setting.class);
		}

		private Query createQueryForPostfach() {
			return new Query().addCriteria(Criteria.where("name").in(POSTFACH_NAME));
		}

		@SneakyThrows
		private String convertSettingToString(Setting setting) {
			return SettingTestFactory.buildSettingJson(setting,
					PostfachSettingBodyTestFactory.buildPostfachJson((PostfachSettingBody) setting.getSettingBody()));
		}
	}

}