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

import java.time.Duration;
import java.util.List;
import java.util.Optional;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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.MqMessageFormat;
import cz.moneta.test.harness.messaging.ReceivedMessage;
import cz.moneta.test.harness.support.auth.Credentials;

/**
 * IBM MQ First Vision endpoint. Provides high-level access to IBM MQ queues
 * with configuration from StoreAccessor.
 * <p>
 * Credentials are loaded from HashiCorp Vault.
 */
public class ImqFirstVisionEndpoint implements Endpoint {

	private static final Logger LOG = LogManager.getLogger(ImqFirstVisionEndpoint.class);

	private final IbmMqConnector connector;
	private final StoreAccessor store;

	private String username, password, keystorePassword;

	// Configuration keys
	private static final String CONNECTION_NAME_LIST_KEY = "endpoints.imq-first-vision.connection-name-list";
	private static final String CHANNEL_KEY = "endpoints.imq-first-vision.channel";
	private static final String QUEUE_MANAGER_KEY = "endpoints.imq-first-vision.queue-manager";
	private static final String SSL_CIPHER_SUITE_KEY = "endpoints.imq-first-vision.ssl-cipher-suite";
	private static final String VAULT_PATH_KEY = "vault.imq-first-vision.secrets.path";
	private static final String VAULT_KEYSTORE_PASSWORD_KEY = "keystorePassword";
	
	private static final String KEYSTORE_PATH = "keystores/imq-keystore.jks";

	/**
	 * Constructor that reads configuration from StoreAccessor.
	 */
	public ImqFirstVisionEndpoint(StoreAccessor store) {
		this.store = store;

		// Read configuration
		String connectionNameList = getConfig(CONNECTION_NAME_LIST_KEY);
		String channel = getConfig(CHANNEL_KEY);
		String queueManager = getConfig(QUEUE_MANAGER_KEY);
		String sslCipherSuite = getConfig(SSL_CIPHER_SUITE_KEY);

		loadCredentialsFromVault();

		try {
			this.connector = new IbmMqConnector(connectionNameList, channel, queueManager, username, password,
					KEYSTORE_PATH, keystorePassword, sslCipherSuite);

			LOG.info("Initialized IBM MQ First Vision endpoint for queue manager: {}", queueManager);

		} catch (Exception e) {
			throw new IllegalStateException("Failed to initialize IBM MQ endpoint", e);
		}
	}

	/**
	 * Get a configuration value from StoreAccessor.
	 */
	private String getConfig(String key) {
		return Optional.ofNullable(store.getConfig(key))
				.orElseThrow(() -> new IllegalStateException("You need to configure " + key));
	}

	/**
	 * Load credentials from HashiCorp Vault.
	 */
	private void loadCredentialsFromVault() {
		try {
			// Get vault URL from configuration
			String vaultPath = getConfig(VAULT_PATH_KEY);
			String vaultUrl = getConfig(HarnessConfigConstants.VAULT_URL_CONFIG);
			String vaultUser = getConfig(HarnessConfigConstants.VAULT_USERNAME_CONFIG);
			String vaultPassword = getConfig(HarnessConfigConstants.VAULT_PASSWORD_CONFIG);

			VaultConnector vaultConnector = new VaultConnector(vaultUrl, vaultUser, vaultPassword);

			Optional<Credentials> credentials = vaultConnector.getUsernameAndPassword(vaultPath);

			if (credentials.isPresent()) {
				this.username = credentials.get().getUsername();
				this.password = credentials.get().getPassword();
				this.keystorePassword = vaultConnector.getValue(vaultPath, VAULT_KEYSTORE_PASSWORD_KEY)
						.map(Object::toString).orElse(null);
				LOG.info("Successfully loaded credentials from Vault for path: {}", vaultPath);
			} else {
				throw new IllegalStateException("Credentials not found in Vault at path: " + vaultPath);
			}
		} catch (Exception e) {
			throw new IllegalStateException("Failed to load credentials from Vault", e);
		}
	}

	/**
	 * Send a message to a queue.
	 *
	 * @param queueName  Physical queue name or logical name (from
	 *                   ImqFirstVisionQueue)
	 * @param payload    Message payload
	 * @param format     Message format
	 * @param properties JMS properties
	 */
	public void send(String queueName, String payload, MqMessageFormat format,
			java.util.Map<String, String> properties) {
		connector.send(queueName, payload, format, properties);
	}

	/**
	 * Send a message to a queue using logical queue name.
	 */
	public void send(ImqFirstVisionQueue queue, String payload, MqMessageFormat format,
			java.util.Map<String, String> properties) {
		String physicalQueueName = resolveQueue(queue);
		connector.send(physicalQueueName, payload, format, properties);
	}

	/**
	 * Receive a message from a queue.
	 *
	 * @param queueName       Physical queue name or logical name
	 * @param messageSelector JMS message selector (optional)
	 * @param format          Expected message format
	 * @param timeout         Timeout duration
	 * @return Received message
	 */
	public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format, Duration timeout) {
		return connector.receive(queueName, messageSelector, format, timeout);
	}

	/**
	 * Receive a message from a queue using logical queue name.
	 */
	public ReceivedMessage receive(ImqFirstVisionQueue queue, String messageSelector, MqMessageFormat format,
			Duration timeout) {
		String physicalQueueName = resolveQueue(queue);
		return connector.receive(physicalQueueName, messageSelector, format, timeout);
	}

	/**
	 * Browse a queue (non-destructive read).
	 *
	 * @param queueName       Physical queue name or logical name
	 * @param messageSelector JMS message selector (optional)
	 * @param format          Expected message format
	 * @param maxMessages     Maximum number of messages
	 * @return List of received messages
	 */
	public List<ReceivedMessage> browse(String queueName, String messageSelector, MqMessageFormat format,
			int maxMessages) {
		return connector.browse(queueName, messageSelector, format, maxMessages);
	}

	/**
	 * Browse a queue using logical queue name.
	 */
	public List<ReceivedMessage> browse(ImqFirstVisionQueue queue, String messageSelector, MqMessageFormat format,
			int maxMessages) {
		String physicalQueueName = resolveQueue(queue);
		return connector.browse(physicalQueueName, messageSelector, format, maxMessages);
	}

	/**
	 * Resolve logical queue name to physical queue name.
	 *
	 * @param logicalName Logical queue name or ImqFirstVisionQueue enum
	 * @return Physical queue name
	 */
	public String resolveQueue(String logicalName) {
		String configKey = "endpoints.imq-first-vision." + logicalName + ".queue";
		return Optional.ofNullable(store.getConfig(configKey)).orElseThrow(
				() -> new IllegalStateException("Queue '" + logicalName + "' is not configured in " + configKey));
	}

	/**
	 * Resolve ImqFirstVisionQueue enum to physical queue name.
	 */
	public String resolveQueue(ImqFirstVisionQueue queue) {
		return resolveQueue(queue.getConfigKey());
	}

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