package com.tofly.xrtygis.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tofly.common.ftp.config.FtpUtil;
import com.tofly.xrtygis.constant.Constant;
import com.tofly.xrtygis.entity.IFile;
import com.tofly.xrtygis.entity.ValvePit;
import com.tofly.xrtygis.entity.vo.FileInfo;
import com.tofly.xrtygis.entity.vo.ValvePitZipVo;
import com.tofly.xrtygis.mapper.ValvePitMapper;
import com.tofly.xrtygis.service.IFileInfoService;
import com.tofly.xrtygis.service.ValvePitService;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

@Service
public class ValvePitServiceImpl extends ServiceImpl<ValvePitMapper, ValvePit> implements ValvePitService {

    public static int BYTE_LEN = 10240;

    @Value("${xrty.ftp.valvePitPath}")
    private String valvePitPath;

    @Value("${xrty.ftp.tempPath}")
    private String tempPath;
//    private static final String tempPath = "E:\\Tofly\\Code\\tf-ylsw\\ylsw-gis\\temp\\";

    @Autowired
    private IFileInfoService fileInfoService;

    private final Map<String, Integer> isMergePart = new HashMap<>();

    @Override
    public boolean saveValvePit(List<MultipartFile> fileList, ValvePit valvePit) {
        if (fileList != null && fileList.size() != 0) {
            List<FileInfo> annexFileInfo = fileInfoService.saveFileList(fileList, valvePitPath, "TF_YWPN_VALVEPIT_W");
            valvePit.setAnnex(JSON.toJSONString(annexFileInfo));
        }
        // 对象入库
        this.save(valvePit);
        return true;
    }

    /**
     *
     * 单文件分片上传
     *
     * @param filePart  分片文件
     * @param partIndex 当前分片值
     * @param partNum   所有分片数
     * @param fileName  当前文件名称
     * @param fileUid   当前文件uuid
     */
    @Override
    @SneakyThrows
    public boolean singleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {
        // 实际情况下，这些路径都应该是服务器上面存储文件的路径
        String tempFilePath = tempPath + "valvePit/" + fileUid;//临时文件存放路径
        File dir = new File(tempFilePath);
        if (!dir.exists()) dir.mkdirs();
        // 生成一个临时文件名
        String newFileName = fileName + "_" + partIndex + ".part";
        String tempFileNamePath = tempFilePath + "/" + newFileName;
        // 将分片存储到临时文件夹中
        filePart.transferTo(new File(tempFileNamePath));

        File tempDir = new File(tempFilePath);
        File[] tempFiles = tempDir.listFiles();
        if (!partNum.equals(Objects.requireNonNull(tempFiles).length)) return true;
        // 需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成
        if (isMergePart.get(fileUid) != null) return true;
        isMergePart.put(fileUid, tempFiles.length);
        synchronized (this) {
            FileOutputStream fileOutputStream = new FileOutputStream(tempPath + fileName);
            // 这里如果分片很多的情况下，可以采用多线程来执行
            for (int i = 0; i < partNum; i++) {
                //读取分片数据，进行分片合并
                FileInputStream fileInputStream = new FileInputStream(tempFilePath + "/" + fileName + "_" + i + ".part");
                byte[] buf = new byte[1024 * 100]; // 100MB
                int length;
                while ((length = fileInputStream.read(buf)) != -1) { // 读取fis文件输入字节流里面的数据
                    fileOutputStream.write(buf, 0, length); // 通过fos文件输出字节流写出去
                }
                fileInputStream.close();
            }
            fileOutputStream.flush();
            fileOutputStream.close();
            // 删除临时文件夹里面的分片文件 如果使用流操作且没有关闭输入流，可能导致删除失败
            for (int i = 0; i < partNum; i++) {
                new File(tempFilePath + "/" + fileName + "_" + i + ".part").delete();
            }
            // 在删除对应的临时文件夹
            if (Objects.requireNonNull(tempDir.listFiles()).length == 0) tempDir.delete();
            isMergePart.remove(fileUid);

            // 新增阀井照片
            this.saveByZip(new File(tempPath + fileName));
        }
        return true;
    }

    @Override
    public boolean removeValvePitByIds(String ids) {
        List<Long> idList = Arrays.stream(ids.split(Constant.CSV)).map(s -> Long.parseLong(s.trim())).collect(Collectors.toList());
        idList.forEach(info ->{
            ValvePit valvePit = this.getById(info);
            String annex = valvePit.getAnnex();
            if (StringUtils.isNotBlank(annex)) {
                List<FileInfo> fileInfoList = JSON.parseArray(annex, FileInfo.class);
                fileInfoService.removeFileList(fileInfoList);
            }
        });
        this.removeByIds(idList);
        return true;
    }


    /* ***********************************************自定义方法*********************************************** */


    /**
     * 解压阀井照片压缩包并分类保存里面的所有阀井照片
     *
     * @param file 阀井照片压缩包本地文件
     */
    public void saveByZip(File file) throws Exception {
        if (!file.exists()) return;
        List<ValvePitZipVo> valvePitZipVos = new ArrayList<>();
        ZipFile zip = new ZipFile(file);
        Enumeration<? extends ZipEntry> entries = zip.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            String originalName = entry.getName();
            String valvePitCode = originalName.substring(0, originalName.lastIndexOf(Constant.BAR));
            ValvePitZipVo vo = new ValvePitZipVo();
            vo.setValvePitCode(valvePitCode);
            List<ZipEntry> zipEntries = new ArrayList<>();
            if (CollUtil.isNotEmpty(valvePitZipVos)) {
                List<ValvePitZipVo> valvePitZipVosFinal = valvePitZipVos.stream().filter(e -> e.getValvePitCode().equals(valvePitCode)).collect(Collectors.toList());
                if (CollUtil.isNotEmpty(valvePitZipVosFinal)) {
                    zipEntries = valvePitZipVosFinal.get(0).getZipEntries();
                    valvePitZipVos.removeIf(e -> e.getValvePitCode().equals(valvePitCode));
                }
            }
            zipEntries.add(entry);
            vo.setZipEntries(zipEntries);
            valvePitZipVos.add(vo);
        }
        if (CollUtil.isEmpty(valvePitZipVos)) return;

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                4,
                8,
                120,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(1000),
                (r)->{
                    Thread t = new Thread(r);
                    t.setName("thread-" + ((int)(Math.random()*1000)));
                    return t;},
                new ThreadPoolExecutor.CallerRunsPolicy());

        InputStream in = null;
        List<IFile> localFiles = new ArrayList<>();
        List<ValvePit> valvePits = new ArrayList<>();
        for (ValvePitZipVo valvePitZipVo : valvePitZipVos) {
            ValvePit valvePit = new ValvePit();
            valvePit.setCode(valvePitZipVo.getValvePitCode());

            List<ZipEntry> zipEntries = valvePitZipVo.getZipEntries();
            List<FileInfo> fileInfoList = new ArrayList<>();
            for (ZipEntry zipEntry : zipEntries) {
                String originalName = zipEntry.getName();
                String fileSize = zipEntry.getSize() + "kb";
                String suffix = zipEntry.getName().substring(zipEntry.getName().lastIndexOf(Constant.POINT));
                String fileOtherName = IdWorker.getIdStr() + suffix;
                String filePath = valvePitPath + fileOtherName;

                FileInfo fileInfo = new FileInfo();
                fileInfo.setFileName(originalName);
                fileInfo.setFileSize(fileSize);
                fileInfo.setFileFormat(suffix);
                fileInfo.setOtherName(fileOtherName);
                fileInfo.setFilePath(filePath);
                fileInfoList.add(fileInfo);

                IFile localFile = new IFile();
                localFile.setTableName("TF_YWPN_VALVEPIT_W");
                localFile.setFilePath(filePath);
                localFile.setFileName(originalName);
                localFile.setOtherName(fileOtherName);
                localFile.setFileFormat(suffix);
                localFile.setFileSize(fileSize);
                localFiles.add(localFile);

                in = zip.getInputStream(zipEntry);
                byte[] bytes = streamToByteArray(in);
                threadPoolExecutor.execute(() -> FtpUtil.uploadFile2Ftp(bytes, filePath));
            }
            valvePit.setAnnex(JSON.toJSONString(fileInfoList));
            valvePits.add(valvePit);
        }
        if (in != null) in.close();
        zip.close();
        if (file.exists()) file.delete();
        fileInfoService.saveFile(localFiles);
        valvePits.forEach(this::save);
    }

    /**
     * 将输入流转换成 byte[]
     *
     * @param is 输入流
     * @return byte[]
     */
    public static byte[] streamToByteArray(InputStream is) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
        byte[] b = new byte[1024];
        int len;
        while ((len = is.read(b)) != -1) {
            bos.write(b, 0, len);
        }
        byte[] array = bos.toByteArray();
        bos.close();
        return array;
    }

    /**
     * MultipartFile 转 File
     *
     * @param multipartFile 上传文件
     * @return 本地文件
     */
    private static File getFile(MultipartFile multipartFile, String tempPath) throws Exception {
        String fileName = System.currentTimeMillis() + "_" + multipartFile.getOriginalFilename();
        File file = new File(tempPath + fileName);
        OutputStream os = new FileOutputStream(file);
        InputStream in = multipartFile.getInputStream();
        byte[] buffer = new byte[BYTE_LEN];
        int len;
        while ((len = in.read(buffer, 0, BYTE_LEN)) != -1) {
            os.write(buffer, 0, len);
        }
        os.close();
        in.close();
        return file;
    }

}
