package ua.be.wee.model.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.asynchttpclient.AsyncCompletionHandler;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.Request;
import org.asynchttpclient.Response;
import org.json.JSONException;
import org.json.JSONObject;

import ua.be.wee.model.EnactmentController;
import ua.be.wee.model.nodes.AutomatedActivity;
import ua.be.wee.model.nodes.ports.ControlInputPort;
import ua.be.wee.model.nodes.ports.ControlOutputPort;
import ua.be.wee.model.nodes.ports.DataOutputPort;
import ua.be.wee.model.pt.PT;
import ua.be.wee.model.pt.TraceArtifact;

public class AsyncHttpClientService {
	
	public static void asyncHTTPClient(PT pt, AutomatedActivity act, ControlInputPort port, Map<String,Pair<String,String>> artifacts, EnactmentController control) throws JSONException, IOException {
		DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config().setConnectTimeout(Duration.ofMillis(act.getTimeout()));
		AsyncHttpClient client = Dsl.asyncHttpClient(clientBuilder);
		
		String requestBody = createJSONBody(port, artifacts);
		
		Request request = Dsl.post(act.getEndpoint()).setBody(requestBody).setRequestTimeout(Duration.ofMillis(act.getTimeout())).build();
		
		client.executeRequest(request, new AsyncCompletionHandler<String>() {
			@Override
		    public String onCompleted(Response response) throws Exception {
				String result = response.getResponseBody();
				int statusCode = response.getStatusCode();
				System.out.println("Status Code: " + statusCode);
				System.out.println("Body: " + result);
				
				if (statusCode == 200) {
					JSONObject res = new JSONObject(result);
					String outCtrl = res.getString("ctrl");
					List<ControlOutputPort> ctrlOutPorts = act.getCtrlOutPorts();
					ControlOutputPort ctrlOutPort = null;
					for (ControlOutputPort controlOutputPort : ctrlOutPorts) {
						if (controlOutputPort.getName().equals(outCtrl)) {
							ctrlOutPort = controlOutputPort;
							break;
						}
					}
					if (ctrlOutPort == null) {
						throw new Exception("The response of automated activity " + act.getName() + " did not return an output control port that matches one of the activity ports.");
					}
					
					JSONObject outputs = res.getJSONObject("output");
					List<TraceArtifact> traceArts = new ArrayList<TraceArtifact>();
					for (Iterator<String> iterator = outputs.keys(); iterator.hasNext();) {
						String outDataName = iterator.next();
						List<DataOutputPort> dataOutPorts = act.getDataOutPorts();
						
						for (DataOutputPort dataOutputPort : dataOutPorts) {
							if (outDataName.equals(dataOutputPort.getName())) {
								JSONObject outData = outputs.getJSONObject(outDataName);
								String type = outData.getString("type");
								if (type.equals("inline")) {
									String content = outData.getString("content");
									String name = outData.getString("name");
									InputStream is = new ByteArrayInputStream(content.getBytes());
									control.uploadArtifact(is,name);
									TraceArtifact tArt = new TraceArtifact();
									tArt.setLocation(name);
									tArt.setRelatesTo(dataOutputPort.getArtifact());
									traceArts.add(tArt);
								} else if (type.equals("reference")) {
									String url = outData.getString("content");
									String name = outData.getString("name");
									CloseableHttpClient httpclient = HttpClients.createDefault();
									HttpGet httpGet = new HttpGet(url);
									HttpResponse resp = httpclient.execute(httpGet);
									InputStream content = resp.getEntity().getContent();
									control.uploadArtifact(content,name);
									TraceArtifact tArt = new TraceArtifact();
									tArt.setLocation(name);
									tArt.setRelatesTo(dataOutputPort.getArtifact());
									traceArts.add(tArt);
									content.close();
									httpclient.close();
								}
							}
						}	
					} 
					Thread.sleep(500);
					control.addEndEvent(pt, traceArts, ctrlOutPort);
					
				} else {
					throw new Exception("The response of automated activity " + act.getName() + " returned an unexpected status code: " + statusCode);
				}
				
				client.close();
				return result;
		    }
			
			@Override
		    public void onThrowable(Throwable t) {
				if (t instanceof TimeoutException) {
					List<ControlOutputPort> ctrlOutPorts = act.getCtrlOutPorts();
					ControlOutputPort ctrlOutPort = null;
					for (ControlOutputPort controlOutputPort : ctrlOutPorts) {
						if (controlOutputPort.getName().equals("error")) {
							ctrlOutPort = controlOutputPort;
							break;
						}
					}
					try {
						if (ctrlOutPort != null) {
							control.addEndEvent(pt, new ArrayList<>(), ctrlOutPort);
						} else {
							System.err.println("Missing 'error' control port!");
						}
						
					} catch (Exception e) {
						System.err.println("Error happened when adding timeout end event in the trace!");
						e.printStackTrace();
					}
					
				} else {
					List<ControlOutputPort> ctrlOutPorts = act.getCtrlOutPorts();
					ControlOutputPort ctrlOutPort = null;
					for (ControlOutputPort controlOutputPort : ctrlOutPorts) {
						if (controlOutputPort.getName().equals("error")) {
							ctrlOutPort = controlOutputPort;
							break;
						}
					}
					try {
						if (ctrlOutPort != null) {
							control.addEndEvent(pt, new ArrayList<>(), ctrlOutPort);
						} else {
							System.err.println("Missing 'error' control port!");
						}
					} catch (Exception e) {
						System.err.println("Error happened when adding error end event in the trace!");
						e.printStackTrace();
					}
				}
		    }
		});
		
	}
	
	private static String createJSONBody(ControlInputPort port, Map<String,Pair<String,String>> params) throws JSONException {
		JSONObject body = new JSONObject();
		JSONObject input = new JSONObject();
		Set<String> keySet = params.keySet();
		for (String dataPort : keySet) {
			JSONObject inner = new JSONObject();
			if (params.get(dataPort).getFst().equals("txt") || 
					params.get(dataPort).getFst().equals("csv") ||
					params.get(dataPort).getFst().equals("xopp")||
					params.get(dataPort).getFst().equals("drawio")) {
				inner.put("type", "inline");
			} else {
				inner.put("type", "reference");
			}
			inner.put("content", params.get(dataPort).getSnd());
			input.put(dataPort, inner);
			
		}
		body.put("input", input);
		body.put("ctrl", port.getName());
		System.out.println(body.toString());
		return body.toString();
	}

}
