package com.yida.data.common.excel;

import cc.mrbird.febs.common.redis.service.RedisService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yida.data.common.core.entity.constant.CachePrefixConstant;
import com.yida.data.common.core.enums.FileDealStatusEnum;
import com.yida.data.common.core.file.IExportLargeClient;
import com.yida.data.common.core.file.common.DownloadFileChunk;
import com.yida.data.common.core.file.common.export.ExportLargeData;
import com.yida.data.common.core.file.common.export.SelectExportData;
import com.yida.data.common.core.exception.FebsException;
import com.yida.data.common.core.utils.file.ChunkUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.StopWatch;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 导出大文件工具类
 * zip文件
 *
 * @author ZYJ
 * @date 2023/10/19 16:49
 */
@Data
@Slf4j
@AllArgsConstructor
public abstract class DefaultExportLargeClient<T extends SelectExportData, O, BaseService extends IService<O>>
        implements IExportLargeClient<T> {

    /**
     * 导出的service层
     */
    protected final BaseService baseService;

    protected final RedisService redisService;
    protected final String exportCacheKey;
    protected final String uploadUrl;

    /**
     * 查询大文件导出需要的数据
     *
     * @param t 查询条件
     * @return java.util.Map<java.lang.String, java.lang.String>
     * @author ZYJ
     * @date 2023/10/20 16:09
     */
    public abstract Map<String, String> selectExportData(T t);

    @Override
    public void exportLargeData(T t) {
        // 查询需要导出的文件信息
        Map<String, String> map = this.selectExportData(t);
        // zip文件
        File file = null;
        // 错误信息文件
        File errorFile = null;
        try {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            // 生成的文件名称
            String fileName = com.yida.data.common.core.utils.FileUtil.getLocalUploadAddress() + t.getKey() + ".zip";
            file = new File(fileName);
            ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(file.toPath()));
            // 添加处理总数量
            redisService.hset(this.exportCacheKey, t.getKey(), ExportLargeData.builder()
                    .key(t.getKey())
                    .exportStatus(FileDealStatusEnum.DEALING.getStatus())
                    .total(map.size())
                    .finish(0)
                    .build());

            // 完成数量
            int finishNumber = 0;
            // 错误数量
            int errorNumber = 0;
            // 是否完成流程
            boolean flag;
            // 错误信息
            Map<String, String> errorMap = new HashMap<>();

            for (Map.Entry<String, String> entry : map.entrySet()) {
                flag = true;
                String name = "";
                String fileUrl = "";
                try {
                    // 文件名称
                    name = entry.getKey();
//                    log.info("处理数据: {}", name);
                    // 文件地址
                    fileUrl = entry.getValue();
                    // 下载文件
                    URL url = new URL(fileUrl);
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    String message = connection.getHeaderField(0);
                    if (StringUtils.isNotEmpty(message)) {
                        if (message.startsWith("HTTP/1.1 404")) {
                            flag = false;
                            // 添加错误数据
                            errorMap.put(name, fileUrl);
                            errorNumber += 1;
                            finishNumber += 1;

                            redisService.hset(this.exportCacheKey, t.getKey(), ExportLargeData.builder()
                                    .key(t.getKey())
                                    .exportStatus(FileDealStatusEnum.DEALING.getStatus())
                                    .total(map.size())
                                    .finish(finishNumber)
                                    .error(errorNumber)
                                    .build());
                            continue;
                        }
                        zos.putNextEntry(new ZipEntry(name + ".jpg"));
                        InputStream fis = connection.getInputStream();
                        byte[] buffer = new byte[1024];
                        int r = 0;
                        while ((r = fis.read(buffer)) != -1) {
                            zos.write(buffer, 0, r);
                        }
                        fis.close();
                    }
                } catch (Exception e) {
                    flag = false;
                    // 添加错误数据
                    if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(fileUrl)) {
                        errorMap.put(name, fileUrl);
                    }
                    errorNumber += 1;
                    finishNumber += 1;

                    redisService.hset(this.exportCacheKey, t.getKey(), ExportLargeData.builder()
                            .key(t.getKey())
                            .exportStatus(FileDealStatusEnum.DEALING.getStatus())
                            .total(map.size())
                            .finish(finishNumber)
                            .error(errorNumber)
                            .build());
                } finally {
                    if (flag) {
                        finishNumber += 1;
                        redisService.hset(this.exportCacheKey, t.getKey(), ExportLargeData.builder()
                                .key(t.getKey())
                                .exportStatus(FileDealStatusEnum.DEALING.getStatus())
                                .total(map.size())
                                .finish(finishNumber)
                                .error(errorNumber)
                                .build());
                    }
                }
            }
            zos.flush();
            zos.close();
            stopWatch.stop();
            // 处理错误数据
            String errorFileUrl = "";
            if (CollUtil.isNotEmpty(errorMap)) {
                // 表头list的生成，实际的表头信息可以从数据库或者缓存等处动态读取
                List<List<String>> allColList = new ArrayList<>();
                // 姓名表头
                allColList.add(CollUtil.newArrayList("名称"));
                // 校区表头
                allColList.add(CollUtil.newArrayList("地址"));
                // 处理错误数据
                List<List<String>> exportErrorDataList = new ArrayList<>();
                errorMap.forEach((k, v) -> exportErrorDataList.add(CollUtil.newArrayList(k, v)));

                String errorFileName = com.yida.data.common.core.utils.FileUtil.getLocalUploadAddress() + t.getKey() + ".xls";
                errorFile = new File(errorFileName);
                // 导出错误数据
                EasyExcel.write(errorFile)
                        .excelType(ExcelTypeEnum.XLS)
                        // 这里放入动态头
                        .head(allColList).sheet("错误信息")
                        // 当然这里数据也可以用 List<List<String>> 去传入
                        .doWrite(exportErrorDataList);
                // 上传文件到媒资
                errorFileUrl = com.yida.data.common.core.utils.FileUtil.uploadFileToMediaServer(uploadUrl, errorFile);
            }
            // 处理完成
            ExportLargeData exportLargeData = ExportLargeData.builder()
                    .key(t.getKey())
                    .exportStatus(FileDealStatusEnum.DEAL_SUCCESS.getStatus())
                    .fileName(fileName)
                    .fileSize(new File(fileName).length())
                    .total(map.size())
                    .finish(finishNumber)
                    .error(errorNumber)
                    .build();
            if (StrUtil.isNotBlank(errorFileUrl)) {
                exportLargeData.setErrorFilePath(errorFileUrl);
            }
            redisService.hset(this.exportCacheKey, t.getKey(), exportLargeData);
            log.info("导出照片数据总共耗时: {}", stopWatch.getTotalTimeSeconds() + "秒");
        } catch (Exception e) {
            log.error("导出大文件失败", e);
            // 添加处理失败的结果
            redisService.hset(this.exportCacheKey, t.getKey(), ExportLargeData.builder()
                    .key(t.getKey())
                    .exportStatus(FileDealStatusEnum.DEAL_FAIL.getStatus())
                    .build());
            // 删除本地zip文件
            if (Objects.nonNull(file)) {
                FileUtil.del(file);
            }
        } finally {
            // 删除本地错误信息文件
            if (Objects.nonNull(errorFile)) {
                FileUtil.del(errorFile);
            }
        }
    }

    @Override
    public Object selectExportInfo(String key) {
        return redisService.hget(exportCacheKey, key);
    }

    @Override
    public void downLoadChunk(DownloadFileChunk dto, HttpServletResponse response) {
        // 生成的文件信息
        File resultFile = null;
        Integer index = null;
        Integer chunkTotal = null;

        try {
            // 获取缓存信息
            ExportLargeData exportLargeData = (ExportLargeData) redisService.hget(this.exportCacheKey, dto.getKey());
            String fileName = exportLargeData.getFileName();

            String[] splits = fileName.split("\\.");
            String type = splits[splits.length - 1];
            String resultFileName = com.yida.data.common.core.utils.FileUtil.getLocalUploadAddress() + dto.getKey() + "." + type;
            // 生成的分片文件
            resultFile = new File(resultFileName);

            // 分片信息
            Integer chunkSize = dto.getChunkSize();
            index = dto.getIndex();
            chunkTotal = dto.getChunkTotal();

            long offset = (long) chunkSize * (index - 1);
            if (Objects.equals(index, chunkTotal)) {
                offset = resultFile.length() - chunkSize;
            }
            byte[] chunk = ChunkUtil.getChunk(index, chunkSize, resultFileName, offset);

            log.info("下载文件分片" + resultFileName + "," + index + "," + chunkSize + "," + chunk.length + "," + offset);
//        response.addHeader("Access-Control-Allow-Origin","Content-Disposition");
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.addHeader("Content-Length", String.valueOf(chunk.length));
//            response.setHeader("filename", fileName);
            response.setContentType("application/octet-stream");

            ServletOutputStream out = response.getOutputStream();
            out.write(chunk);
            out.flush();
            out.close();
        } catch (Exception e) {
            log.error("下载分片失败", e);
            throw new FebsException("下载分片失败");
        } finally {
            if (Objects.nonNull(resultFile) && Objects.nonNull(index)
                    && Objects.nonNull(chunkTotal) && index.equals(chunkTotal)) {
                FileUtil.del(resultFile);
            }
        }
    }
}
