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.Document;
import com.zzsn.knowbase.entity.KbAuthorizedUser;
import com.zzsn.knowbase.entity.KnowFile;
import com.zzsn.knowbase.entity.Knowledge;
import com.zzsn.knowbase.service.DocumentService;
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.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;

    @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 fileName = file.getOriginalFilename();  // 获取文件名称
            String fileExtension = fileUtility.getFileExtension(fileName);  // 获取文件扩展名
            String fileType = fileUtility.getFileType(fileName);  //获取文件类型
            long fileSize = file.getSize();  // get file size

            // check if the file size exceeds the maximum file size or is less than 0
            if (fileUtility.getMaxFileSize() < fileSize || fileSize <= 0) {
                Result result = Result.error("文件大小不正确！");
                return result;
            }
            // check if file extension is supported by the editor
            if (!fileUtility.getFileExts().contains(fileExtension)) {
                Result result = Result.error("不支持的文件类型！");
                return result;
            }

            String fileId = codeGenerateUtil.geneIdNo(Constants.FINANCE, 8);
            String filePath = getFilePath() + fileId + fileExtension;
            //byte[] bytes = file.getBytes();  // get file in bytes
            //Files.write(Paths.get(filePath), bytes);
            file.transferTo(new File(filesStorage + filePath));

            KnowFile knowFile = new KnowFile();
            knowFile.setFileId(fileId);
            knowFile.setFileName(fileName);
            knowFile.setFilePath(filePath);
            knowFile.setFileType(fileType);
            knowFile.setFileSize(fileSize);
            Result result = Result.OK(knowFile);
            return result;  // create user metadata and return it
        } catch (Exception e) {
            e.printStackTrace();
        }
        // if the operation of file uploading is unsuccessful, an error occurs
        Result result = Result.error("上传文件时出现问题！");
        return result;
    }

    @Override
    public List<Result<KnowFile>> upload(Map<String, MultipartFile> fileMap) {
        List<Result<KnowFile>> list = new ArrayList<>();
        for (Map.Entry<String,MultipartFile> entity : fileMap.entrySet()) {
            MultipartFile file = entity.getValue();// 获取上传文件对象
            Result<KnowFile> result = upload(file);
            list.add(result);
        }
        return list;
    }

    @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;
        System.out.println("===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);
            System.out.println(jsonObj);
            System.out.println("===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");
        System.out.println("====文档编辑完成，现在开始保存编辑后的文档，其下载地址为:" + downloadUri);
        //解析得出文件名
        //String fileName = downloadUri.substring(downloadUri.lastIndexOf('/')+1);
        String fileName = request.getParameter("fileName");
        System.out.println("====下载的文件名:" + 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) {
        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.setUpdateBy(userInfo.getName());
        knowledge.setUpdateTime(cn.hutool.core.date.DateUtil.formatDateTime(new Date()).replace(" ", "T"));

        Integer verifyStatus = knowledge.getVerifyStatus();
        if(verifyStatus != null && (verifyStatus == 1 || verifyStatus == 2))
            knowledge.setVerifyTime(cn.hutool.core.date.DateUtil.formatDateTime(new Date()).replace(" ", "T"));
        knowledge.setVerifierId(userInfo.getUserId());
        knowledge.setVerifierName(userInfo.getUsername());

        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();
        }

        String fileType = fileUtility.getFileType(knowFile.getFileName());
        knowFile.setFileType(fileType);
        File file = new File(filesStorage + knowFile.getFilePath());
        Long size = file.length();
        knowFile.setFileSize(size);
        knowledgeService.addKnowledge(knowFile,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;
    }

}
