package io.github.delirius325.jmeter.backendlistener.elasticsearch;

import static io.github.delirius325.jmeter.backendlistener.elasticsearch.ElasticSearchRequests.SEND_BULK_REQUEST;

import java.io.IOException;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;

import org.apache.http.HttpStatus;
import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ElasticSearchMetricSender {
    private static final Logger logger = LoggerFactory.getLogger(ElasticSearchMetricSender.class);
    private RestClient client;
    private String esIndex;
    private List<String> metricList;
    private String authUser;
    private String authPwd;
    private String awsEndpoint;

    public ElasticSearchMetricSender(RestClient cli, String index, String user, String pwd, String endpoint) {
        this.client = cli;
        this.esIndex = index;
        this.metricList = new LinkedList<String>();
        this.authUser = user.trim();
        this.authPwd = pwd.trim();
        this.awsEndpoint = endpoint;
    }

    /**
     * This method returns the current size of the ElasticSearch documents list
     * 
     * @return integer representing the size of the ElasticSearch documents list
     */
    public int getListSize() {
        return this.metricList.size();
    }

    /**
     * This method closes the REST client
     */
    public void closeConnection() throws IOException {
        this.client.close();
    }

    /**
     * This method clears the ElasticSearch documents list
     */
    public void clearList() {
        this.metricList.clear();
    }

    /**
     * This method adds a metric to the list (metricList).
     * 
     * @param metric
     *            String parameter representing a JSON document for ElasticSearch
     */
    public void addToList(String metric) {
        this.metricList.add(metric);
    }

    /**
     * This method sets the Basic Authorization header to requests
     */
    private Request setAuthorizationHeader(Request request) {
        if (this.awsEndpoint.equals("") && !this.authPwd.equals("")) {
            String encodedCredentials = Base64.getEncoder()
                    .encodeToString((this.authUser + ":" + this.authPwd).getBytes());
            RequestOptions.Builder options = request.getOptions().toBuilder();
            options.addHeader("Authorization", "Basic " + encodedCredentials);
            request.setOptions(options);

        }
        return request;
    }

    /**
     * This method creates the ElasticSearch index.
     */
    public void createIndex() {
        try {
            this.client.performRequest(setAuthorizationHeader(new Request("PUT", "/" + this.esIndex)));
        } catch (IOException e) {
            logger.info("Index already exists!");
        }
    }
    
    public int getElasticSearchVersion() {
    	Request request = new Request("GET", "/" );
    	int elasticSearchVersion = -1;
    	 try {
             Response response = this.client.performRequest(setAuthorizationHeader(request));
             if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK && logger.isErrorEnabled()) {
                 logger.error("Unable to perform request to ElasticSearch engine for index {}. Response status: {}",
                              this.esIndex, response.getStatusLine().toString());
             }else {
            	 String responseBody = EntityUtils.toString(response.getEntity());
     			 JSONObject elasticSearchConfig = new JSONObject(responseBody);
     			 JSONObject version  = (JSONObject) elasticSearchConfig.get("version");
     			 String elasticVersion =  version.get("number").toString();
     			 elasticSearchVersion = Integer.parseInt(elasticVersion.split("\\.")[0]);
                 logger.info("ElasticSearch Version : "  + Integer.toString(elasticSearchVersion));
             }
         } catch (Exception e) {
             if (logger.isErrorEnabled()) {
                 logger.error("Exception" + e);
                 logger.error("ElasticSearch Backend Listener was unable to perform request to the ElasticSearch engine. Check your JMeter console for more info.");
             }
         }
    	 return elasticSearchVersion;
    }
    

    /**
     * This method sends the ElasticSearch documents for each document present in the list (metricList). All is being
     * sent through the low-level ElasticSearch REST Client.
     */
    public void sendRequest(int elasticSearchVersionPrefix) {
    	Request request;
    	StringBuilder bulkRequestBody = new StringBuilder();
    	String actionMetaData;
    	if(elasticSearchVersionPrefix < 7) {
    		 request = new Request("POST", "/" + this.esIndex + "/SampleResult/_bulk");
 			 actionMetaData = String.format(SEND_BULK_REQUEST, this.esIndex, "SampleResult");
    	}
    	else {
    		 request = new Request("POST", "/" + this.esIndex + "/_bulk");
    		 actionMetaData = String.format(SEND_BULK_REQUEST, this.esIndex);
    	}
    		
        for (String metric : this.metricList) {
            bulkRequestBody.append(actionMetaData);
            bulkRequestBody.append(metric);
            bulkRequestBody.append("\n");
        }

        request.setEntity(new NStringEntity(bulkRequestBody.toString(), ContentType.APPLICATION_JSON));

        try {

            Response response = this.client.performRequest(setAuthorizationHeader(request));

            if (logger.isErrorEnabled()) {
                if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                    logger.error("ElasticSearch Backend Listener failed to write results for index {}. Response status: {}",
                                 this.esIndex, response.getStatusLine().toString());
                } else {
                    logger.debug("ElasticSearch Backend Listener has successfully written to ES instance [{}] _bulk request {}",
                                 client.getNodes().iterator().next().getHost().toHostString(), request.toString());
                }
            }
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                logger.error("Exception" + e);
                logger.error("Elastic Search Request End Point: " + request.getEndpoint());
                logger.error("ElasticSearch Backend Listener was unable to perform request to the ElasticSearch engine. Check your JMeter console for more info.");
            }
        }
    }

}