package com.zzsn.knowbase.service.impl;

import com.alibaba.fastjson.JSON;
import com.zzsn.knowbase.constant.Constants;
import com.zzsn.knowbase.constant.DocumentConstants;
import com.zzsn.knowbase.constant.ErrorCodeEnum;
import com.zzsn.knowbase.entity.*;
import com.zzsn.knowbase.service.DocumentService;
import com.zzsn.knowbase.service.IEnclosureService;
import com.zzsn.knowbase.service.IKnowledgeService;
import com.zzsn.knowbase.service.ILocalFileService;
import com.zzsn.knowbase.util.*;
import com.zzsn.knowbase.util.file.FileUtil;
import com.zzsn.knowbase.util.file.FileUtility;
import com.zzsn.knowbase.util.file.Md5Utils;
import com.zzsn.knowbase.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.hashids.Hashids;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.*;

/**
 * @Version 1.0
 * @Author: ZhangJingKun
 * @Date: 2024/1/9 9:36
 * @Content:
 */
@Service
@Slf4j
public class LocalFileServiceImpl implements ILocalFileService {

    @Autowired
    private FileUtility fileUtility;//文件工具类

    @Autowired
    private CodeGenerateUtil codeGenerateUtil; //生成唯一id

    @Autowired
    private DocumentService documentService;

    @Autowired
    private IKnowledgeService knowledgeService;

    @Autowired
    private IEnclosureService enclosureService;

    @Value("${files.storage}")
    String filesStorage;
    @Value("${files.docservice.url.site}")
    private String officeUrl;
    @Value("${files.docservice.url.command}")
    private String officeCommand;

    @Override
    public Result<KnowFile> upload(MultipartFile file) {
        try {
            String contentMd5 = FileMd5Utils.getFileMD5String(file);
            Enclosure enclosure = enclosureService.getByMd5(contentMd5);
            if(enclosure == null){
                enclosure = new Enclosure();
                InputStream in = null;
                try {
                    String fileName = file.getOriginalFilename();  // 获取文件名称
                    String fileType = fileUtility.getFileType(fileName);  //获取文件类型
                    long fileSize = file.getSize();  // get file size
                    if (fileUtility.getMaxFileSize() < fileSize || fileSize <= 0) {
                        Result result = Result.error("文件大小超过范围！");
                        log.info("文件大小超过范围!");
                        return result;
                    }
                    if (!fileUtility.getFileExts().contains(fileType)) {
                        Result result = Result.error("不支持的文件类型！");
                        log.info("不支持的文件类型!");
                        return result;
                    }
                    enclosure.setExtensionName(fileType);
                    enclosure.setName(fileName);
                    enclosure.setContentMd5(contentMd5);
                    enclosure.setFileSize(file.getSize());
                    enclosure.setCreateTime(new Date());
                    enclosure.setCreateBy(SpringContextUtils.getUserInfo().getUsername());

                    log.info("文件上传到minio：{}", fileName);
                    in = file.getInputStream();
                    String fullFileName = MinioUtil.uploadByStream(in, MimeUtil.getMimeStrByName(fileName), null, MinioUtil.getRelativePath(fileName));
                    enclosure.setUrlPath(fullFileName);
                    enclosureService.save(enclosure);
                } catch (Exception e) {
                    log.error("文件上传失败！{}", e.getMessage());
                } finally {
                    if(in!=null){
                        try {
                            in.close();
                        } catch (IOException e) {
                            log.error(e.getMessage());
                        }
                    }
                }
            }
            //封装es关联对象并返回
            KnowFile knowFile = new KnowFile();
            knowFile.setFileId(enclosure.getId());
            knowFile.setFileName(enclosure.getName());
            knowFile.setFilePath(enclosure.getUrlPath());
            knowFile.setFileType(enclosure.getExtensionName());
            knowFile.setFileSize(enclosure.getFileSize());
            log.info("文件上传成功name:{},path:{}",knowFile.getFileName(),knowFile.getFilePath());
            return Result.OK(knowFile);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        Result result = Result.error("上传文件时出现问题！");
        log.info("上传文件时出现问题！");
        return result;
    }

    @Override
    public Result<List<KnowFile>> upload(Map<String, MultipartFile> fileMap) {
        List<KnowFile> list = new ArrayList<>();
        Result<List<KnowFile>> res = Result.OK(list);


        for (Map.Entry<String,MultipartFile> entity : fileMap.entrySet()) {
            MultipartFile file = entity.getValue();// 获取上传文件对象
            Result<KnowFile> result = upload(file);
            if(result.getCode() == 200){
                res.getResult().add(result.getResult());
            } else {
                return res.error500(result.getMessage());
            }
        }
        return res;
    }

    @Override
    public void download(String fileName, String filePath, HttpServletResponse response) {

        // path是指想要下载的文件的路径
        File file = new File(filesStorage + filePath);
        try {
            // 将文件写入输入流
            FileInputStream fileInputStream = new FileInputStream(file);
            InputStream fis = new BufferedInputStream(fileInputStream);
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            // 清空response
            response.reset();
            // 设置response的Header
            response.setCharacterEncoding("UTF-8");
            //Content-Disposition的作用：告知浏览器以何种方式显示响应返回的文件，用浏览器打开还是以附件的形式下载到本地保存
            //attachment表示以附件方式下载   inline表示在线打开   "Content-Disposition: inline; filename=文件名.mp3"
            // filename表示文件的默认名称，因为网络传输只支持URL编码的相关支付，因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filePath, "UTF-8"));
            // 告知浏览器文件的大小
            response.addHeader("Content-Length", "" + file.length());
            OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
            response.setContentType("application/octet-stream");
            outputStream.write(buffer);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //edit
    @Override
    public String editDocFile(String fileName, String filePath, String userName, Model model) {
        Document document = documentService.getDocument(documentService.buildDocument(filePath, fileName));
        model.addAttribute("document", document);
        // 如果该格式不支持编辑，则返回预览页面
        if (!documentService.canEdit(document)) {
            return "/view";
        }
        model.addAttribute("documentEditParam", documentService.buildDocumentEditParam(userName, userName,filePath));
        return "/editor";
    }

    @Override
    public String viewerDocFile(String fileName, String filePath, String userName, Model model) {
        Document document = documentService.getDocument(documentService.buildDocument(filePath, fileName));
        model.addAttribute("document", document);
//        // 如果该格式不支持编辑，则返回预览页面
//        if (!documentService.canEdit(document)) {
//            return "/view";
//        }
        model.addAttribute("documentEditParam", documentService.buildDocumentEditParam(userName, userName,filePath));
        return "/view";
    }
    //编辑文档时回调接口
    @Override
    public void callBack(HttpServletRequest request, HttpServletResponse response) throws IOException{
        PrintWriter writer = null;
        JSONObject jsonObj = null;
        log.info("===saveeditedfile------------");

        try {
            writer = response.getWriter();
            Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");
            String body = scanner.hasNext() ? scanner.next() : "";
            jsonObj = (JSONObject) new JSONParser().parse(body);
            log.info(jsonObj.toJSONString());
            log.info("===saveeditedfile:" + jsonObj.get("status"));
            /*
            0-找不到具有密钥标识符的文档，
            1-文档正在编辑，
            2-文档已准备好保存，
            3-发生文档保存错误，
            4-文档已关闭，没有任何更改，
            6-文档正在编辑，但当前文档状态已保存，
            7-强制保存文档时发生错误。
            */
            if ((long) jsonObj.get("status") == 2) {
                callBackSaveDocument(jsonObj,request, response);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        /*
         * status = 1，我们给onlyoffice的服务返回{"error":"0"}的信息，这样onlyoffice会认为回调接口是没问题的，这样就可以在线编辑文档了，否则的话会弹出窗口说明
         * 在线编辑还没有关闭，前端有人下载文档时，强制保存最新内容  当status 是6时说明有人在编辑时下载文档
         * */
        System.out.println(jsonObj.get("status"));
        if ((long) jsonObj.get("status") == 6) {
            //处理当文档正在编辑为关闭时，下载文档
            if (((String)jsonObj.get("userdata")).equals("sample userdata")){
                callBackSaveDocument(jsonObj,request, response);
            }

            System.out.println("====保存失败:");
            writer.write("{\"error\":1}");
        } else {
            //执行删除编辑时下载保存的文件:
            deleteTempFile(request.getParameter("fileName"));
            writer.write("{\"error\":0}");
        }
    }


    /**
     * 编辑以后保存文件
     * @param jsonObj
     * @param request
     * @param response
     * @throws IOException
     */
    public void callBackSaveDocument(JSONObject jsonObj, HttpServletRequest request, HttpServletResponse response) throws IOException {
        /*
         * 当我们关闭编辑窗口后，十秒钟左右onlyoffice会将它存储的我们的编辑后的文件，，此时status = 2，通过request发给我们，我们需要做的就是接收到文件然后回写该文件。
         * */
        /*
         * 定义要与文档存储服务保存的编辑文档的链接。当状态值仅等于2或3时，存在链路。
         * */
        String downloadUri = (String) jsonObj.get("url");
        log.info("====文档编辑完成，现在开始保存编辑后的文档，其下载地址为:" + downloadUri);
        //解析得出文件名
        //String fileName = downloadUri.substring(downloadUri.lastIndexOf('/')+1);
        String fileName = request.getParameter("fileName");
        log.info("====下载的文件名:" + fileName);

        URL url = new URL(downloadUri);
        java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
        InputStream stream = connection.getInputStream();
        //更换为实际的路径F:\DataOfHongQuanzheng\java\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\Java Example\\app_data\192.168.56.1\
        //File savedFile = new File("F:\\DataOfHongQuanzheng\\onlyoffice_data\\app_data\\"+fileName);
        File savedFile = new File(filesStorage + fileName);
        if (null!=((String) jsonObj.get("userdata"))&&((String) jsonObj.get("userdata")).equals("sample userdata")) {
            savedFile = new File(filesStorage + fileName);
        }


        try (FileOutputStream out = new FileOutputStream(savedFile)) {
            int read;
            final byte[] bytes = new byte[1024];
            while ((read = stream.read(bytes)) != -1) {
                out.write(bytes, 0, read);
            }
            out.flush();
        }
        connection.disconnect();
    }

    public void deleteTempFile(String fileName) {
        //因为临时存储的文件都添加了v1前缀所以删除文件时需要在文件名测前边加一个v1
        File file = new File(filesStorage + "v1" + fileName);
        if (file.exists()) {
            file.delete();
        }
    }


    @Override
    public Result<?> editKnowledge(Knowledge knowledge) {
        log.info("保存文档：" + knowledge);
        Result result = Result.OK();
        String id = knowledge.getId();
        if(id == null || "".equals(id))
            return Result.error("文章id不能为空");
        String publish = EsDateUtil.esFieldDateFormat(knowledge.getPublishDate());
        knowledge.setPublishDate(publish);
        KbAuthorizedUser userInfo = SpringContextUtils.getUserInfo();
        knowledge.setVerifyTime(EsDateUtil.esFieldDateFormat(cn.hutool.core.date.DateUtil.formatDateTime(new Date())));
        knowledge.setVerifierId(userInfo.getUserId());
        knowledge.setVerifierName(userInfo.getUsername());
        if(Integer.valueOf("0").equals(knowledge.getImportData())){
            List<KnowFile> files = knowledge.getFiles();
            KnowFile knowFile = files.get(0);
            /*String filePath = knowFile.getFilePath();
            try {
                getDoucmentEditStatus(filePath,knowFile.getFileName());

            } catch (ParseException e) {
                e.printStackTrace();
                result = Result.error("文件保存失败！");
            }

            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                log.info("文件转换");
                File file = new File(filesStorage+filePath);
                XWPFDocument document = new XWPFDocument(new FileInputStream(file));
                document.write(new FileOutputStream(filesStorage+filePath+".tmp"));
                document.close();
                //log.info("文件转换成功：{}", filePath+".tmp");

                Path path = Paths.get(filesStorage+filePath);
                Files.delete(path);
                //log.info("文件删除成功：{}", filesStorage+filePath);
                Path path1 = Paths.get(filesStorage+filePath+".tmp");
                Files.move(path1, path);
                //log.info("文件重命名成功");
                log.info("文件转换成功：{}", filePath);
            } catch (IOException e) {
                e.printStackTrace();
            }

            String fileType = fileUtility.getFileType(knowFile.getFileName());
            knowFile.setFileType(fileType);
            File file = new File(filesStorage + knowFile.getFilePath());
            Long size = file.length();
            knowFile.setFileSize(size);
            log.info("knowFile:" + knowFile);*/
            knowledgeService.addKnowledge(knowFile,knowledge,userInfo);
        }else {
            knowledgeService.addKnowledge(null,knowledge,userInfo);
        }
        return result;


    }
    public  ResponseEntity<Object> getDoucmentEditStatus(String filePath,String fileName) throws ParseException {
        String url = officeUrl+officeCommand;
        Map<String,String>  map = new HashMap<String,String>();
        map.put("c", "forcesave");
        String docFileMd5 = Md5Utils.getFileMd5(new File(filesStorage+filePath));
        if (StringUtils.isBlank(docFileMd5)) {
            throw new DocumentException(ErrorCodeEnum.DOC_FILE_MD5_ERROR);
        }
//        String pathShortMd5 = Md5Utils.md5(filesStorage + filePath);
//        String nameShortMd5 = Md5Utils.md5(fileName);
//        Hashids hashids = new Hashids(DocumentConstants.HASH_KEY);
        // (将路径字符串短md5值 + 名称字符串短md5值) ==> 再转成短id形式 ==> 作为文档的key（暂且认为是不会重复的）
        //String key = hashids.encodeHex(String.format("%s%s%s", docFileMd5,pathShortMd5, nameShortMd5));

        File docFile = new File(filesStorage + filePath);
        fileName = StringUtils.isNotBlank(fileName) ? fileName : docFile.getName();
        String fileKey = FileUtil.fileKey(docFile, fileName);

        map.put("key", fileKey);
        map.put("userdata", "sample userdata");
        JSONObject obj = (JSONObject) new JSONParser().parse(FileUtil.editStatus(url, JSON.toJSONString(map)));
        return new ResponseEntity<Object>(obj, HttpStatus.OK);

    }

    public String fileKey(File docFile, String name) {
        String docFileMd5 = Md5Utils.getFileMd5(docFile);
        if (StringUtils.isBlank(docFileMd5)) {
            log.error("$$$ 构建文件信息失败！计算文件 md5 失败！");
            throw new DocumentException(ErrorCodeEnum.DOC_FILE_MD5_ERROR);
        }
        String pathShortMd5 = Md5Utils.md5(docFile.getAbsolutePath());
        String nameShortMd5 = Md5Utils.md5(name);
        Hashids hashids = new Hashids(DocumentConstants.HASH_KEY);
        // (将路径字符串短md5值 + 名称字符串短md5值) ==> 再转成短id形式 ==> 作为文档的key（暂且认为是不会重复的）
        String key = hashids.encodeHex(String.format("%s%s%s", docFileMd5,pathShortMd5, nameShortMd5));
        if (StringUtils.isBlank(key)) {
            throw new DocumentException(ErrorCodeEnum.DOC_FILE_KEY_ERROR);
        }
        return key;
    }


    /**
     * 文件下载
     */
    /*
    @Override
    public ResponseEntity<Resource> download(String fileName, String filePath) {
        Path path = Paths.get(filePath);
        try {
            Resource resource = new UrlResource(path.toUri());
            if (resource.exists() || resource.isReadable()) {
                return ResponseEntity.ok()
                        .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
                        .body(resource);
            } else {
                throw new RuntimeException("文件不存在或不可读");
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
            throw new RuntimeException("文件读取失败");
        }
    }
     */





    //生成文件夹路径
    private String getFilePath(){
        LocalDate currentDate = LocalDate.now();
        String current = currentDate.toString().replace("-", "");
        String filePath = current + "/";

        //判断文件夹是否存在，不存在创建
        Path directory = Paths.get(filesStorage + filePath);
        if (!Files.exists(directory)) {
            try {
                Files.createDirectories(directory);
                log.info("文件夹创建成功：" + filePath);
            } catch (IOException e) {
                log.error("文件夹创建失败：" + filePath);
                e.printStackTrace();
            }
        }
        return filePath;
    }

}
