/*
 * 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.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;

import org.apache.commons.io.IOUtils;
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.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.products.ov.commons.infrastructure.exception.OvCommonsInfrastructureException;
import com.scytl.products.ov.keystore.KeystorePasswords;
import com.scytl.products.ov.keystore.KeystorePasswordsReader;
import com.scytl.products.ov.keystore.KeystorePasswordsReaderFactory;
import com.scytl.products.ov.keystore.KeystoreReader;
import com.scytl.products.ov.keystore.KeystoreReaderFactory;
import com.scytl.products.ov.keystore.TestKeystorePasswordsReader;
import com.scytl.products.ov.keystore.TestKeystoreReader;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

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

@SuppressWarnings("restriction")
public class RestClientConnectionManagerTest {

    private static int httpPort;

    private static final String urlWithout_Interceptors = "/urlWithout_Interceptors";

    private static final String urlWith_Interceptors = "/urlWith_Interceptors";

    @BeforeClass
    public static void runOnceSetup() throws IOException {
        Security.addProvider(new BouncyCastleProvider());
        setupHttpServer();
    }

    private static int discoverFreePorts(int from, int to) throws IOException {
        int result = 0;
        ServerSocket tempServer = null;

        for (int i = from; i <= to; i++) {
            try {
                tempServer = new ServerSocket(i);
                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 setupHttpServer() {
        try {

            httpPort = discoverFreePorts(50000, 60000);

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

            // initialise the HTTP server
            HttpServer httpServer = HttpServer.create(address, 0);
            httpServer.createContext(urlWithout_Interceptors + "/", new HandlerForUrlWithout_Interceptors());
            httpServer.createContext(urlWith_Interceptors + "/", new HandlerForUrlWith_Interceptors());

            httpServer.setExecutor(null); // creates a default executor
            httpServer.start();

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

        }
    }

    public static class HandlerForUrlWithout_Interceptors implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {

            String response = "This is the response from HandlerForUrlWithout_Interceptors.";

            t.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    public static class HandlerForUrlWith_Interceptors implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {

            String requestBody = IOUtils.toString(t.getRequestBody(), "UTF-8");

            String response = "This is the response from HandlerForUrlWith_Interceptors. Request body: " + requestBody;

            System.out.println(response);

            if (t.getRequestHeaders().containsKey(RestClientInterceptor.HEADER_SIGNATURE)) {

                t.getResponseHeaders().add(RestClientInterceptor.HEADER_SIGNATURE,
                    t.getRequestHeaders().get(RestClientInterceptor.HEADER_SIGNATURE).toString());
            }

            t.getResponseHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN);
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    @Test
    public void testClientWithoutInterceptor() throws OvCommonsInfrastructureException, RetrofitException {

        String url = "http://localhost:" + httpPort + urlWithout_Interceptors;

        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());
    }

    private String getSignatureFromResponseHeaders(Response<?> response) {
        String result = "";

        for (String header : response.headers().names()) {
            if (RestClientInterceptor.HEADER_SIGNATURE.equals(header)) {
                result = response.headers().get(header);
                break;
            }
        }
        return result;
    }


    @Test
    public void testClientWithInterceptor() throws OvCommonsInfrastructureException, RetrofitException, GeneralCryptoLibException {

        String url = "http://localhost:" + httpPort + urlWith_Interceptors;

        RestClientConnectionManager restClientConnectionManager = RestClientConnectionManager.getInstance();

        AsymmetricService asymmetricService = new AsymmetricService();
        KeyPair keyPair = asymmetricService.getKeyPairForSigning();

        Retrofit restAdapter = restClientConnectionManager.getRestClientWithInterceptor(url, keyPair.getPrivate(),
            NodeIdentifier.SECURE_DATA_MANAGER);

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

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

        String signature = getSignatureFromResponseHeaders(response);

        Assert.assertEquals(200, response.code());
        Assert.assertNotNull(signature);

    }

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

        String url = "http://localhost:" + httpPort + urlWith_Interceptors;

        new MockUp<KeystoreReaderFactory>() {
            @Mock
            public KeystoreReader getInstance() {
                return TestKeystoreReader.getInstance();
            }
        };
        new MockUp<KeystorePasswordsReaderFactory>() {
            @Mock
            public KeystorePasswordsReader getInstance(final NodeIdentifier nodeIdentifier) {
                return TestKeystorePasswordsReader.getInstance(nodeIdentifier);
            }
        };

        // The following mock is so that tests won't fail in the Jenkins environment
        // because of Bouncy Castle apparently not getting initialized in the HttpServer.
        AsymmetricService asymmetricService = new AsymmetricService();
        KeyPair keyPair = asymmetricService.getKeyPairForSigning();
        new MockUp<TestKeystoreReader>() {
            @Mock
            PrivateKey readSigningPrivateKey(NodeIdentifier nodeIdentifier, KeystorePasswords keystorePasswords) {
                return keyPair.getPrivate();
            };
        };

        RestClientConnectionManager restClientConnectionManager = RestClientConnectionManager.getInstance();

        Retrofit restAdapter = restClientConnectionManager.getConfigurationRestClientWithInterceptor(url,
            NodeIdentifier.SECURE_DATA_MANAGER);

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

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

        String signature = getSignatureFromResponseHeaders(response);

        Assert.assertEquals(200, response.code());
        Assert.assertNotNull(signature);

    }

    @Test
    public void testClientWithInterceptorAndQueryParams()
            throws OvCommonsInfrastructureException, GeneralCryptoLibException, RetrofitException {

        String url = "http://localhost:" + httpPort + urlWith_Interceptors;

        RestClientConnectionManager restClientConnectionManager = RestClientConnectionManager.getInstance();

        Security.addProvider(new BouncyCastleProvider());
        AsymmetricService asymmetricService = new AsymmetricService();
        KeyPair keyPair = asymmetricService.getKeyPairForSigning();

        Retrofit restAdapter = restClientConnectionManager.getRestClientWithInterceptor(url, keyPair.getPrivate(),
            NodeIdentifier.SECURE_DATA_MANAGER);

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

        Response<ResponseBody> response = RetrofitConsumer.executeCall(retrofitTestResponse.getResponseWithQueryParams("p1", "p2"));

        String signature = getSignatureFromResponseHeaders(response);

        Assert.assertEquals(200, response.code());
        Assert.assertNotNull(signature);

    }

    @Test
    public void testPostClientWithInterceptor() throws OvCommonsInfrastructureException, GeneralCryptoLibException, RetrofitException {

        String url = "http://localhost:" + httpPort + urlWith_Interceptors;

        RestClientConnectionManager restClientConnectionManager = RestClientConnectionManager.getInstance();

        Security.addProvider(new BouncyCastleProvider());
        AsymmetricService asymmetricService = new AsymmetricService();
        KeyPair keyPair = asymmetricService.getKeyPairForSigning();

        Retrofit restAdapter = restClientConnectionManager.getRestClientWithInterceptor(url, keyPair.getPrivate(),
            NodeIdentifier.SECURE_DATA_MANAGER);

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

        RequestBody body = RequestBody.create(okhttp3.MediaType.parse("text"), ("this is a test body"));

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

        String signature = getSignatureFromResponseHeaders(response);

        Assert.assertEquals(200, response.code());
        Assert.assertNotNull(signature);

    }

    @Test
    public void testPostPojo() throws OvCommonsInfrastructureException, GeneralCryptoLibException, RetrofitException, IOException  {

        String url = "http://localhost:" + httpPort + urlWith_Interceptors;

        RestClientConnectionManager restClientConnectionManager = RestClientConnectionManager.getInstance();

        Security.addProvider(new BouncyCastleProvider());
        AsymmetricService asymmetricService = new AsymmetricService();
        KeyPair keyPair = asymmetricService.getKeyPairForSigning();

        Retrofit restAdapter = restClientConnectionManager.getRestClientWithInterceptorAndJacksonConverter(url, keyPair.getPrivate(),
            NodeIdentifier.SECURE_DATA_MANAGER);

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

        TestPojo testPojo = new TestPojo();

        testPojo.setProperty1("simpleText");

        String complexText =
            "-----BEGIN CERTIFICATE-----\n" + "MIIDhTCCAm2gAwIBAgIUViDChuk4vZyQR7F9nqXXEb7kLUAwDQYJKoZIhvcNAQEL\n"
                + "BQAwWDEWMBQGA1UEAwwNVGVuYW50IDEwMCBDQTEWMBQGA1UECwwNT25saW5lIFZv\n"
                + "dGluZzEOMAwGA1UECgwFU2N5dGwxCTAHBgNVBAcMADELMAkGA1UEBhMCRVMwHhcN\n"
                + "MTcwMTEwMTIwMDU2WhcNMTcwNTMxMjI1OTU5WjB/MT0wOwYDVQQDDDRBZG1pbmlz\n"
                + "dHJhdGlvbkJvYXJkIGRiZmJlODhmNzI0MzQxYTZhMTQ5YjI2Zjc1MjVmMzhkMRYw\n"
                + "FAYDVQQLDA1PbmxpbmUgVm90aW5nMQ4wDAYDVQQKDAVTY3l0bDEJMAcGA1UEBwwA\n"
                + "MQswCQYDVQQGEwJFUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIHm\n"
                + "RGpLMyqh6omBLry8642zQ9KRCA+fJUIietkY0LUeisDL/LU0rOUM9Mrzem2vtn7f\n"
                + "zEV97rW9xaLLrLEmDGcXzuFC5DaXitl3wwEtmucO7ROrMNZCzgUYO8owMAhbboAh\n"
                + "EWfsB07NgZ5UmnwQXjHDXVmQ0XhNpFVHQ78Oo+POdm3tJs94l2QgerOaWOPENOHu\n"
                + "TmU/Dj3xHtK2MMchNtk+j5VWmHmQwHDOtpmNjARlrXQHpU9aC6TZS6N9q9oUJJ3Z\n"
                + "r9IRg0j5GQMxl4nzxt+HkKLnsUMsYmzeLM7BS2gk8fo45i/OSl9pXk54t1NoWK2Y\n"
                + "mari/eDInv4/5T/x3u8CAwEAAaMgMB4wDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB\n"
                + "/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAA4FEqv2Z53obzoxeG9rPG9AGZBV3j66\n"
                + "l5DKOswlkG3fPOghYhSJ8h5T5P4idP7acLcdz+AFnlMcfZItsY/wo5Xva4ZObR1J\n"
                + "N1F67tMCM6FUSpgZECmhqlnuBMM4MrPVW5DIpQxQNBNZ+kALUx/iWZkMKqmXse6p\n"
                + "MHBI0vJYnW5JXS4PddoYjxnHree7piXLia4uC30ON08/TayBuYNCy8K96k1YP83v\n"
                + "dMs2oMK/mavopHXWG+DF9OBwuhbSdb1r5gXk4dhUdmxo7taSD9lN19fbWHAoDmDK\n"
                + "C2Mn7olmRrpYx+ir9oU93jU6xt34S8VIqeD2Xk4EuqTU5+F43oltsRY=\n" + "-----END CERTIFICATE-----\n";

        String sanitizedText = complexText.replace("\n", " ");

        testPojo.setProperty2(sanitizedText);
        
        Response<ResponseBody> response = RetrofitConsumer.executeCall(retrofitTestResponse.postPojo(testPojo));

        String signature = getSignatureFromResponseHeaders(response);
        
        String responseBody = new String(response.body().bytes(), StandardCharsets.UTF_8);

        System.out.println(responseBody);

        Assert.assertEquals(200, response.code());
        Assert.assertNotNull(signature);
        Assert.assertFalse(responseBody.contains("\\\u003d"));
        Assert.assertFalse(responseBody.contains("\n"));

    }

}
