package util;

import static java.nio.charset.StandardCharsets.UTF_8;


import api.wsit.product_request.execute.ApiSchema;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import config.ClientConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class HttpClientUtil {

    public static String doRequest(String httpMethod, String apiEndpointUrl, HashMap<String, String> requestUriParameters, HashMap<String, String> requestHeaderJson, String requestPayload) {
        HttpHost proxyHost = null;
        RequestConfig config = null;
        String responseMessage = null;
        HttpGet getRequest = new HttpGet(apiEndpointUrl);
        HttpPost postRequest = new HttpPost(apiEndpointUrl);
        for (Map.Entry<String, String> entry : requestHeaderJson.entrySet()) {
            switch (httpMethod) {
                case HttpGet.METHOD_NAME:
                    getRequest.setHeader(entry.getKey(), entry.getValue());
                    break;
                case HttpPost.METHOD_NAME:
                    if (!entry.getValue().equals(ContentType.MULTIPART_FORM_DATA.getMimeType())) {
                        postRequest.setHeader(entry.getKey(), entry.getValue());
                    }
                    break;
            }
        }
        SSLContext sslContext = null;
        try {
            sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                    return true;
                }
            }).build();
            SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslContext,
                    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            CloseableHttpClient client;
            switch (ClientConfig.proxyMode) {
                case (0):
                    config = RequestConfig.custom().build();
                    client = HttpClients.custom().setSSLSocketFactory(sslcsf).build();
                    break;
                case (1):
                    proxyHost = new HttpHost(ClientConfig.proxyHost, ClientConfig.proxyPort);
                    config = RequestConfig.custom().setProxy(proxyHost).build();
                    client = HttpClients.custom().setSSLSocketFactory(sslcsf).build();
                    break;
                case (2):
                    proxyHost = new HttpHost(ClientConfig.proxyHost, ClientConfig.proxyPort);
                    config = RequestConfig.custom().setProxy(proxyHost).build();
                    CredentialsProvider credsProvider = new BasicCredentialsProvider();
                    //Setting the credentials
                    credsProvider.setCredentials(new AuthScope(ClientConfig.proxyHost, ClientConfig.proxyPort),
                            new UsernamePasswordCredentials(ClientConfig.proxyUser, ClientConfig.proxyPassword));
                    client = HttpClients.custom().setSSLSocketFactory(sslcsf).setDefaultCredentialsProvider(credsProvider).build();
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + ClientConfig.proxyMode);
            }
            CloseableHttpResponse response = null;
            String contentType =  requestHeaderJson.get("Content-Type");
            switch (httpMethod) {
                case HttpGet.METHOD_NAME:
                    getRequest.setConfig(config);
                    URI uri = getRequestUriParameters(getRequest, requestUriParameters);
                    getRequest.setURI(uri);
                    System.out.println("  #a.1. HTTP Request: " + getRequest.getRequestLine() + "\n  #a.2 HTTP Headers:\n" + Arrays.toString(getRequest.getAllHeaders()) + "\n");
                    response = client.execute(getRequest);
                    break;
                case HttpPost.METHOD_NAME:
                    postRequest.setConfig(config);
                    System.out.println("  #a.1. HTTP Request: " + postRequest.getRequestLine() + "\n  #a.2. HTTP Headers:\n" + Arrays.toString(postRequest.getAllHeaders()) + "\n");
                    if (contentType.equals(ContentType.MULTIPART_FORM_DATA.getMimeType())) {
                        JSONObject requestData = JSONObject.parseObject(requestPayload);
                        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
                        for (Map.Entry<String, Object> entry : requestData.entrySet()) {
                            if (entry.getValue() instanceof List) {
                                List<String> files = (List) entry.getValue();
                                files.forEach( file -> {
                                    FileBody fileBody = new FileBody(new File(file), ContentType.DEFAULT_BINARY);
                                    builder.addPart("attachDocument", fileBody);
                                });
                            }
                        }
                        postRequest.setEntity(builder.build());
                    } else {
                        postRequest.setEntity(new StringEntity(requestPayload, ContentType.APPLICATION_JSON.getMimeType(),"UTF-8"));
                    }
                    response = client.execute(postRequest);
                    break;
            }
            HttpEntity entity = response.getEntity();
            responseMessage = EntityUtils.toString(entity);
            System.out.println("  #b.1. Response Status Line: " + response.getStatusLine() + "\n  #b.2. Response Headers: \n" + Arrays.toString(response.getAllHeaders()) + "\n  #b.3. Response Message: \n" + responseMessage);
            return responseMessage;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static URI getRequestUriParameters(HttpGet getRequuest, HashMap<String, String> requestUriParameters) {
        try {
            URIBuilder uri = new URIBuilder(getRequuest.getURI());
            if (requestUriParameters != null) {
                for (Map.Entry<String, String> entry : requestUriParameters.entrySet()) {
                    uri.addParameter(entry.getKey(), entry.getValue());
                }
                return uri.build();
            }
            return uri.build();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static HashMap<String, String> generateRequestHeaders(String httpMethod, String apiSchema, Map token, String clientHostingRegion, String onBehalfOf, Boolean xHsbcCryptoSignature, HashMap<String, String> additionalRequestHeaders) {
        String contentType;
        String requestId = String.valueOf(UUID.randomUUID());
        HashMap<String, String> requestHeaders = new HashMap<String, String>();
        if (clientHostingRegion == "CN") {
            contentType = "text-plain";
        } else {
            contentType = "application/json";
        }
        switch (apiSchema) {
            case (ApiSchema.Edge.SCHEMA_NAME):
                requestHeaders.put("Authorization", "JWS ".concat(token.get("authorization").toString()));
                requestHeaders.put("X-HSBC-Trade-Finance-Token", token.get("xHsbcTradeFinance").toString());
                if (httpMethod != HttpGet.METHOD_NAME) {
                    requestHeaders.put("X-HSBC-Request-Idempotency-Key", requestId);
                }
                requestHeaders.put("X-HSBC-countryCode", clientHostingRegion);
                requestHeaders.put("Content-Type", contentType);
                requestHeaders.put("X-HSBC-Request-Correlation-Id", requestId);
                if (xHsbcCryptoSignature != true) {
                    requestHeaders.put("X-HSBC-Crypto-Signature", String.valueOf(xHsbcCryptoSignature));
                }
                requestHeaders.putAll(additionalRequestHeaders);
                break;
            case (ApiSchema.Gtrf.SCHEMA_NAME):
                requestHeaders.put("Authorization", "JWS ".concat(token.get("authorization").toString()));
                requestHeaders.put("CountryCode", clientHostingRegion);
                requestHeaders.put("Content-Type", contentType);
                requestHeaders.put("requestId", String.valueOf(UUID.randomUUID()).replace("-", ""));
                requestHeaders.put("requestTime", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                requestHeaders.put("schemaVersion", "1.0.0");
                requestHeaders.putAll(additionalRequestHeaders);
                break;
        }
        printHttpRequestHeaders(requestHeaders);
        return requestHeaders;
    }

    public static HashMap<String, String> generateRequestHeadersForFile(String httpMethod, String apiSchema, Map token, String clientHostingRegion, String onBehalfOf, Boolean xHsbcCryptoSignature, HashMap<String, String> additionalRequestHeaders) {
        String contentType;
        String requestId = String.valueOf(UUID.randomUUID());
        HashMap<String, String> requestHeaders = new HashMap<String, String>();
        contentType = "multipart/form-data";
        switch (apiSchema) {
            case (ApiSchema.Edge.SCHEMA_NAME):
                requestHeaders.put("Authorization", "JWS ".concat(token.get("authorization").toString()));
                requestHeaders.put("X-HSBC-Trade-Finance-Token", token.get("xHsbcTradeFinance").toString());
                if (httpMethod != HttpGet.METHOD_NAME) {
                    requestHeaders.put("X-HSBC-Request-Idempotency-Key", requestId);
                }
                requestHeaders.put("X-HSBC-countryCode", clientHostingRegion);
                requestHeaders.put("Content-Type", contentType);
                requestHeaders.put("X-HSBC-Request-Correlation-Id", requestId);
                if (xHsbcCryptoSignature != true) {
                    requestHeaders.put("X-HSBC-Crypto-Signature", String.valueOf(xHsbcCryptoSignature));
                }
                requestHeaders.putAll(additionalRequestHeaders);
                break;
            case (ApiSchema.Gtrf.SCHEMA_NAME):
                requestHeaders.put("Authorization", "JWS ".concat(token.get("authorization").toString()));
                requestHeaders.put("CountryCode", clientHostingRegion);
                requestHeaders.put("Content-Type", contentType);
                requestHeaders.put("requestId", String.valueOf(UUID.randomUUID()).replace("-", ""));
                requestHeaders.put("requestTime", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                requestHeaders.put("schemaVersion", "1.0.0");
                requestHeaders.putAll(additionalRequestHeaders);
                break;
        }
        printHttpRequestHeaders(requestHeaders);
        return requestHeaders;
    }

    public static void printHttpRequestHeaders(HashMap<String, String> headers) {
        for (HashMap.Entry<String, String> entry : headers.entrySet()) {
            System.out.println("    " + entry.getKey() + ":" + entry.getValue());
        }
        System.out.println();
    }
}
