/*
 * 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.shardingsphere.proxy.frontend.ssl;

import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
import org.apache.shardingsphere.infra.config.props.ConfigurationPropertyKey;
import org.apache.shardingsphere.proxy.backend.context.ProxyContext;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.Arrays;

/**
 * Proxy SSL context.
 */
@Slf4j
public final class ProxySSLContext {
    
    private static final ProxySSLContext INSTANCE = new ProxySSLContext();
    
    private SslContext sslContext;
    
    /**
     * Init SSL context.
     *
     * @throws SSLException SSL exception
     */
    public static void init() throws SSLException {
        ConfigurationProperties props = ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData().getProps();
        if (!props.<Boolean>getValue(ConfigurationPropertyKey.PROXY_FRONTEND_SSL_ENABLED)) {
            log.info("Proxy frontend SSL/TLS is disabled.");
            return;
        }
        SslContextBuilder sslContextBuilder = prepareSSLContextBuilder();
        String versions = props.<String>getValue(ConfigurationPropertyKey.PROXY_FRONTEND_SSL_VERSION).trim();
        sslContextBuilder.protocols(versions.split(","));
        String ciphers = props.<String>getValue(ConfigurationPropertyKey.PROXY_FRONTEND_SSL_CIPHER).trim();
        if (!ciphers.isEmpty()) {
            sslContextBuilder.ciphers(Arrays.asList(ciphers.split(",")));
        }
        INSTANCE.sslContext = sslContextBuilder.build();
        log.info("Proxy frontend SSL/TLS is enabled. Supported protocols: {}", versions);
    }
    
    private static SslContextBuilder prepareSSLContextBuilder() {
        KeyPair keyPair = SSLUtils.generateRSAKeyPair();
        X509Certificate x509Certificate = SSLUtils.generateSelfSignedX509Certificate(keyPair);
        SslContextBuilder result = SslContextBuilder.forServer(keyPair.getPrivate(), x509Certificate);
        log.info("RSA key pair and CA certificate are generated by Proxy and self-signed.");
        return result;
    }
    
    /**
     * Get instance.
     *
     * @return got instance 
     */
    public static ProxySSLContext getInstance() {
        return INSTANCE;
    }
    
    /**
     * Is SSL enabled.
     *
     * @return is SSL enabled
     */
    public boolean isSSLEnabled() {
        return null != sslContext;
    }
    
    /**
     * Create a new {@link SSLEngine}.
     *
     * @param allocator allocator
     * @return a new {@link SSLEngine}
     */
    public SSLEngine newSSLEngine(final ByteBufAllocator allocator) {
        return sslContext.newEngine(allocator);
    }
}
