package com.esv.freight.file.module.file.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.esv.freight.file.common.em.DbDeletedEnum;
import com.esv.freight.file.common.exception.EException;
import com.esv.freight.file.common.util.AESSecretUtils;
import com.esv.freight.file.module.file.dao.FileMetaDao;
import com.esv.freight.file.module.file.em.FileTypeEnum;
import com.esv.freight.file.module.file.entity.FileMetaEntity;
import com.esv.freight.file.module.file.form.FileForm;
import com.esv.freight.file.module.file.service.FileMetaService;
import com.esv.freight.file.module.file.vo.FileVO;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64;
import java.util.Date;


@Service("fileMetaService")
@Slf4j
@RefreshScope
public class FileMetaServiceImpl extends ServiceImpl<FileMetaDao, FileMetaEntity> implements FileMetaService {

    @Value("${aes.sha1prng.key:3.1415926535}")
    private String AES_KEY;

    @Value("${file.download.base-url:undefined}")
    private String FILE_DOWNLOAD_BASE_URL;

    private GridFsTemplate gridFsTemplate;

    private MongoDbFactory mongoDbFactory;

    @Autowired
    public FileMetaServiceImpl(GridFsTemplate gridFsTemplate, MongoDbFactory mongoDbFactory) {
        this.gridFsTemplate = gridFsTemplate;
        this.mongoDbFactory = mongoDbFactory;
    }

    @Override
    public FileVO uploadFile(FileForm fileForm) throws EException {
        String fileType = fileForm.getFileType();
        String fileName = fileForm.getFileName();
        String fileData = fileForm.getFileData();
        byte[] bytes = Base64.getDecoder().decode(fileData);
        InputStream inputStream = new ByteArrayInputStream(bytes);

        // MongoDB存储文件
        ObjectId objectId;
        String fileId;
        try {
            objectId = gridFsTemplate.store(inputStream, fileName, FileTypeEnum.getEnumByType(fileType).getContentType());
            fileId = objectId.toString();
        } catch (Exception e) {
            log.error("存储文件数据时发生错误：{}", e.getMessage(), e);
            throw new EException("存储文件数据时发生错误");
        }

        // MySQL存储文件元数据
        FileMetaEntity metaEntity = new FileMetaEntity();
        metaEntity.setFileId(fileId);
        metaEntity.setFileMd5(DigestUtils.md5Hex(bytes));
        metaEntity.setFileSize((long) bytes.length);
        metaEntity.setFileName(fileName);
        metaEntity.setFileType(fileType);
        try {
            this.baseMapper.insert(metaEntity);
        } catch (Exception e) {
            log.error("存储文件元信息时发生错误：{}", e.getMessage(), e);

            // 删除MongoDB存储文件
            try {
                deleteMongoFile(fileId);
            } catch (Exception ex) {
                log.error("删除文件[id={}]数据时发生错误：{}", fileId, e.getMessage(), e);
            }

            throw new EException("存储文件元信息发生错误");
        }

        // 返回数据
        String aesId = AESSecretUtils.encryptToStr("esv," + fileId + "," + System.currentTimeMillis(), AES_KEY);
        String downloadUrl = FILE_DOWNLOAD_BASE_URL + aesId;
        FileVO fileVO = new FileVO(fileId, downloadUrl);

        return fileVO;
    }

    @Override
    public void directDownload(String id, HttpServletResponse response) {
        // 校验id是否有效
        String decryptId;
        try {
            decryptId = StringUtils.trimToEmpty(AESSecretUtils.decryptToStr(id, AES_KEY));
        } catch (Exception e) {
            this.downloadError("无效的文件ID", response);
            return;
        }
        String[] ids = decryptId.split(",");
        if (3 != ids.length || !"esv".equals(ids[0])) {
            this.downloadError("无效的文件ID", response);
            return;
        }
        String fileId = ids[1];

        // 获取文件ID
        FileMetaEntity queryEntity = new FileMetaEntity();
        queryEntity.setFileId(fileId);
        QueryWrapper<FileMetaEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.setEntity(queryEntity);
        FileMetaEntity fileMetaEntity = this.getOne(queryWrapper);
        if (null == fileMetaEntity) {
            this.downloadError("无效的文件ID", response);
            return;
        }

        // 获取文件数据
        Query query = Query.query(Criteria.where("_id").is(fileId));
        GridFSFile gridFSFile = gridFsTemplate.findOne(query);
        if (null == gridFSFile) {
            this.downloadError("文件数据不存在", response);
            return;
        } else {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String fileName = gridFSFile.getFilename();
            OutputStream outputStream = null;
            try {
                // 处理中文文件名乱码
                if (request.getHeader("User-Agent").toUpperCase().contains("MSIE") ||
                        request.getHeader("User-Agent").toUpperCase().contains("TRIDENT")
                        || request.getHeader("User-Agent").toUpperCase().contains("EDGE")) {
                        fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
                } else {
                    // 非IE浏览器的处理：
                    fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
                }

                response.reset();
                response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\"");
                response.setCharacterEncoding("UTF-8");
                response.setContentLength((int) gridFSFile.getLength());
                FileTypeEnum fileTypeEnum = FileTypeEnum.getEnumByType(fileMetaEntity.getFileType());
                response.setContentType(fileTypeEnum.getContentType());

                GridFSBucket bucket = GridFSBuckets.create(mongoDbFactory.getDb());
                GridFSDownloadStream gridFSDownloadStream = bucket.openDownloadStream(gridFSFile.getObjectId());
                GridFsResource gridFsResource = new GridFsResource(gridFSFile, gridFSDownloadStream);
                outputStream = response.getOutputStream();
                IOUtils.copy(gridFsResource.getInputStream(), outputStream);
                outputStream.flush();
                outputStream.close();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                this.downloadError("服务内部错误", response);
            } finally {
                if (null != outputStream) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    }
                }
            }
        }
    }

    @Override
    public void deleteFile(FileForm fileForm) throws EException {
        // 校验文件是否存在
        String fileId = fileForm.getId();
        FileMetaEntity queryEntity = new FileMetaEntity();
        queryEntity.setFileId(fileId);
        QueryWrapper<FileMetaEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.setEntity(queryEntity);
        FileMetaEntity fileMetaEntity = this.getOne(queryWrapper);
        if (null == fileMetaEntity) {
            throw new EException(1001, "文件不存在");
        }

        // 逻辑删除MySQL记录
        FileMetaEntity updateEntity = new FileMetaEntity();
        updateEntity.setDeleted(DbDeletedEnum.YES.getCode());
        updateEntity.setDeleteTime(new Date());
        updateEntity.setFileId(fileId);
        int flag = this.baseMapper.logicDelete(updateEntity);
        if (0 == flag) {
            throw new EException("文件删除失败");
        } else {
            // 不论MongoDB是否删除，都认为删除请求成功
            try {
                deleteMongoFile(fileId);
            } catch (Exception e) {
                log.error("删除文件[id={}]数据时发生错误：{}", fileId, e.getMessage(), e);
            }
        }
    }

    @Override
    public String getFileData(FileForm fileForm) throws EException {
        // 校验文件是否存在
        String fileId = fileForm.getId();
        FileMetaEntity queryEntity = new FileMetaEntity();
        queryEntity.setFileId(fileId);
        QueryWrapper<FileMetaEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.setEntity(queryEntity);
        FileMetaEntity fileMetaEntity = this.getOne(queryWrapper);
        if (null == fileMetaEntity) {
            throw new EException(1001, "文件不存在");
        }

        // 获取文件数据
        String fileData;
        Query query = Query.query(Criteria.where("_id").is(fileId));
        GridFSFile gridFSFile = gridFsTemplate.findOne(query);
        if (null == gridFSFile) {
            throw new EException("文件数据不存在");
        }
        GridFSBucket bucket = GridFSBuckets.create(mongoDbFactory.getDb());
        GridFSDownloadStream gridFSDownloadStream = bucket.openDownloadStream(gridFSFile.getObjectId());
        GridFsResource gridFsResource = new GridFsResource(gridFSFile, gridFSDownloadStream);
        try {
            InputStream inputStream = gridFsResource.getInputStream();
            byte[] bytes = IOUtils.toByteArray(inputStream);
            fileData = Base64.getEncoder().encodeToString(bytes);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new EException("服务内部错误");
        }

        return fileData;
    }

    /**
     * description 删除MongoDB文件
     * param [id]
     * return void
     * author Administrator
     * createTime 2020/04/14 15:33
     **/
    private void deleteMongoFile(String id) throws Exception {
        Query query = Query.query(Criteria.where("_id").is(id));
        GridFSFile gridFSFile = gridFsTemplate.findOne(query);
        if (null != gridFSFile) {
            gridFsTemplate.delete(query);
        }
    }

    /**
     * 文件下载错误返回
     * @param errorMsg
     * @param response
     */
    private void downloadError(String errorMsg, HttpServletResponse response) {
        try {
            response.reset();
            response.setContentType("text/html;charset=UTF-8");
            response.setCharacterEncoding("UTF-8");
            OutputStream out = response.getOutputStream();
            out.write(errorMsg.getBytes("UTF-8"));
            out.flush();
            out.close();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

}