package net.northking.cctp.element.internal.utils;

import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.exception.ComponentRuntimeException;
import net.northking.cctp.element.system.FileStorage;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * 多线程下载文件
 *
 * @author fengxiang.zhang
 * @date 2022/4/14
 * @since 1.0
 */
public class FileServer {
    private static final Logger logger= LoggerFactory.getLogger(FileServer.class);
    /**
     * 存放下载文件默认目录
     */
    private final static String DEFAULT_FILE_PATH = FileStorage.BASE_STORAGE_DIR + File.separator + "temp";
    /**
     * 存放SSL证书目录
     */
    private final static String CREDENTIALS_FILE_PATH = DEFAULT_FILE_PATH + File.separator + "ssl";
    /**
     * 文件下载器
     */
    private static final ExecutorService downloader = Executors.newFixedThreadPool(5);
    /**
     * 文件下载任务索引
     */
    private static final ConcurrentHashMap<String, Job> jobMap = new ConcurrentHashMap<>();
    /**
     * JUST LOCKER
     */
    private static Object lock = new Object();
    /**
     * 文件服务器地址
     */
    private static String REMOTE_SERVER_URL;

    private static MinioClient miniolClient;

    /**
     * minio地址
     */
    public static final String MINIO_HOST = "nk.s3.host";

    /**
     * minio端口
     */
    public static final String MINIO_PORT = "nk.s3.port";

    /**
     * minio tls
     */
    public static final String MINIO_TLS = "nk.s3.tls";

    /**
     * minio bucket
     */
    public static final String MINIO_BUCKET = "nk.s3.bucket";

    private static String FILE_BUCKET;

    /**
     * minio access-key
     */
    public static final String MINIO_ACCESSKEY = "nk.s3.access-key";

    /**
     * minio secret-key
     */
    public static final String MINIO_SECRETKEY = "nk.s3.secret-key";

    static {
        try {
            FileUtils.forceMkdir(new File(DEFAULT_FILE_PATH));
            FileUtils.forceMkdir(new File(CREDENTIALS_FILE_PATH));
        } catch (Exception e) {
            logger.error("初始化文件存储目录失败", e);
        }
    }

    /**
     * 获取资源文件
     */
    public static File getFile(IExecuteContext execContext,String fileId, long timeout, TimeUnit unit) {
        return getFile(execContext,DEFAULT_FILE_PATH, fileId, timeout, unit);
    }

    /**
     * 获取ssl证书文件
     */
    public static File getCredentialsFile(IExecuteContext execContext,String fileId, long timeout, TimeUnit unit) {
        return getFile(execContext,CREDENTIALS_FILE_PATH, fileId, timeout, unit);
    }

    /**
     * 获取资源文件
     */
    private static File getFile(IExecuteContext execContext,String parent, String fileId, long timeout, TimeUnit unit) {
        Result result;
        try {
            getMinioClient(execContext);
        } catch (Exception e) {
            throw new ComponentRuntimeException("获取文件服务器地址失败异常", e);
        }
        try {
            // 查看本地是否已经存在
            String localPath = parent + File.separator + StringUtils.replace(fileId, "/", "_");
            File file = new File(localPath);
            if (file.exists() && file.isFile()) {
                return file;
            }
            // 添加到文件下载任务队列中
            CountDownLatch countDownLatch = new CountDownLatch(1);
            String remoteUrl = fileId;
            logger.debug("下载文件任务：{}", remoteUrl);
            Job job = new Job(remoteUrl, localPath).addCountDownLatch(countDownLatch);
            addJob(job);
            // 调用线程等待下载完成或超时
            boolean await = countDownLatch.await(timeout, unit);
            logger.info("调用线程等待下载完成或超时:{}",await);
            result = job.result;
        } catch (Exception e) {
            throw new ComponentRuntimeException("下载文件[" + fileId + "]异常", e);
        }
        if (!result.isSuccess()) {
            throw new ComponentRuntimeException("下载文件[" + fileId + "]失败", result.e);
        }
        return result.dest;
    }

    /**
     * 添加文件下载任务
     */
    private static void addJob(Job job) {
        synchronized (lock) {
            Job current = jobMap.putIfAbsent(job.localPath, job);
            if (current != null) {
                current.countDownLatches.addAll(job.countDownLatches);
            } else {
                current = job;
            }
            Job finalCurrent = current;
            downloader.submit(() -> {
                try {
                    File dest = downloadFile(finalCurrent.remoteUrl, finalCurrent.localPath);
                    finalCurrent.result.success(dest);
                } catch (Exception e) {
                    finalCurrent.result.fail(e);
                } finally {
                    synchronized (lock) {
                        finalCurrent.countDownLatches.forEach(countDownLatch -> {
                            try {
                                countDownLatch.countDown();
                            } catch (Exception e) {
                            }
                        });
                        finalCurrent.countDownLatches.clear();
                        jobMap.remove(finalCurrent.localPath);
                    }
                }
            });
        }
    }

    /**
     * 下载文件
     */
    private static File downloadFile(String remoteUrl, String localPath) throws Exception {
        File dest = new File(localPath);
        if (dest.exists() && dest.isFile()) {
            return dest;
        }
        logger.debug("开始下载文件：{}", remoteUrl);
        String replace = remoteUrl.replace("_", "/");
        InputStream in = null;
        try {
            in = miniolClient.getObject((GetObjectArgs)((io.minio.GetObjectArgs.Builder)((io.minio.GetObjectArgs.Builder)GetObjectArgs.builder().bucket(FILE_BUCKET)).object(replace)).build());
        } catch (Exception var5) {
            throw var5;
        }
        if (!dest.getParentFile().exists()) {
            dest.getParentFile().mkdirs();
        }
        writeFile(in, dest);
        return dest;
    }

    private static void writeFile(InputStream in, File file) throws Exception {
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(file);
            byte[] chs = new byte[1024];
            int len;
            while ((len = in.read(chs)) != -1) {
                out.write(chs, 0, len);
            }
        } finally {
            IOUtils.close(out, in);
        }
    }

    static class Job {

        private String localPath;
        private String remoteUrl;
        private List<CountDownLatch> countDownLatches;
        private Result result;

        public Job(String remoteUrl, String localPath) {
            this.localPath = localPath;
            this.remoteUrl = remoteUrl;
            this.countDownLatches = new ArrayList<>();
            this.result = new Result();
        }

        public Job addCountDownLatch(CountDownLatch countDownLatch) {
            this.countDownLatches.add(countDownLatch);
            return this;
        }

        public String getLocalPath() {
            return localPath;
        }

        public void setLocalPath(String localPath) {
            this.localPath = localPath;
        }

        public String getRemoteUrl() {
            return remoteUrl;
        }

        public void setRemoteUrl(String remoteUrl) {
            this.remoteUrl = remoteUrl;
        }

        public List<CountDownLatch> getCountDownLatches() {
            return countDownLatches;
        }

        public void setCountDownLatches(List<CountDownLatch> countDownLatches) {
            this.countDownLatches = countDownLatches;
        }

        public Result getResult() {
            return result;
        }

        public void setResult(Result result) {
            this.result = result;
        }
    }

    static class Result {
        private boolean success;
        private File dest;
        private Throwable e;

        public Result success(File dest) {
            this.success = true;
            this.dest = dest;
            return this;
        }

        public Result fail(Throwable e) {
            this.success = false;
            this.e = e;
            return this;
        }

        public boolean isSuccess() {
            return success;
        }

        public void setSuccess(boolean success) {
            this.success = success;
        }

        public File getDest() {
            return dest;
        }

        public void setDest(File dest) {
            this.dest = dest;
        }

        public Throwable getE() {
            return e;
        }

        public void setE(Throwable e) {
            this.e = e;
        }
    }


    /**
     * @description: 获取文件服务客户端
     * @date: 2023/2/17 11:12
     *
     */
    private static void getMinioClient(IExecuteContext execContext){
        try{
            //获取minio信息
            String host = execContext.getProperty(MINIO_HOST);
            String port = execContext.getProperty(MINIO_PORT);
            String bucket = execContext.getProperty(MINIO_BUCKET);
            FILE_BUCKET = bucket;
            String tls = execContext.getProperty(MINIO_TLS);
            String accessKey = execContext.getProperty(MINIO_ACCESSKEY);
            String secretKey = execContext.getProperty(MINIO_SECRETKEY);
            MinioClient client = MinioClient.builder().endpoint(host, Integer.valueOf(port), Boolean.valueOf(tls))
                    .credentials(accessKey, secretKey).build();
            miniolClient = client;
        } catch (Exception e)        {
            logger.error("获取文件服务器地址失败!", e);
            throw e;
        }
    }

}
