package cz.moneta.test.harness.endpoints.messaging;

import cz.moneta.test.harness.connectors.VaultConnector;
import cz.moneta.test.harness.connectors.messaging.IbmMqConnector;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
import cz.moneta.test.harness.messaging.model.ReceivedMessage;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;

public class IbmMqEndpoint implements Endpoint {

    private static final String CONFIG_HOST = "messaging.ibmmq.host";
    private static final String CONFIG_PORT = "messaging.ibmmq.port";
    private static final String CONFIG_CHANNEL = "messaging.ibmmq.channel";
    private static final String CONFIG_QUEUE_MANAGER = "messaging.ibmmq.queue-manager";
    private static final String CONFIG_KEYSTORE_PATH = "messaging.ibmmq.keystore.path";
    private static final String CONFIG_KEYSTORE_PASSWORD = "messaging.ibmmq.keystore.password";
    private static final String CONFIG_VAULT_PATH = "vault.path.messaging.ibmmq";
    private static final String CONFIG_USERNAME = "messaging.ibmmq.username";
    private static final String CONFIG_PASSWORD = "messaging.ibmmq.password";
    private static final String CONFIG_SSL_CYPHER_SUITES = "messaging.ibmmq.ssl-cipher-suite";

    private final StoreAccessor store;
    private volatile IbmMqConnector connector;
    private final Object connectorLock = new Object();

    public IbmMqEndpoint(StoreAccessor store) {
        this.store = store;
    }

    public void send(String queueName,
                     String payload,
                     MqMessageFormat format,
                     Map<String, String> properties) {
        getConnector().send(queueName, payload, format, properties);
    }

    public ReceivedMessage receive(String queueName,
                                   String messageSelector,
                                   MqMessageFormat expectedFormat,
                                   Duration timeout) {
        return getConnector().receive(queueName, messageSelector, expectedFormat, timeout);
    }

    public List<ReceivedMessage> browse(String queueName,
                                        Predicate<ReceivedMessage> filter,
                                        MqMessageFormat expectedFormat,
                                        Duration timeout) {
        return getConnector().browse(queueName, filter, expectedFormat, timeout);
    }

    @Override
    public void close() {
        IbmMqConnector current = connector;
        if (current != null) {
            current.close();
        }
    }

    private IbmMqConnector getConnector() {
        IbmMqConnector current = connector;
        if (current == null) {
            synchronized (connectorLock) {
                current = connector;
                if (current == null) {
                    current = createConnector();
                    connector = current;
                }
            }
        }
        return current;
    }

    private IbmMqConnector createConnector() {
        String host = requireConfig(CONFIG_HOST);
        int port = Integer.parseInt(requireConfig(CONFIG_PORT));
        String channel = requireConfig(CONFIG_CHANNEL);
        String queueManager = requireConfig(CONFIG_QUEUE_MANAGER);
        String username = resolveSecret(CONFIG_USERNAME, "username");
        String password = resolveSecret(CONFIG_PASSWORD, "password");
        String keystorePath = store.getConfig(CONFIG_KEYSTORE_PATH);
        String keystorePassword = store.getConfig(CONFIG_KEYSTORE_PASSWORD);
        String sslCipherSuites = store.getConfig(CONFIG_SSL_CYPHER_SUITES);

        return new IbmMqConnector(host, port, channel, queueManager, username, password, keystorePath, keystorePassword, sslCipherSuites);
    }

    private String resolveSecret(String localConfigKey, String vaultKey) {
        return Optional.ofNullable(store.getConfig(localConfigKey))
                .or(() -> readFromVault(vaultKey))
                .orElseThrow(() -> new IllegalStateException("Missing messaging secret: " + localConfigKey));
    }

    private Optional<String> readFromVault(String key) {
        String path = store.getConfig(CONFIG_VAULT_PATH);
        if (path == null || path.isBlank()) {
            return Optional.empty();
        }
        String basePath = store.getConfig("vault.path.base");
        String resolvedPath = path.startsWith("/") || basePath == null || basePath.isBlank()
                ? path
                : basePath + "/" + path;

        return createVaultConnector().flatMap(vault -> vault.getValue(resolvedPath, key));
    }

    private Optional<VaultConnector> createVaultConnector() {
        return Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_URL_CONFIG))
                .flatMap(url -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_USERNAME_CONFIG))
                        .flatMap(username -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_PASSWORD_CONFIG))
                                .map(password -> new VaultConnector(url, username, password))));
    }

    private String requireConfig(String key) {
        return Optional.ofNullable(store.getConfig(key))
                .orElseThrow(() -> new IllegalStateException("Missing required config: " + key));
    }
}
