package be.iminds.ilabt.jfed.experimenter_gui.bugreporting;

import be.iminds.ilabt.jfed.bugreport.BugReport;
import be.iminds.ilabt.jfed.fedmon.webapi.client.FedmonWebApiClient;
import be.iminds.ilabt.jfed.fedmon.webapi.client.FedmonWebApiClientConfig;
import be.iminds.ilabt.jfed.fedmon.webapi.client.FedmonWebApiClientDirect;
import be.iminds.ilabt.jfed.util.JFedTrustStore;
import be.iminds.ilabt.util.jsonld.iface.JsonLdObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.application.Platform;
import javafx.concurrent.Task;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;

/**
 * User: twalcari
 * Date: 7/17/2014
 * Time: 14:35
 */
public class BugReportToServerTask extends Task<Integer> {
    private static final Logger LOG = LoggerFactory.getLogger(BugReportToServerTask.class);

    private static final String BUG_REPORT_URL = "https://flsmonitor-api.fed4fire.eu/bugreport";

    private final BugReportComposer bugReportComposer;
    private final FedmonWebApiClientDirect fedmonWebApiClient;

    public BugReportToServerTask(BugReportComposer bugReportComposer,
                                 final FedmonWebApiClientConfig origFedmonWebApiClientConfig) {
        this.bugReportComposer = bugReportComposer;

        FedmonWebApiClientConfig noLoggingFedmonWebApiClientConfig = new FedmonWebApiClientConfig() {
            @Override
            public URIBuilder getUriBuilder(Class<? extends JsonLdObject> aClass, boolean b) {
                return origFedmonWebApiClientConfig.getUriBuilder(aClass, b);
            }

            @Override
            public URIBuilder getUriBuilder(Class<? extends JsonLdObject> aClass, String s, boolean b) {
                return origFedmonWebApiClientConfig.getUriBuilder(aClass, s, b);
            }

            @Override
            public JFedTrustStore getTrustStore() {
                return origFedmonWebApiClientConfig.getTrustStore();
            }

            @Override
            public List<X509Certificate> getUserCertificateChain() {
                return origFedmonWebApiClientConfig.getUserCertificateChain();
            }

            @Override
            public PrivateKey getUserPrivateKey() {
                return origFedmonWebApiClientConfig.getUserPrivateKey();
            }

            @Override
            public be.iminds.ilabt.jfed.log.Logger getLogger() {
                //FORCED to null, because:
                //   - bug report calls contain all call logs
                //   - sending multiple bugreports would include all previous bugreport calls
                //   - the size of all calls for a single bugreport grows exponentially with each bugreport in the same session, because of this!
                return null;
            }
        };
        this.fedmonWebApiClient = new FedmonWebApiClientDirect(noLoggingFedmonWebApiClientConfig);

        updateTitle("Submit bugreport '"+bugReportComposer.getSubject()+"'");
    }

    protected Integer call() throws Exception {
        if (!bugReportComposer.isPreparationFinished()) {
            updateMessage("Waiting for connectivity tests to finish");
            updateProgress(-1, 0);


            String dotsString = "";
            while (!bugReportComposer.isPreparationFinished()) {
                dotsString += ".";
                if (dotsString.length() > 3)
                    dotsString = ".";

                updateMessage("Waiting for connectivity tests to finish" + dotsString);
                Thread.sleep(800);
            }
        }



        updateMessage("Preparing bug report");
        updateProgress(0, bugReportComposer.getTotalProgress());
        BugReport bugReport = bugReportComposer.createBugReport();
        updateProgress(bugReportComposer.getProgress(), bugReportComposer.getTotalProgress());

        updateMessage("Sending bug report to Server");
        BugReport createdBugReport = fedmonWebApiClient.create(bugReport);
        Integer createdId = createdBugReport.getId();
        updateProgress(bugReportComposer.getProgress(), bugReportComposer.getTotalProgress());

        updateMessage("Sending call logs to Server");
        bugReportComposer.iterateOverApiCallDetails((id, detail) -> {
//            String callCreateUriString = BUG_REPORT_URL+"/"+createdId+"/call/"+id.getId();
            String callCreateUriString = BUG_REPORT_URL+"/"+createdId+"/call";
            URI callCreateUri;
            try {
                callCreateUri = new URI(callCreateUriString);
            } catch (URISyntaxException e) {
                this.failed();
                LOG.error("Failed to create bugreport call URI: \""+callCreateUriString+"\"", e);
                throw new RuntimeException("Failed to create bugreport call URI \""+callCreateUriString+"\"", e);
            }

            //to xml?
//          SerializableApiCallDetailsWriter.writeApiCallDetails(details, xtw, eventFactory);
//          xtw.flush();

            String jsonData;
            try {
                jsonData = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(detail);
            } catch (JsonProcessingException e) {
                this.failed();
                LOG.error("Error converting call detail to JSON", e);
                throw new RuntimeException("Error converting call detail to JSON", e);
            }
            StringEntity callDataEntity =
                    new StringEntity(jsonData, ContentType.create("application/json", "UTF-8"));

            try {
                fedmonWebApiClient.contactServer(
                        callCreateUri,
                        callDataEntity,
                        FedmonWebApiClientDirect.HttpMethod.POST,
                        null,
                        null
                );
            } catch (FedmonWebApiClient.FedmonWebApiClientException e) {
                LOG.error("Server error: Failed to create bugreport @ "+callCreateUri.toASCIIString(), e);
                Platform.runLater(() -> this.failed());
                throw new RuntimeException("Server error: Failed to create bugreport @ "+callCreateUri.toASCIIString(), e);
            }

            updateProgress(bugReportComposer.getProgress(), bugReportComposer.getTotalProgress());
        });

        return createdBugReport.getId();
    }

    private static class FileBodyWithProgress extends FileBody {
        private OutputStreamProgress outstream;


        private FileBodyWithProgress(File file, String mimeType, String charset) {
            super(file, mimeType, charset);
        }

        @Override
        public void writeTo(OutputStream outstream) throws IOException {
            this.outstream = new OutputStreamProgress(outstream);
            super.writeTo(this.outstream);
        }

        /**
         * Progress: 0-100
         */
        public int getProgress() {
            if (outstream == null) {
                return 0;
            }
            long contentLength = getContentLength();
            if (contentLength <= 0) { // Prevent division by zero and negative values
                return 0;
            }
            long writtenLength = outstream.getWrittenLength();
            return (int) (100 * writtenLength / contentLength);
        }
    }

    private static class OutputStreamProgress extends OutputStream {

        private final OutputStream outstream;
        private volatile long bytesWritten = 0;

        public OutputStreamProgress(OutputStream outstream) {
            this.outstream = outstream;
        }

        @Override
        public void write(int b) throws IOException {
            outstream.write(b);
            bytesWritten++;
        }

        @Override
        public void write(byte[] b) throws IOException {
            outstream.write(b);
            bytesWritten += b.length;
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            outstream.write(b, off, len);
            bytesWritten += len;
        }

        @Override
        public void flush() throws IOException {
            outstream.flush();
        }

        @Override
        public void close() throws IOException {
            outstream.close();
        }

        public long getWrittenLength() {
            return bytesWritten;
        }
    }

    @Override
    protected void failed() {
        super.failed();
        LOG.error("Writing bugreport to server failed: {}",
                getException() != null ? getException().getMessage() : "no error",
                getException());

    }
}
