package com.avaya.customer.example.servlet.utils;

import static com.avaya.customer.example.servlet.ContextListener.appConfig;
import static com.avaya.customer.example.servlet.utils.Utils.delay;

import com.avaya.collaboration.authorization.AccessToken;
import com.avaya.customer.example.servlet.api.ChatMessage;
import com.avaya.customer.example.servlet.api.EmailTranscript;
import com.avaya.customer.example.servlet.api.MessageTranscriptV1;

import com.avaya.customer.example.servlet.api.MessageTranscriptV2;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javax.net.ssl.SSLContext;

/**
 * Download Manager.
 */
public class DownloadManager {

    private String ssoToken;
    private boolean initialized = false;
    private final int MAX_ATTEMPTS = 30;
    private static final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(50);
    private static final Logger LOG = LoggerFactory.getLogger(DownloadManager.class);

    private SSLContext sslContext;

    public void init() {
        if (!initialized) {
            LOG.info("Init DownloadManager");
            this.initialized = true;

            try {
                KeyStore trustStore = appConfig.getClientTrustStore();

                sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustStrategy() {

                    @Override
                    public boolean isTrusted(X509Certificate[] var1, String var2) throws CertificateException {
                        return false;
                    }
                }).build();
            } catch (Exception e) {
                LOG.error("Exception while loading trust store: ", e);
            }

            this.start();
        }
    }

    void start() {
        Thread t = new Thread(() -> {
            LOG.info("DownloadManger started!");
            int n = 0;
            while (n < MAX_ATTEMPTS) {
                getToken();
                while (ssoToken != null && !ssoToken.equals("invalidToken") && !ssoToken.equals("")) {
                    try {
                        queue.take().run();
                        n = 0;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    delay(5);
                }
                n++;
                LOG.warn("Token is invalid: expired or failed to get. Retry to get after 20 sec");
                delay(20);
            }

            // This will give up retrying after 30 attempts, but it will begin trying again if it gets another
            // transcript request with an attachment
            LOG.warn("Gave up after " + MAX_ATTEMPTS + " failed attempts to get a valid token.");
            this.initialized = false;
        });
        t.start();
    }

    public void downloadAttachments(Object transcript, String filepath) {
        this.init();

        String destDir = filepath + "_attachments/";
        File file = new File(destDir);
        file.mkdirs();

        if (transcript instanceof EmailTranscript) {
            EmailTranscript emailTranscript = (EmailTranscript)transcript;

            for (String attachment : emailTranscript.getAttachments()) {
                downloadAttachment(attachment, destDir);
            }
        } else if (transcript instanceof MessageTranscriptV1) {
            MessageTranscriptV1 messageTranscript = (MessageTranscriptV1)transcript;

            downloadAttachment(messageTranscript.getMessage(), destDir);
        } else if (transcript instanceof MessageTranscriptV2) {
            MessageTranscriptV2 messageTranscript = (MessageTranscriptV2)transcript;

            downloadAttachment(messageTranscript.getMessage(), destDir);
        }else if (transcript instanceof ChatMessage) {
            ChatMessage chatMessage = (ChatMessage)transcript;

            downloadAttachment(chatMessage.getMessage(), destDir);
        }
    }

    private void downloadAttachment(String attachmentInfo, String destDir) {
        try {
            String attachmentId = getAttachmentId(attachmentInfo);
            String attachmentUrl = getAttachmentUrl(attachmentId);
            queue.add(new DownloadTask(attachmentUrl, destDir, attachmentId));
        } catch (Exception e) {
            LOG.error("Error downloading attachment", e);
        }
    }

    private String getAttachmentId(String attachmentUrl) {
        // Filter out the attachment ID from the attachment URL
        return attachmentUrl.replaceFirst(".*/", "");
    }

    private String getAttachmentUrl(String attachmentId) throws ExternalException, ConfigurationException {

        // Build the appropriate download URL
        return appConfig.getAttachmentUrl() + attachmentId;
    }

    private void getToken() {
        try {
            AccessToken accessToken = Authorization.getAccessToken(appConfig);
            if (accessToken != null) {
                ssoToken = accessToken.toString();
                appConfig.setSsoToken(ssoToken);
            }
        } catch (Exception e) {
            LOG.error("Exception thrown while getting access token: ", e);
        }
    }

    class DownloadTask implements Runnable {

        private final String url;
        private final String fileNamePrefix;
        private final String destDir;

        public DownloadTask(String url, String destDir, String fileNamePrefix) {
            this.url = url;
            this.destDir = destDir;
            this.fileNamePrefix = fileNamePrefix;
        }

        @Override
        public void run() {
            LOG.info("New download task started: " + url);
            try {
                HttpClient httpclient = HttpClientBuilder.create().setSslcontext(sslContext).build();
                URI uri = URI.create(url);
                HttpGet httpGet = new HttpGet(uri);
                httpGet.addHeader("Authorization", "Bearer " + ssoToken);
                HttpResponse response = httpclient.execute(httpGet);
                int responseCode = response.getStatusLine().getStatusCode();

                switch (responseCode) {
                    case 200: {
                        LOG.info("Downloading...");
                        InputStream is = response.getEntity().getContent();
                        String disposition = response.getFirstHeader("Content-Disposition").getValue();
                        String fileName = disposition.replaceFirst("(?i)^.*filename=\"?([^\"]+)\"?.*$", "$1");
                        if (fileNamePrefix != null && !fileNamePrefix.isEmpty()) {
                            fileName = fileNamePrefix + "-" + fileName;
                        }

                        String fullPath = destDir + fileName;
                        FileOutputStream fos = new FileOutputStream(new File(fullPath));
                        int inByte;
                        while ((inByte = is.read()) != -1) {
                            fos.write(inByte);
                        }
                        is.close();
                        fos.close();
                        LOG.info("Attachment saved to: " + fullPath);
                        break;
                    }
                    case 500: {
                        LOG.warn("Oceana Server Error: " + response.getStatusLine().getReasonPhrase());
                        break;
                    }
                    case 401: {
                        LOG.warn("Oceana Authentification Error: " + response.getStatusLine().getReasonPhrase());
                        ssoToken = "invalidToken"; // set token to invalid, so the DownloadManager can refresh it
                        queue.add(this); // add this task back to the queue, so DownloadManager can re-execute it after
                        break;
                    }
                    default: {
                        LOG.warn("Unknown response: " + response.getStatusLine().getReasonPhrase());
                        break;
                    }
                }
            } catch (IOException | RuntimeException e) {
                LOG.error("Exception thrown on download attempt: ", e);
            }
        }
    }
}
