package com.artfess.cqxy.universal.manager.impl;

import com.artfess.base.context.BaseContext;
import com.artfess.base.exception.BaseException;
import com.artfess.base.manager.impl.BaseManagerImpl;
import com.artfess.base.query.*;
import com.artfess.base.util.BeanUtils;
import com.artfess.base.util.FileUtil;
import com.artfess.base.util.HttpUtil;
import com.artfess.cqxy.projectApproval.model.DecisionAnnualPlan;
import com.artfess.cqxy.projectManagement.manager.ProjectManagementManager;
import com.artfess.cqxy.projectManagement.model.ProjectManagement;
import com.artfess.cqxy.search.enums.FunctionEnum;
import com.artfess.cqxy.search.manager.GlobalRetrievalManager;
import com.artfess.cqxy.search.model.GlobalRetrieval;
import com.artfess.cqxy.universal.dao.AccessoryDao;
import com.artfess.cqxy.universal.manager.AccessoryManager;
import com.artfess.cqxy.universal.manager.UtilsManager;
import com.artfess.cqxy.universal.model.Accessory;
import com.artfess.cqxy.universal.model.Chunk;
import com.artfess.cqxy.utils.BizUtils;
import com.artfess.file.util.MinioUtil;
import com.artfess.file.util.OfficeToPdf;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.jsonwebtoken.lang.Assert;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.sql.Wrapper;
import java.time.LocalDateTime;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.util.*;

/**
 * 通用附件表(BizAccessory)表服务实现类
 *
 * @author 黎沐华
 * @since 2022-02-16 17:14:40
 */
@Service
public class AccessoryManagerImpl extends BaseManagerImpl<AccessoryDao, Accessory> implements AccessoryManager {

    @Autowired
    private UtilsManager utils;

    @Autowired
    private GlobalRetrievalManager grm;

    @Autowired
    private ProjectManagementManager pmm;

    @Autowired
    private BaseContext baseContext;

    @Autowired
    private OfficeToPdf officeToPdf;

    @Autowired
    private ProjectManagementManager projectManagementManager;

    /************************附件信息管理************************/
    @Override
    public boolean saveAccess(List<Accessory> accessoryList) {
        // 同步设置更新信息为创建信息，WPS预览时需要相关数据，不能为空
        for (Accessory ele : accessoryList) {
            String userId = baseContext.getCurrentUserId();
            String userName = baseContext.getCurrentUserName();
            if (StringUtils.isBlank(ele.getId())) {
                ele.setCreateBy(userId);
                ele.setCreateName(userName);
                ele.setCreateTime(LocalDateTime.now());
            } else {
                ele.setUpdateName(userName);
                ele.setUpdateBy(userId);
                ele.setUpdateTime(LocalDateTime.now());
            }
        }
        boolean result = saveBatch(accessoryList);
        // 同步信息到检索表
        List<GlobalRetrieval> globalRetrievals = new ArrayList<>();
        for (Accessory ele : accessoryList) {
            ProjectManagement p = pmm.getById(ele.getProjectId());
            ProjectManagement projectManagement = BeanUtils.isEmpty(p) ? new ProjectManagement() : p;
            GlobalRetrieval temp = new GlobalRetrieval();
            temp.setProjectId(ele.getProjectId());
            temp.setProjectName(projectManagement.getProjectName());
            temp.setPersonCharge(projectManagement.getProjectManager());
            temp.setFunctionCode(FunctionEnum.zero.getCode());
            temp.setFunctionName(FunctionEnum.zero.getName());
            temp.setArchivesType(2);
            temp.setBizTableName(FunctionEnum.zero.getTableName());
            temp.setBizDataId(ele.getId());
            temp.setTableApiUrl(FunctionEnum.zero.getTableApiUrl());
            temp.setFunctionPath(FunctionEnum.zero.getFunctionPath());
            temp.setFileType(ele.getType());
            temp.setFileName(ele.getName());
            temp.setFileSourceId(ele.getSourceId());
            temp.setFileUrl(ele.getUrl());
            temp.setFileSuffix(ele.getSuffix());
            temp.setFileGroup(ele.getGroup());
            temp.setFileSize(ele.getFileSize());
            temp.setSearchTitle(ele.getName() + "_" + ele.getSuffix() + "_" + ele.getGroup() + "_" + ele.getNode() + "_" + ele.getDirectory());
            globalRetrievals.add(temp);
        }
        grm.saveBatch(globalRetrievals);
        return result;
    }

    @Override
    public List<Accessory> getAccessoryBySourceId(String sourceId) {
        QueryFilter<Accessory> queryFilter = QueryFilter.build();
        queryFilter.addFilter("SOURCE_ID_", sourceId, QueryOP.EQUAL);
        queryFilter.addFilter("IS_DELE_", "0", QueryOP.EQUAL);
        return this.baseMapper.queryByPageAndSourceID(
                convert2IPage(new PageBean(0, -1, false)),
                convert2Wrapper(queryFilter, currentModelClass())).
                getRecords();
    }

    @Override
    public PageList<Accessory> getAccessoryPageBySourceId(QueryFilter<Accessory> queryFilter) {
        // 限制SourceID不能为空
        String sourceID = null;
        List<QueryField> test = queryFilter.getQuerys();
        for (QueryField ele : test) {
            if ("SOURCE_ID_".equals(ele.getProperty())) {
                sourceID = String.valueOf(ele.getValue());
                break;
            }
        }
        Assert.notNull(sourceID, "SOURCE_ID_不能为空");
        queryFilter.addFilter("IS_DELE_", "0", QueryOP.EQUAL);
        IPage result = baseMapper.queryByPageAndSourceID(
                convert2IPage(queryFilter.getPageBean()),
                convert2Wrapper(queryFilter, currentModelClass()));
        PageList<Accessory> pageList = new PageList<Accessory>(result);
        List<Accessory> rows = pageList.getRows();
        if(null != rows && rows.size() > 0){
            for (Accessory accessory : rows){
                if(StringUtils.isNotBlank(accessory.getProjectId())){
                    // 查询项目名称
                    ProjectManagement projectManagement = projectManagementManager.get(accessory.getProjectId());
                    accessory.setProjectName(projectManagement.getProjectName());
                }
            }
        }
        return pageList;
    }

    @Override
    public boolean removeBySourceId(String sourceId) {
//        UpdateWrapper<Accessory> updateWrapper = new UpdateWrapper<>();
//        updateWrapper.set("IS_DELE_","1")
//                .eq("IS_DELE_","0")
//                .eq("SOURCE_ID_",sourceId);
        // 改为真删除，避免垃圾数据过多
        QueryWrapper<Accessory> queryWrapper = new QueryWrapper<Accessory>();
        queryWrapper.eq("SOURCE_ID_", sourceId);
        int delete = baseMapper.delete(queryWrapper);
//        baseMapper.remove(sourceId);
        // 同步删除检索表中的信息
        grm.remove(2, sourceId);

//        return this.update(updateWrapper);
        return true;
    }

    /************************上传文件************************/
    @Override
    public String fileUploadPost(Chunk chunk, HttpServletResponse response) throws IOException {
        /**
         * 每一个上传块都会包含如下分块信息：
         * chunkNumber: 当前块的次序，第一个块是 1，注意不是从 0 开始的。
         * totalChunks: 文件被分成块的总数。
         * chunkSize: 分块大小，根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。
         * currentChunkSize: 当前块的大小，实际大小。
         * totalSize: 文件总大小。
         * identifier: 这个就是每个文件的唯一标示。
         * filename: 文件名。
         * relativePath: 文件夹上传的时候文件的相对路径属性。
         * 一个分块可以被上传多次，当然这肯定不是标准行为，但是在实际上传过程中是可能发生这种事情的，这种重传也是本库的特性之一。
         *
         * 根据响应码认为成功或失败的：
         * 200 文件上传完成
         * 201 文加快上传成功
         * 500 第一块上传失败，取消整个文件上传
         * 507 服务器出错自动重试该文件块上传
         */
        // 以文件名作为目录保存各片文件
        String path = utils.getFileDir(chunk.getFilename());
        // 各片文件加上后缀，固定格式
//        File file= new File(path, "temp_"+chunk.getChunkNumber());
        File file = new File(path, "" + chunk.getChunkNumber());
        // 创建文件
        if (!file.exists()) {
            // 父级文件夹，用于管理所有分片文件
            new File(path).mkdirs();
            // 分片块文件
            file.createNewFile();
        }
        //进行写文件操作
        try (
                //将块文件写入文件中
                InputStream fos = chunk.getFile().getInputStream();
                RandomAccessFile raf = new RandomAccessFile(file, "rw")
        ) {
            int len = -1;
            byte[] buffer = new byte[1024];
//            raf.seek((chunk.getChunkNumber()-1)*1024*1024*5);
            while ((len = fos.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
            if (chunk.getChunkNumber() == 1) {
                file.delete();
            }
            response.setStatus(507);
            return "exception:文件写入异常";
        }
        if (chunk.getChunkNumber().equals(chunk.getTotalChunks())) {
            response.setStatus(200);
            return "over";
        } else {
            response.setStatus(201);
            return "ok";
        }
    }

    /**
     * 合并文件
     *
     * @param fileName 文件名
     * @return 合并后的文件
     * @throws FileNotFoundException 找不到文件
     */
    private File mergeFile(String fileName) throws FileNotFoundException {
        String dirPath = utils.getFileDir(fileName);
        // 创建合并后的文件对象，如果这个文件存在，将其删除（后续会在RandomAccessFile中重新构建），确保fs数组读取到的只有分片碎片文件，
        File result = new File(dirPath + "/" + fileName);
        if (result.exists()) {
            result.delete();
        }
        // 获取路径下的所有文件，将其转变为List<RandomAccessFile>
        File[] fs = new File(dirPath).listFiles();
        List<File> fileList = Arrays.asList(fs);
        Collections.sort(fileList, (o1, o2) -> {
            return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
        });
        List<RandomAccessFile> rafList = new ArrayList<>();
        for (File ele : fileList) {
            RandomAccessFile tempFile = new RandomAccessFile(ele, "rw");
            rafList.add(tempFile);
        }
        try (
                // 开始合并文件，这里的资源会自动释放
                // 输出管道
                FileChannel outputChannel = new RandomAccessFile(result, "rw").getChannel();
        ) {
            // 遍历分片文件集合
            for (RandomAccessFile ele : rafList) {
                // 获取单个文件的输出管道
                FileChannel inputChannel = ele.getChannel();
                // 从输入管道写入数据到输出管道——输入管道、偏移量、写入长度
                outputChannel.transferFrom(inputChannel, outputChannel.size(), inputChannel.size());
                //释放资源
                inputChannel.close();
            }
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * minIO上传
     *
     * @param fileName 上传文件名称
     * @param bizPath  文件夹路径
     * @return 附件对象
     * @throws Exception 异常
     */
    @Override
    public Accessory minioUpload(String bizPath, String fileName) throws Exception {
        Assert.notNull(fileName, "文件名不能为空！");
        File file = mergeFile(fileName);
        Accessory accessory = new Accessory();
        if (null == file || file.length() == 0 || !file.exists()) {
            throw new BaseException("文件为空");
        }
        accessory.setName(fileName);
        String name = DigestUtils.md5Hex(UUID.randomUUID().toString()) + fileName.substring(fileName.lastIndexOf("."));
        //如果为空则上传到MinIO中的默认文件夹
        if (StringUtils.isEmpty(bizPath)) {
            bizPath = "default";
        }
        accessory.setSuffix(fileName.substring(fileName.lastIndexOf(".") + 1));
        String url = MinioUtil.getMinIoUrl(bizPath, name);
        // URL MinIO 异步上传
        accessory.setUrl(url);
        String userId = baseContext.getCurrentUserId();
        String userName = baseContext.getCurrentUserName();
        accessory.setCreateBy(userId);
        accessory.setUpdateBy(userId);
        accessory.setCreateName(userName);
        accessory.setUpdateName(userName);
        accessory.setCreateTime(LocalDateTime.now());
        accessory.setUpdateTime(LocalDateTime.now());
        accessory.setIsDele("0");
        accessory.setType(BizUtils.handleFileType(accessory.getSuffix()));
        accessory.setFileSize(new DecimalFormat("#,##0.##").format(file.length() / 1024.00 / 1024.00) + " MB");
        utils.startMinIOUpload(file, bizPath, name);
        int a = 0;
        return accessory;
    }

    /**********************下载文件************************/
    @Override
    public void minIoDownFile(String ids, String bizPath, HttpServletResponse response) throws Exception {
        String customBucket = "xypm", filePath = "download/" + bizPath;
        if (StringUtils.isNotBlank(ids)) {
            File path = new File(filePath);
            if (!path.exists()) {
                path.mkdirs();
            }
            List<String> idList = Arrays.asList(ids.split(","));
            List<Accessory> accessories = this.listByIds(idList);
            if (accessories.size() == 1) {
                Accessory accessory = accessories.get(0);
                String url = accessory.getUrl();
                String substring = url.substring(url.lastIndexOf("/"));
                FileOutputStream out = new FileOutputStream(path + "/" + accessory.getName());
                MinioUtil.downFile(customBucket, bizPath + substring, out, false);
                HttpUtil.downLoadFile(response, filePath + "/" + accessory.getName(), accessory.getName());
                FileUtil.deleteFile(filePath + "/" + accessory.getName());
                return;
            }

            accessories.forEach(s -> {
                String url = s.getUrl();
                String substring = url.substring(url.lastIndexOf("/"));
                FileOutputStream out = null;
                try {
                    out = new FileOutputStream(path + "/" + s.getName());
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                MinioUtil.downFile(customBucket, bizPath + substring, out, false);

            });
            this.zip(filePath, true);
            String zipPath = filePath + ".zip";
            HttpUtil.downLoadFile(response, zipPath, zipPath.substring(zipPath.lastIndexOf("/") + 1));
            // 删除导出的文件
            FileUtil.deleteFile(zipPath);
        }
    }

    /***
     *
     * @param accessory 附件对象
     * @param bizPath
     * @return 返回
     * @throws Exception
     */
    @Override
    public String minIoDownFile(Accessory accessory, String bizPath) throws Exception {
        if (StringUtils.isEmpty(bizPath)) {
            bizPath = "default";
        }
        String customBucket = "xypm", filePath = "download/" + bizPath + "/" + accessory.getId();
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
//        Accessory accessory = this.getById(id);
        String url = accessory.getUrl();
        String dir = url.substring(url.lastIndexOf("/"));
        String suffix = accessory.getSuffix();
        File fileFullPath = new File(file + "/" + accessory.getName());
        if(fileFullPath.exists()){
            return filePath + "/";
        }
        FileOutputStream out = new FileOutputStream(file + "/" + accessory.getName());
        MinioUtil.downFile(customBucket, bizPath + dir, out, false);
        String downPath = filePath + "/";
//        FileUtil.deleteFile(downPath);
        return downPath;

    }

    /**
     * 压缩文件
     *
     * @param path     压缩文件\文件夹路径
     * @param isDelete 压缩后是否删除原文件\文件夹
     */
    private void zip(String path, Boolean isDelete) {
        ZipFile zipFile = null;
        try {
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
            parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
            File file = new File(path);
            Assert.isTrue(file.exists(), "文件不存在");
            ArrayList<String> list = new ArrayList<>();
            if (file.isDirectory()) {
                zipFile = new ZipFile(new File(path + ".zip"));
                zipFile.setFileNameCharset("utf-8");
                zipFile.addFolder(path, parameters);
            } else {
                zipFile = new ZipFile(new File(path.split(".")[0] + ".zip"));
                zipFile.setFileNameCharset("utf-8");
                zipFile.addFile(file, parameters);
            }
            if (isDelete) {
                FileUtil.deleteDir(file);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void updateProjectIdByProiectId(String oldProjectIds, String newProjectId) {
        UpdateWrapper<Accessory> wrapper = new UpdateWrapper<Accessory>()
                .set(StringUtils.isNotBlank(newProjectId), "PROJECT_ID_", newProjectId)
                .in("PROJECT_ID_", Arrays.asList(oldProjectIds.split(",")));
        this.update(wrapper);
    }

    @Override
    public String fileToPdf(Accessory accessory) {
        String suffix = accessory.getSuffix();
        if("pdf".equalsIgnoreCase(suffix)){
            return null;
        }
        String url = accessory.getUrl();
        String[] urlArr = url.split("/");
        try {
            String downFilePath = minIoDownFile(accessory, urlArr[4]);
            String fileName = accessory.getName();
            String inputPath = downFilePath + fileName;
            String outputPath = downFilePath + fileName + ".pdf";
            int i = fileName.lastIndexOf(".");
            if ((i > -1) && (i < (fileName.length() - 1))) {
                outputPath =  downFilePath + fileName.substring(0, i)  + ".pdf";
            }
            officeToPdf.office2pdf(inputPath, outputPath);
            return outputPath;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public List<Accessory> getAccessoryByGroup(String group) {
        QueryFilter<Accessory> queryFilter = QueryFilter.build();
        queryFilter.addFilter("GROUP_", group, QueryOP.EQUAL);
        queryFilter.addFilter("IS_DELE_", "0", QueryOP.EQUAL);
        return this.baseMapper.queryByPageAndSourceID(
                convert2IPage(new PageBean(0, -1, false)),
                convert2Wrapper(queryFilter, currentModelClass())).
                getRecords();
    }
}

