package com.codvision.base.config.excel;


import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.EasyExcel;
import com.codvision.authcore.bean.SysUserBean;
import com.codvision.authcore.util.AuthUtil;
import com.codvision.base.config.excel.custom.CustomErrorCellWriteHandler;
import com.codvision.base.entity.SysFile;
import com.codvision.base.service.ISysFileService;
import com.codvision.base.support.TimestampStringConverter;
import com.codvision.base.support.WebSocketServer;
import com.codvision.webcore.bean.QueryPage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static com.codvision.base.support.BaseConst.ZIP_FILE_PREFIX;
import static com.codvision.base.support.BaseConst.ZIP_PATH;

/**
 * 动态表头导出对象
 */
@Slf4j
@SuppressWarnings("all")
public abstract class AbstractDynamicHeaderExoprtClass {


    private static final int DEFAULT_PAGE_SIZE = 1000;
    private ISysFileService sysFileService;
    private WebSocketServer socketServer;

    public AbstractDynamicHeaderExoprtClass() {
        this.sysFileService = SpringUtil.getBean(ISysFileService.class);
        this.socketServer = SpringUtil.getBean(WebSocketServer.class);
    }

    protected abstract List<List<Object>> getList(QueryPage queryPage, Map<String, Object> paraMap);

    /**
     * 动态表头Excel导出
     *
     * @param fileName     文件名称
     * @param paraMap      查询数据库参数
     * @param head         表头
     * @param flagDatabase 是否为通过数据库mapper查询
     * @return
     */
    public String excelExport(String fileName, Map<String, Object> paraMap, List<List<String>> head, Boolean flagDatabase) {
        SysUserBean user = AuthUtil.getCurrentUser();

        String finalFileName = IdUtil.simpleUUID() + fileName;
        //此方法返回可用处理器的虚拟机的最大数量; 不小于1
        AtomicReference<Long> fileSize = new AtomicReference<>();
        int core = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(core, core * 2 + 1, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(15));
        threadPoolExecutor.submit(() -> {
            int index = 1;
            List<Callable<List<List<Object>>>> tasks = new ArrayList<>();
            while (true) {
                QueryPage queryPage = new QueryPage<>(index, DEFAULT_PAGE_SIZE);
                List list = getList(queryPage, paraMap);
                if (CollectionUtils.isEmpty(list)) {
                    break;
                }

                Callable<List<List<Object>>> task = () -> list;
                tasks.add(task);
                index++;
                if (!flagDatabase) {
                    break;
                }
            }

            ZipOutputStream zipout = null;
            InputStream inputStream = null;
            File zip = null;
            try {
                File savePath = new File(ZIP_PATH);
                if (!savePath.exists()) {
                    // 判断导出路径是否存在，不存在创建文件夹
                    savePath.setWritable(true);
                    savePath.setReadable(true);
                    savePath.mkdirs();
                }
                //导出压缩文件的全路径(此地址改成服务器存放地址)
                String zipFilePath = ZIP_PATH + finalFileName + ".zip";
                String zipUploafPath = ZIP_FILE_PREFIX + finalFileName + ".zip";
                //导出zip
                zip = new File(zipFilePath);
                zipout = new ZipOutputStream(new FileOutputStream(zip));
                List<Future<List<List<Object>>>> futures = threadPoolExecutor.invokeAll(tasks);
                int num = 0;
                if (futures.size() > 0) {
                    for (Future<List<List<Object>>> future : futures) {
                        //sheetName页名称
                        String sheetName = "sheetName";
                        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

                        EasyExcel.write(outputStream).head(head)
                                // timestamp转换器，easyexcel不支持转换timestamp
                                .registerConverter(new TimestampStringConverter())
                                .registerWriteHandler(new CustomErrorCellWriteHandler<>(future.get()))
                                .sheet(sheetName).doWrite(future.get());

                        inputStream = new ByteArrayInputStream(outputStream.toByteArray());

                        //excel文件写入zip
                        ZipEntry zipEntry = new ZipEntry(fileName + "-" + num + ".xlsx");
                        zipout.putNextEntry(zipEntry);
                        int len;
                        byte[] buf = new byte[1024];
                        while ((len = inputStream.read(buf)) > 0) {
                            zipout.write(buf, 0, len);
                        }
                        num++;
                    }
                }
                //websocket通知客户端已完成下载，请去excel任务中心下载。需要在登录完成后，配置websocket连接
                log.info("success" + "==========================" + zipUploafPath);
                socketServer.sendInfo("EXPORT_SUCCESS", Math.toIntExact(user.getId()));
                sysFileService.updateIsValidByFileName(ZIP_FILE_PREFIX + finalFileName + ".zip");
                fileSize.set(zip.getTotalSpace());
            } catch (Exception e) {
                log.error(e.getMessage());
                if (e instanceof RejectedExecutionException) {
                    //线程数已达最大值，且队列数已满。无法接收新线程，拒绝新线程任务。
                    log.info("线程数已达最大值，且队列数已满。无法接收新线程，拒绝新线程任务。");
                }
            } finally {
                try {
                    zipout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        //记录下载文件的记录
        SysFile sysFile = new SysFile();
        sysFile.setFileName(ZIP_FILE_PREFIX + finalFileName + ".zip");
        sysFile.setOriginal(fileName + ".zip");
        sysFile.setType("zip");
        sysFile.setFileSize(fileSize.get());
        sysFile.setSource(1);
        sysFile.setIsValid(0);
        //上传位置为ECS服务器
        sysFile.setPosition("2");
        if (ObjectUtil.isNotEmpty(user)) {
            sysFile.setCreateUser(user.getName());
        }
        sysFileService.save(sysFile);
        return ZIP_FILE_PREFIX + finalFileName + ".zip";
    }
}
