/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.commons.infrastructure.remote.client;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.security.KeyStore;
import java.security.Security;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import com.scytl.products.ov.commons.infrastructure.config.InfrastructureConfig;
import com.scytl.products.ov.commons.infrastructure.exception.OvCommonsInfrastructureException;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;

import mockit.Mock;
import mockit.MockUp;
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
import retrofit2.Response;

@SuppressWarnings("restriction")
public class RestClientConnectionManagerWithSSLTest {

    private static int sslPort;

    private static final String sslUrl = "/testSSL/";

    private static final String keystoreFilePath =
        System.getProperty("java.io.tmpdir") + File.separator + "tomcat.keystore";

    @BeforeClass
    public static void runOnceSetup() throws IOException {

        Security.addProvider( new BouncyCastleProvider() );
        new MockUp<InfrastructureConfig>() {
            @Mock
            String getRestKeystoreFilepath() {
                return keystoreFilePath;
            }

        };

        setupHttpServerWithSSL();

    }

    private static int discoverFreePorts(int[] ports) throws IOException {
        int result = 0;
        ServerSocket tempServer = null;

        for (int port : ports) {
            try {
                tempServer = new ServerSocket(port);
                result = tempServer.getLocalPort();
                break;

            } catch (IOException ex) {
                continue; // try next port
            }
        }

        if (result == 0) {
            throw new IOException("no free port found");
        }

        tempServer.close();
        return result;
    }

    public static void setupHttpServerWithSSL() {
        try {

            // Copy the keystore file...

            InputStream inStream = null;
            OutputStream outStream = null;

            try {

                File destinationFile = new File(keystoreFilePath);

                inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("tomcat.keystore");
                outStream = new FileOutputStream(destinationFile);

                byte[] buffer = new byte[1024];
                int length;

                while ((length = inStream.read(buffer)) > 0) {
                    outStream.write(buffer, 0, length);
                }

                inStream.close();
                outStream.close();

            } catch (IOException e) {
                e.printStackTrace();
            }

            sslPort = discoverFreePorts(new int[] {8443, 8444, 10, 12, 14, 65535 });

            // setup the socket address
            InetSocketAddress address = new InetSocketAddress(sslPort);

            // initialise the HTTPS server
            HttpsServer httpsServer = HttpsServer.create(address, 0);
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");

            // initialise the keystore
            char[] password = "secretpassword".toCharArray();
            KeyStore ks = KeyStore.getInstance("JKS");

            ks.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("tomcat.keystore"), password);

            // setup the key manager factory
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
            kmf.init(ks, password);

            // setup the trust manager factory
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
            tmf.init(ks);

            // setup the HTTPS context and parameters
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
            httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
                @Override
                public void configure(HttpsParameters params) {
                    try {
                        // initialise the SSL context
                        SSLContext c = SSLContext.getDefault();
                        SSLEngine engine = c.createSSLEngine();
                        params.setNeedClientAuth(true);
                        params.setCipherSuites(engine.getEnabledCipherSuites());
                        params.setProtocols(engine.getEnabledProtocols());

                        // get the default parameters
                        SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();
                        params.setSSLParameters(defaultSSLParameters);

                    } catch (Exception ex) {
                        System.out.println("Failed to create HTTPS port");
                    }
                }
            });
            httpsServer.createContext(sslUrl, new MySSLHandler());
            httpsServer.setExecutor(null); // creates a default executor
            httpsServer.start();

        } catch (Exception exception) {
            throw new RuntimeException("Failed to create HTTPS server on free port of localhost", exception);
        }
    }

    public static class MySSLHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "This is the response.";
            t.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    @Ignore
    @Test
    public void testWithSSL() throws OvCommonsInfrastructureException, RetrofitException {

        String url = "https://localhost:" + sslPort + sslUrl;

        RestClientConnectionManager restClientConnectionManager = RestClientConnectionManager.getInstance();

        Retrofit restAdapter = restClientConnectionManager.getRestClient(url);

        RetrofitTestResponse retrofitTestResponse = restAdapter.create(RetrofitTestResponse.class);

        Response<ResponseBody> response = RetrofitConsumer.executeCall(retrofitTestResponse.getResponse());

        Assert.assertEquals(200, response.code());
    }

}
