/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.kafka.connect.runtime.rest.util;

import org.apache.kafka.common.config.SslConfigs;
import org.apache.kafka.common.config.types.Password;
import org.apache.kafka.common.network.CertStores;
import org.apache.kafka.connect.runtime.rest.RestServerConfig;

import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class SSLUtilsTest {

    private String keystorePath;
    private String truststorePath;
    private Password keystorePassword;
    private Password truststorePassword;

    @BeforeEach
    public void before() throws Exception {
        CertStores serverCertStores = new CertStores(true, "localhost");
        Map<String, Object> sslConfig = serverCertStores.getUntrustingConfig();
        keystorePath = sslConfig.get("ssl.keystore.location").toString();
        truststorePath = sslConfig.get("ssl.truststore.location").toString();
        keystorePassword = (Password) sslConfig.get("ssl.keystore.password");
        truststorePassword = (Password) sslConfig.get("ssl.keystore.password");
    }

    @Test
    public void testGetOrDefault() {
        String existingKey = "exists";
        String missingKey = "missing";
        String value = "value";
        String defaultValue = "default";
        Map<String, Object> map = new HashMap<>();
        map.put("exists", "value");

        assertEquals(value, SSLUtils.getOrDefault(map, existingKey, defaultValue));
        assertEquals(defaultValue, SSLUtils.getOrDefault(map, missingKey, defaultValue));
    }

    @Test
    public void testCreateServerSideSslContextFactory() {
        Map<String, String> configMap = new HashMap<>();
        configMap.put("ssl.keystore.location", keystorePath);
        configMap.put("ssl.keystore.password", keystorePassword.value());
        configMap.put("ssl.key.password", keystorePassword.value());
        configMap.put("ssl.truststore.location", truststorePath);
        configMap.put("ssl.truststore.password", truststorePassword.value());
        configMap.put("ssl.provider", "SunJSSE");
        configMap.put("ssl.cipher.suites", "SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5");
        configMap.put("ssl.secure.random.implementation", "SHA1PRNG");
        configMap.put("ssl.client.auth", "required");
        configMap.put("ssl.endpoint.identification.algorithm", "HTTPS");
        configMap.put("ssl.keystore.type", "JKS");
        configMap.put("ssl.protocol", "TLS");
        configMap.put("ssl.truststore.type", "JKS");
        configMap.put("ssl.enabled.protocols", "TLSv1.2,TLSv1.1,TLSv1");
        configMap.put("ssl.keymanager.algorithm", "SunX509");
        configMap.put("ssl.trustmanager.algorithm", "PKIX");

        RestServerConfig config = RestServerConfig.forPublic(null, configMap);
        SslContextFactory.Server ssl = SSLUtils.createServerSideSslContextFactory(config);

        assertEquals("file://" + keystorePath, ssl.getKeyStorePath());
        assertEquals("file://" + truststorePath, ssl.getTrustStorePath());
        assertEquals("SunJSSE", ssl.getProvider());
        assertArrayEquals(new String[] {"SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_RC4_128_MD5"}, ssl.getIncludeCipherSuites());
        assertEquals("SHA1PRNG", ssl.getSecureRandomAlgorithm());
        assertTrue(ssl.getNeedClientAuth());
        assertFalse(ssl.getWantClientAuth());
        assertEquals("JKS", ssl.getKeyStoreType());
        assertEquals("JKS", ssl.getTrustStoreType());
        assertEquals("TLS", ssl.getProtocol());
        assertArrayEquals(new String[] {"TLSv1.2", "TLSv1.1", "TLSv1"}, ssl.getIncludeProtocols());
        assertEquals("SunX509", ssl.getKeyManagerFactoryAlgorithm());
        assertEquals("PKIX", ssl.getTrustManagerFactoryAlgorithm());
    }

    @Test
    public void testCreateClientSideSslContextFactory() {
        Map<String, String> configMap = new HashMap<>();
        configMap.put("ssl.keystore.location", keystorePath);
        configMap.put("ssl.keystore.password", keystorePassword.value());
        configMap.put("ssl.key.password", keystorePassword.value());
        configMap.put("ssl.truststore.location", truststorePath);
        configMap.put("ssl.truststore.password", truststorePassword.value());
        configMap.put("ssl.provider", "SunJSSE");
        configMap.put("ssl.cipher.suites", "SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5");
        configMap.put("ssl.secure.random.implementation", "SHA1PRNG");
        configMap.put("ssl.client.auth", "required");
        configMap.put("ssl.endpoint.identification.algorithm", "HTTPS");
        configMap.put("ssl.keystore.type", "JKS");
        configMap.put("ssl.protocol", "TLS");
        configMap.put("ssl.truststore.type", "JKS");
        configMap.put("ssl.enabled.protocols", "TLSv1.2,TLSv1.1,TLSv1");
        configMap.put("ssl.keymanager.algorithm", "SunX509");
        configMap.put("ssl.trustmanager.algorithm", "PKIX");

        RestServerConfig config = RestServerConfig.forPublic(null, configMap);
        SslContextFactory.Client ssl = SSLUtils.createClientSideSslContextFactory(config);

        assertEquals("file://" + keystorePath, ssl.getKeyStorePath());
        assertEquals("file://" + truststorePath, ssl.getTrustStorePath());
        assertEquals("SunJSSE", ssl.getProvider());
        assertArrayEquals(new String[] {"SSL_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_RC4_128_MD5"}, ssl.getIncludeCipherSuites());
        assertEquals("SHA1PRNG", ssl.getSecureRandomAlgorithm());
        assertEquals("JKS", ssl.getKeyStoreType());
        assertEquals("JKS", ssl.getTrustStoreType());
        assertEquals("TLS", ssl.getProtocol());
        assertArrayEquals(new String[] {"TLSv1.2", "TLSv1.1", "TLSv1"}, ssl.getIncludeProtocols());
        assertEquals("SunX509", ssl.getKeyManagerFactoryAlgorithm());
        assertEquals("PKIX", ssl.getTrustManagerFactoryAlgorithm());
    }

    @Test
    public void testCreateServerSideSslContextFactoryDefaultValues() {
        Map<String, String> configMap = new HashMap<>();
        configMap.put("ssl.keystore.location", keystorePath);
        configMap.put("ssl.keystore.password", keystorePassword.value());
        configMap.put("ssl.key.password", keystorePassword.value());
        configMap.put("ssl.truststore.location", truststorePath);
        configMap.put("ssl.truststore.password", truststorePassword.value());
        configMap.put("ssl.provider", "SunJSSE");
        configMap.put("ssl.cipher.suites", "SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5");
        configMap.put("ssl.secure.random.implementation", "SHA1PRNG");

        RestServerConfig config = RestServerConfig.forPublic(null, configMap);
        SslContextFactory.Server ssl = SSLUtils.createServerSideSslContextFactory(config);

        assertEquals(SslConfigs.DEFAULT_SSL_KEYSTORE_TYPE, ssl.getKeyStoreType());
        assertEquals(SslConfigs.DEFAULT_SSL_TRUSTSTORE_TYPE, ssl.getTrustStoreType());
        assertEquals(SslConfigs.DEFAULT_SSL_PROTOCOL, ssl.getProtocol());
        assertArrayEquals(List.of(SslConfigs.DEFAULT_SSL_ENABLED_PROTOCOLS.split("\\s*,\\s*")).toArray(), ssl.getIncludeProtocols());
        assertEquals(SslConfigs.DEFAULT_SSL_KEYMANGER_ALGORITHM, ssl.getKeyManagerFactoryAlgorithm());
        assertEquals(SslConfigs.DEFAULT_SSL_TRUSTMANAGER_ALGORITHM, ssl.getTrustManagerFactoryAlgorithm());
        assertFalse(ssl.getNeedClientAuth());
        assertFalse(ssl.getWantClientAuth());
    }

    @Test
    public void testCreateServerSideSslContextFactoryWithListenerPrefixedConfigs() {
        Map<String, String> configMap = new HashMap<>();
        // Use listener-prefixed SSL configs (but not all of them).
        // This triggers the "all" branch of valuesWithPrefixAllOrNothing,
        // which returns only prefixed entries — keys without a prefixed
        // counterpart (like ssl.cipher.suites) will be absent from the map.
        configMap.put("listeners.https.ssl.keystore.location", keystorePath);
        configMap.put("listeners.https.ssl.keystore.password", keystorePassword.value());
        configMap.put("listeners.https.ssl.key.password", keystorePassword.value());
        configMap.put("listeners.https.ssl.truststore.location", truststorePath);
        configMap.put("listeners.https.ssl.truststore.password", truststorePassword.value());

        RestServerConfig config = RestServerConfig.forPublic(null, configMap);
        assertDoesNotThrow(() -> SSLUtils.createServerSideSslContextFactory(config));
    }

    @Test
    public void testCreateClientSideSslContextFactoryWithListenerPrefixedConfigs() {
        Map<String, String> configMap = new HashMap<>();
        configMap.put("listeners.https.ssl.keystore.location", keystorePath);
        configMap.put("listeners.https.ssl.keystore.password", keystorePassword.value());
        configMap.put("listeners.https.ssl.key.password", keystorePassword.value());
        configMap.put("listeners.https.ssl.truststore.location", truststorePath);
        configMap.put("listeners.https.ssl.truststore.password", truststorePassword.value());

        RestServerConfig config = RestServerConfig.forPublic(null, configMap);
        assertDoesNotThrow(() -> SSLUtils.createClientSideSslContextFactory(config));
    }

    @Test
    public void testCreateClientSideSslContextFactoryDefaultValues() {
        Map<String, String> configMap = new HashMap<>();
        configMap.put("ssl.keystore.location", keystorePath);
        configMap.put("ssl.keystore.password", keystorePassword.value());
        configMap.put("ssl.key.password", keystorePassword.value());
        configMap.put("ssl.truststore.location", truststorePath);
        configMap.put("ssl.truststore.password", truststorePassword.value());
        configMap.put("ssl.provider", "SunJSSE");
        configMap.put("ssl.cipher.suites", "SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5");
        configMap.put("ssl.secure.random.implementation", "SHA1PRNG");

        RestServerConfig config = RestServerConfig.forPublic(null, configMap);
        SslContextFactory.Client ssl = SSLUtils.createClientSideSslContextFactory(config);

        assertEquals(SslConfigs.DEFAULT_SSL_KEYSTORE_TYPE, ssl.getKeyStoreType());
        assertEquals(SslConfigs.DEFAULT_SSL_TRUSTSTORE_TYPE, ssl.getTrustStoreType());
        assertEquals(SslConfigs.DEFAULT_SSL_PROTOCOL, ssl.getProtocol());
        assertArrayEquals(List.of(SslConfigs.DEFAULT_SSL_ENABLED_PROTOCOLS.split("\\s*,\\s*")).toArray(), ssl.getIncludeProtocols());
        assertEquals(SslConfigs.DEFAULT_SSL_KEYMANGER_ALGORITHM, ssl.getKeyManagerFactoryAlgorithm());
        assertEquals(SslConfigs.DEFAULT_SSL_TRUSTMANAGER_ALGORITHM, ssl.getTrustManagerFactoryAlgorithm());
    }
}
