package de.ozgcloud.xta.client;

import static de.ozgcloud.xta.client.XtaClientITCase.*;
import static de.ozgcloud.xta.client.extension.XtaServerSetupExtensionTestUtil.*;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import de.ozgcloud.xta.client.config.XtaClientConfig;
import de.ozgcloud.xta.client.extension.StaticStringListAppender;
import de.ozgcloud.xta.client.extension.XtaMessageExampleLoader;
import de.ozgcloud.xta.client.extension.XtaRemoteServerSetupExtension;
import de.ozgcloud.xta.client.model.XtaFile;
import de.ozgcloud.xta.client.model.XtaIdentifier;
import de.ozgcloud.xta.client.model.XtaMessage;
import de.ozgcloud.xta.client.model.XtaMessageMetaData;
import de.ozgcloud.xta.client.model.XtaMessageStatus;
import de.ozgcloud.xta.client.model.XtaTransportReport;
import de.ozgcloud.xta.client.xdomea.XdomeaXtaMessageCreator;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@EnabledIfEnvironmentVariable(
		named = "KOP_SH_KIEL_DEV_PATH",
		matches = ".+",
		disabledReason =
				"This test requires the path KOP_SH_KIEL_{DEV,TEST}_PATH and password KOP_SH_KIEL_{DEV,TEST}_PASSWORD of KOP_SH_KIEL_DEV.p12 and OZG-CLOUD_SH_KIEL_TEST.pfx. "
						+ "Additionally, the endpoint of the DEV-xta-server at li33-0005.dp.dsecurecloud.de must be reachable."
)
class XtaClientRemoteITCase {

	@RegisterExtension
	private static final XtaRemoteServerSetupExtension XTA_REMOTE_SERVER_SETUP_EXTENSION = new XtaRemoteServerSetupExtension();

	private static final XdomeaXtaMessageCreator XDOMEA_XTA_MESSAGE_CREATOR = XdomeaXtaMessageCreator.createInstance();

	private XtaClient testClient;
	private XtaClient silentTestClient;
	private XtaClient devClient;
	private XtaClient silentDevClient;

	private List<XtaMessageMetaData> supportCheckedMetadataItems;
	private List<XtaMessage> processedMessages;
	private Consumer<XtaMessage> processMessageDummy;
	private Predicate<XtaMessageMetaData> isSupportedDummy;

	private List<String> sendMessageIds;

	@BeforeEach
	@SneakyThrows
	void setup() {
		processMessageDummy = message -> {
		};
		supportCheckedMetadataItems = new ArrayList<>();
		isSupportedDummy = metaData -> true;
		processedMessages = new ArrayList<>();

		testClient = XTA_REMOTE_SERVER_SETUP_EXTENSION.getTestClient();
		silentTestClient = XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentTestClient();
		devClient = XTA_REMOTE_SERVER_SETUP_EXTENSION.getDevClient();
		silentDevClient = XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentDevClient();

		if (Optional.ofNullable(System.getenv("CLEAR_ALL_MESSAGES")).map(d -> d.equals("yes")).orElse(false)) {
			closeAllMessages(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentDevClientConfig(), DEV_READER_CLIENT_IDENTIFIER);
			closeAllMessages(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentTestClientConfig(), TEST_READER_CLIENT_IDENTIFIER);
		}

		failIfAnyMessagePending(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentDevClientConfig(), DEV_READER_CLIENT_IDENTIFIER);
		failIfAnyMessagePending(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentTestClientConfig(), TEST_READER_CLIENT_IDENTIFIER);

		StaticStringListAppender.clearLogLines();
	}

	@AfterEach
	void cleanup() {
		closeMessagesForAllReaders();
	}

	private void closeMessagesForAllReaders() {
		closeMessagesById(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentDevClientConfig(), DEV_READER_CLIENT_IDENTIFIER, sendMessageIds);
		closeMessagesById(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentTestClientConfig(), TEST_READER_CLIENT_IDENTIFIER, sendMessageIds);
	}

	@DisplayName("fetch messages")
	@Nested
	class TestFetchMessages {

		private List<XtaMessage> sendMessages;

		@BeforeEach
		void setup() {
			sendMessages = List.of(
					createMessage("dfoerdermittel", DEV_READER_CLIENT_IDENTIFIER, DEV_READER_CLIENT_IDENTIFIER),
					createMessage("dfoerdermittel", DEV_READER_CLIENT_IDENTIFIER, TEST_READER_CLIENT_IDENTIFIER),
					createMessage("abgabe0401-kleiner-waffenschein", TEST_AUTHOR_CLIENT_IDENTIFIER, TEST_READER_CLIENT_IDENTIFIER),
					createMessage("dfoerdermittel", TEST_READER_CLIENT_IDENTIFIER, TEST_READER_CLIENT_IDENTIFIER)
			);
			sendMessageIds = sendMessages.stream()
					.map(message -> sendTestMessage(
							message.metaData().authorIdentifier().equals(DEV_READER_CLIENT_IDENTIFIER)
									? silentDevClient
									: silentTestClient,
							message))
					.toList();
		}

		@DisplayName("should fetch no messages if no client identifier is configured")
		@Test
		void shouldFetchNoMessagesIfNoClientIdentifierIsConfigured() {
			setupClientsWithIdentifiers(emptyList());

			var messages = fetchMessages();

			assertThat(supportCheckedMetadataItems).isEmpty();
			assertThat(processedMessages).isEmpty();
			assertThat(messages).isEmpty();
		}

		@DisplayName("should fetch no messages if client identifier has no messages pending")
		@Test
		void shouldFetchNoMessagesIfClientIdentifierHasNoMessagesPending() {
			setupClientsWithIdentifiers(List.of(TEST_AUTHOR_CLIENT_IDENTIFIER));

			var messages = fetchMessages();

			assertThat(supportCheckedMetadataItems).isEmpty();
			assertThat(processedMessages).isEmpty();
			assertThat(messages).isEmpty();
		}

		@DisplayName("should fetch messages from first reader")
		@Test
		@SneakyThrows
		void shouldFetchMessagesFromFirstReader() {
			setupClientsWithIdentifiers(List.of(DEV_READER_CLIENT_IDENTIFIER));

			var transportReports = fetchMessages();

			assertThat(supportCheckedMetadataItems).hasSize(1);
			assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.getFirst());
			assertThatTransportReports(transportReports)
					.reportExactlyFor(processedMessages)
					.haveExactlyClosedStatusFor(messageIdBySendIndex(0));
		}

		@DisplayName("should fetch messages from second reader")
		@Test
		void shouldFetchMessagesFromSecondReader() {
			setupClientsWithIdentifiers(List.of(TEST_READER_CLIENT_IDENTIFIER));

			var transportReports = fetchMessages();

			assertThat(supportCheckedMetadataItems).hasSize(3);
			assertThatMessages(processedMessages).containExactlyInAnyOrder(sendMessages.get(1), sendMessages.get(2), sendMessages.get(3));
			assertThatTransportReports(transportReports)
					.reportExactlyFor(processedMessages)
					.haveExactlyClosedStatusFor(messageIdBySendIndex(1), messageIdBySendIndex(2), messageIdBySendIndex(3));
		}

		@DisplayName("should fetch messages from first and second reader")
		@Test
		void shouldFetchMessagesFromFirstAndSecondReader() {
			setupClientsWithIdentifiers(List.of(DEV_READER_CLIENT_IDENTIFIER, TEST_READER_CLIENT_IDENTIFIER));

			var transportReports = fetchMessages();

			assertThat(supportCheckedMetadataItems).hasSize(1 + 3);
			assertThatMessages(processedMessages).containExactlyInAnyOrder(
					sendMessages.get(0),
					sendMessages.get(1), sendMessages.get(2), sendMessages.get(3)
			);
			assertThatTransportReports(transportReports)
					.reportExactlyFor(processedMessages)
					.haveExactlyClosedStatusFor(
							messageIdBySendIndex(0),
							messageIdBySendIndex(1), messageIdBySendIndex(2), messageIdBySendIndex(3)
					);
		}

		@SneakyThrows
		private void setupClientsWithIdentifiers(List<XtaIdentifier> identifiers) {
			devClient = createClientWithIdentifiersAndClientCert(
					identifiers,
					XTA_REMOTE_SERVER_SETUP_EXTENSION.getDevClientConfig().getClientCertKeystore()
			);
			testClient = createClientWithIdentifiersAndClientCert(
					identifiers,
					XTA_REMOTE_SERVER_SETUP_EXTENSION.getTestClientConfig().getClientCertKeystore()
			);
		}

		@SneakyThrows
		private XtaClient createClientWithIdentifiersAndClientCert(List<XtaIdentifier> identifiers, XtaClientConfig.KeyStore clientCertKeyStore) {
			return XtaClient.from(
					XTA_REMOTE_SERVER_SETUP_EXTENSION.createSpecificClientConfigBuilder()
							.clientCertKeystore(clientCertKeyStore)
							.clientIdentifiers(identifiers)
							.maxListItems(TWO_MAX_LIST_ITEMS)
							.isMessageSupported(this::isSupported)
							.build()
			);
		}

		private boolean isSupported(XtaMessageMetaData metaData) {
			supportCheckedMetadataItems.add(metaData);
			return isSupportedDummy.test(metaData);
		}

		private String messageIdBySendIndex(int sendIndex) {
			return sendMessageIds.get(sendIndex);
		}

		private List<XtaTransportReport> fetchMessages() {
			return Stream.concat(
					fetchDevMessages().stream(),
					fetchTestMessages().stream()
			).toList();
		}

		@SneakyThrows
		private List<XtaTransportReport> fetchDevMessages() {
			return devClient.fetchMessages(this::processMessage);
		}

		@SneakyThrows
		private List<XtaTransportReport> fetchTestMessages() {
			return testClient.fetchMessages(this::processMessage);
		}

		private void processMessage(XtaMessage message) {
			processedMessages.add(message);
			processMessageDummy.accept(message);
		}

	}

	@DisplayName("send message")
	@Nested
	class TestSendMessage {

		@DisplayName("should return transport report with open status")
		@SneakyThrows
		@ParameterizedTest
		@ValueSource(strings = { "dfoerdermittel", "abgabe0401-kleiner-waffenschein" })
		void shouldReturn(String messageLabel) {
			XtaMessage xtaMessage = createXdomeaMessage(loadMessage(messageLabel).messageFile());

			var result = sendMessage(xtaMessage);

			assertThat(result.status()).isEqualTo(XtaMessageStatus.OPEN);
		}

		@SneakyThrows
		private XtaTransportReport sendMessage(XtaMessage xtaMessage) {
			var report = testClient.sendMessage(xtaMessage);
			recordMessageIdForCleanup(report);
			return report;
		}

		private void recordMessageIdForCleanup(XtaTransportReport report) {
			var messageId = report.metaData().messageId();
			assertThat(messageId).isNotNull();
			sendMessageIds = List.of(messageId);
		}
	}

	private XtaMessage createMessage(String messageLabel, XtaIdentifier author, XtaIdentifier reader) {
		return XtaMessageExampleLoader.load(
				XtaMessageExampleLoader.MessageExampleConfig.builder()
						.messageLabel(messageLabel)
						.reader(reader)
						.author(author)
						.build());
	}

	@SneakyThrows
	private static XtaMessage createXdomeaMessage(XtaFile messageFile) {
		return XDOMEA_XTA_MESSAGE_CREATOR.createMessage(messageFile);
	}

	private static XtaMessage loadMessage(String messageLabel) {
		return XtaMessageExampleLoader.load(XtaMessageExampleLoader.MessageExampleConfig.builder()
				.messageLabel(messageLabel)
				.author(AUTHOR_CLIENT_IDENTIFIER)
				.reader(READER_CLIENT_IDENTIFIER1)
				.build());
	}

}