package com.microservices.file.service.impl;


import com.alibaba.fastjson2.JSONObject;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.microservices.common.core.constant.Constants;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.DateUtils;
import com.microservices.common.core.utils.StringUtils;
import com.microservices.common.core.utils.file.FileTypeUtils;
import com.microservices.common.core.utils.file.FileUtils;
import com.microservices.common.core.utils.html.EscapeUtil;
import com.microservices.common.core.utils.uuid.IdUtils;
import com.microservices.common.httpClient.domain.GitLinkRequestUrl;
import com.microservices.common.httpClient.util.GitLinkRequestHelper;
import com.microservices.common.security.utils.SecurityUtils;
import com.microservices.file.mapper.SysFileInfoMapper;
import com.microservices.file.service.ISysFileInfoAsyncService;
import com.microservices.file.service.ISysFileInfoService;
import com.microservices.file.utils.FileUploadUtils;
import com.microservices.system.api.domain.SysFileInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 文件管理Service业务层处理
 *
 * @author microservices
 * @date 2021-12-29
 */
@Service
public class SysFileInfoServiceImpl implements ISysFileInfoService {
    private static final Logger logger = LoggerFactory.getLogger(SysFileInfoServiceImpl.class);
    @Autowired
    private SysFileInfoMapper sysFileInfoMapper;
    @Autowired
    private GitLinkRequestHelper gitLinkRequestHelper;
    @Autowired
    private ISysFileInfoAsyncService fileInfoAsyncService;
    /**
     * 上传文件存储在本地的根路径
     */
    @Value("${file.path}")
    private String localFilePath;

    /**
     * 文件名最大长度（Linux默认支持最大的文件长度为255字节）
     */
    public int fileNameMaxSize = 255;
    /**
     * 文件大小限制（单位MB）
     */
    @Value("${file.maxSize:50}")
    public Long fileMaxSize;
    /**
     * 允许文件类型(该格式允许通过静态资源直接访问)
     */
    @Value("${file.allowedExtension}")
    public String allowedExtension;
    /**
     * cms允许的文件后缀
     */
    @Value("${file.cmsAllowedExtension}")
    public String cmsAllowedExtension;

    /**
     * 域名或本机访问地址
     */
    @Value("${file.domain}")
    public String domain;

    @Override
    public SysFileInfo selectSysFileInfoByFileObjectName(String fileObjectName) {
        return sysFileInfoMapper.selectSysFileInfoByFileObjectName(fileObjectName);
    }

    /**
     * 查询文件管理
     *
     * @param fileId 文件管理主键
     * @return 文件管理
     */
    @Override
    public SysFileInfo selectSysFileInfoByFileId(Long fileId) {
        SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
        if (sysFileInfo == null) {
            throw new ServiceException("该文件不存在(文件Id[%d])", fileId);
        }
        return sysFileInfo;
    }

    /**
     * 查询文件管理列表
     *
     * @param sysFileInfo 文件管理
     * @return 文件管理
     */
    @Override
    public List<SysFileInfo> selectSysFileInfoList(SysFileInfo sysFileInfo) {
        return sysFileInfoMapper.selectSysFileInfoList(sysFileInfo);
    }

    /**
     * 新增文件管理
     *
     * @param sysFileInfo 文件管理
     * @return 结果
     */
    @Override
    public int insertSysFileInfo(SysFileInfo sysFileInfo) {
        // 当文件没有后缀时默认设置other后缀
        if (StringUtils.isEmpty(sysFileInfo.getFileSuffix())) {
            sysFileInfo.setFileSuffix("other");
        }
        sysFileInfo.setCreateBy(SecurityUtils.getUsername());
        sysFileInfo.setCreateTime(DateUtils.getNowDate());
        return sysFileInfoMapper.insertSysFileInfo(sysFileInfo);
    }

    /**
     * 修改文件管理
     *
     * @param sysFileInfo 文件管理
     * @return 结果
     */
    @Override
    public int updateSysFileInfo(SysFileInfo sysFileInfo) {
        sysFileInfo.setUpdateTime(DateUtils.getNowDate());
        return sysFileInfoMapper.updateSysFileInfo(sysFileInfo);
    }

    /**
     * 批量删除文件管理
     *
     * @param fileIds 需要删除的文件管理主键
     * @return 结果
     */
    @Override
    public int deleteSysFileInfoByFileIds(Long[] fileIds) {
        for (int i = 0; i < fileIds.length; i++) {
            Long fileId = fileIds[i];
            SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
            String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
            FileUtils.deleteFile(filePath);
        }
        return sysFileInfoMapper.deleteSysFileInfoByFileIds(fileIds);
    }

    /**
     * 删除文件管理信息
     *
     * @param fileId 文件管理主键
     * @return 结果
     */
    @Override
    public int deleteSysFileInfoByFileId(Long fileId) {
        SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
        String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
        FileUtils.deleteFile(filePath);
        return sysFileInfoMapper.deleteSysFileInfoByFileId(fileId);
    }

    @Override
    public int deleteSysFileInfoByFileObjectName(String fileObjectName) {
        SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileObjectName(fileObjectName);
        String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
        FileUtils.deleteFile(filePath);
        return sysFileInfoMapper.deleteSysFileInfoByFileObjectName(fileObjectName);
    }

    @Override
    public List<SysFileInfo> getFileListByFileIds(String fileIds) {
        if (StringUtils.isBlank(fileIds)) {
            return new ArrayList<>();
        }
        List<SysFileInfo> sysFileInfoList = new ArrayList<>();
        String[] fileIdStrList = fileIds.split(",");
        for (String fileIdStr : fileIdStrList) {
            Long fileId;
            try {
                fileId = Long.valueOf(fileIdStr);
                if (sysFileInfoList.stream().anyMatch(x -> x.getFileId().equals(fileId))) {
                    continue;
                }
            } catch (Exception e) {
                logger.error("检查附件Id时，附件Id字符串类型转Long类型失败：{}", e.getMessage());
                throw new ServiceException("错误的附件Id(附件id[" + fileIdStr + "])");
            }

            SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByFileId(fileId);
            if (sysFileInfo == null) {
                throw new ServiceException("该附件不存在(附件Id[" + fileId + "])");
            }
            //默认通过Id下载
            String url = EscapeUtil.removeExtraSlashOfUrl(domain + "/open/download/" + fileId);
            sysFileInfo.setDownloadUrl(url);
            sysFileInfoList.add(sysFileInfo);
        }
        return sysFileInfoList;
    }

    /**
     * 文件上传
     *
     * @param baseDir 相对应用的基目录
     * @param file    上传的文件
     * @return 返回上传成功的文件名
     */
    @Override
    public String upload(String baseDir, MultipartFile file) {
        // 根据字节长度判断
        int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).getBytes().length;
        if (fileNameLength > fileNameMaxSize) {
            throw new ServiceException(String.format("文件名称长度超出限定长度(文件名称最大长度[%d])", fileNameLength));
        }

        String fileName = FileUploadUtils.extractFilename(file);
        String absPath = FileUploadUtils.getAbsoluteFile(baseDir, fileName).getAbsolutePath();
        try {
            file.transferTo(Paths.get(absPath));
        } catch (IOException e) {
            logger.error("保存文件出现异常:{0}", e);
            throw new ServiceException("保存文件出现异常");
        }
        return FileUploadUtils.getPathFileName(fileName);
    }

    @Override
    public SysFileInfo selectSysFileInfoByFileIdentifier(String fileIdentifier) {
        SysFileInfo sysFileInfo = sysFileInfoMapper.selectSysFileInfoByIdentifier(fileIdentifier);
        if (sysFileInfo == null) {
            throw new ServiceException("该文件不存在(文件标识[%s])", fileIdentifier);
        }
        sysFileInfo.setDownloadUrl(EscapeUtil.removeExtraSlashOfUrl(domain + "/common/downloadByIdentifier/" + sysFileInfo.getFileIdentifier()));
        return sysFileInfo;
    }

    @Override
    public void fileDownloadById(Long fileId, HttpServletResponse response, HttpServletRequest request) {
        SysFileInfo sysFileInfo = selectSysFileInfoByFileId(fileId);
        if (StringUtils.isNotEmpty(sysFileInfo.getFileIdentifier())) {
            throw new ServiceException("当前文件无法下载");
        }
        fileDownload(sysFileInfo, response);
    }

    @Override
    public void fileDownloadByIdentifier(String fileIdentifier, HttpServletResponse response, HttpServletRequest request) {
        SysFileInfo sysFileInfo = selectSysFileInfoByFileIdentifier(fileIdentifier);
        if (StringUtils.isEmpty(sysFileInfo.getFilePath()) || !sysFileInfo.getFilePath().contains("/zone-identifier")) {
            throw new ServiceException("当前文件无法下载");
        }
        fileDownload(sysFileInfo, response);
    }

    @Override
    public List<SysFileInfo> getFileListByIdentifier(String fileIdentifiers) {
        if (StringUtils.isBlank(fileIdentifiers)) {
            return new ArrayList<>();
        }
        List<SysFileInfo> sysFileInfoList = new ArrayList<>();
        String[] fileIdentifierList = fileIdentifiers.split(",");
        for (String fileIdentifier : fileIdentifierList) {
            if (sysFileInfoList.stream().anyMatch(x -> x.getFileIdentifier().equals(fileIdentifier))) {
                continue;
            }

            SysFileInfo sysFileInfo = selectSysFileInfoByFileIdentifier(fileIdentifier);
            //默认通过Id下载
            String url = EscapeUtil.removeExtraSlashOfUrl(domain + "/common/downloadByIdentifier/" + fileIdentifier);
            sysFileInfo.setDownloadUrl(url);
            sysFileInfoList.add(sysFileInfo);
        }
        return sysFileInfoList;
    }

    @Override
    public boolean deleteFile(String fileIdentifier, String type) {
        switch (type) {
            case "pms-gitlink": {
                gitLinkRequestHelper.doDelete(GitLinkRequestUrl.DELETE_ATTACHMENTS(fileIdentifier));
                return true;
            }
            default: {
                SysFileInfo sysFileInfo = selectSysFileInfoByFileIdentifier(fileIdentifier);
                String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
                FileUtils.deleteFile(filePath);
                return sysFileInfoMapper.deleteSysFileInfoByFileId(sysFileInfo.getFileId()) > 0;
            }
        }
    }

    @Override
    public Boolean deleteFileByIdentifiers(String fileIdentifiers) {
        if (StringUtils.isEmpty(fileIdentifiers)) {
            return true;
        }
        int success = 0;
        String[] fileIdentifierList = fileIdentifiers.split(",");
        for (String fileIdentifier : fileIdentifierList) {
            SysFileInfo sysFileInfo = selectSysFileInfoByFileIdentifier(fileIdentifier);
            String filePath = localFilePath + StringUtils.substringAfter(sysFileInfo.getFilePath(), Constants.RESOURCE_PREFIX);
            FileUtils.deleteFile(filePath);
            success += sysFileInfoMapper.deleteSysFileInfoByFileId(sysFileInfo.getFileId());
        }
        return success > 0;
    }

    @Override
    public void getFileEntityByIdentifier(HttpServletResponse response, String fileIdentifier) {
        SysFileInfo sysFileInfo = selectSysFileInfoByFileIdentifier(fileIdentifier);
        String filePath = localFilePath + sysFileInfo.getFilePath();
        File file = new File(filePath);
        if (!file.exists()) {
            throw new ServiceException("该文件不存在");
        }
        response.setHeader("FileName", sysFileInfo.getFileOriginName());
        try {
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment;filename=" + file.getName());

            try (FileInputStream fileInputStream = new FileInputStream(file);
                 OutputStream outputStream = response.getOutputStream()) {
                // 根据实际情况调整缓冲区大小
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.flush();
            }
        } catch (IOException e) {
            throw new ServiceException("文件读取失败", e);
        }
    }

    @Override
    public SysFileInfo upload(MultipartFile file, String fileType, String hierarchy) {
        if (file == null || file.isEmpty()) {
            throw new ServiceException("不允许上传空文件");
        }
        SysFileInfo sysFileInfo = new SysFileInfo();
        // 上传文件路径
        String baseFilePath = localFilePath;
        boolean isCommonFileType = true;

        if (StringUtils.isBlank(fileType)) {
            fileType = "";
        }
        if (StringUtils.isBlank(hierarchy)) {
            hierarchy = "";
        } else {
            hierarchy = validPathSafety(hierarchy);
        }
        boolean isLocalUpload = true;
        Long fileId = null;
        switch (fileType) {
            case "pms-gitlink":
            case "gitlink": {
                isLocalUpload = false;
                JSONObject uploadRes = gitLinkRequestHelper.doPostFile(GitLinkRequestUrl.UPLOAD_ATTACHMENTS(), file);
                sysFileInfo.setFilePath(uploadRes.getString("title"));
                sysFileInfo.setFileSizeInfo(uploadRes.getString("filesize"));
                String gitlinkIdStr = uploadRes.getString("id");
                if (StringUtils.isNumeric(gitlinkIdStr)) {
                    try {
                        fileId = Long.valueOf(gitlinkIdStr);
                    } catch (NumberFormatException e) {
                        throw new ServiceException("Gitlink附件Id格式异常(附件Id[%s])", gitlinkIdStr);
                    }
                } else {
                    sysFileInfo.setFileIdentifier(gitlinkIdStr);
                }
                sysFileInfo.setFileSuffix(uploadRes.getString("content_type"));
                String url = uploadRes.getString("url");
                if (StringUtils.isEmpty(url)) {
                    url = GitLinkRequestUrl.GET_ATTACHMENTS(gitLinkRequestHelper.gitLinkUrl, gitlinkIdStr);
                }
                sysFileInfo.setDownloadUrl(url);
                break;
            }
            case "pms": {
                // 项目管理下需生成唯一标识，防止用户通过id递增访问
                sysFileInfo.setFileIdentifier(genFileIdentifier());
                baseFilePath += "/" + fileType + "/" + hierarchy + "/";
                isCommonFileType = false;
                break;
            }
            case "resource": {
                baseFilePath += "/" + fileType + "/";
                isCommonFileType = false;
                break;
            }
            case "zone-identifier": {
                // 生成唯一标识，防止用户通过id递增访问
                sysFileInfo.setFileIdentifier(genFileIdentifier());
                baseFilePath += "/" + fileType + "/";
                isCommonFileType = false;
                break;
            }
            case "cms": {
                //获取配置文件中允许类型列表
                String[] allowedExtensions = null;
                if (StringUtils.isNotEmpty(cmsAllowedExtension)) {
                    allowedExtensions = cmsAllowedExtension.split(",");
                }
                assertAllowed(file, allowedExtensions);

                baseFilePath += "/" + fileType + "/";
                isCommonFileType = false;
                break;
            }
            default: {
                //获取配置文件中允许类型列表
                String[] allowedExtensions = null;
                if (StringUtils.isNotEmpty(allowedExtension)) {
                    allowedExtensions = allowedExtension.split(",");
                }
                assertAllowed(file, allowedExtensions);
            }
        }

        if (isLocalUpload) {
            // 上传并返回新文件名称
            String filePath = upload(baseFilePath, file);
            if (!isCommonFileType) {
                if ("pms".equals(fileType)) {
                    filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + hierarchy + "/" + filePath);
                } else {
                    filePath = EscapeUtil.removeExtraSlashOfUrl("/" + fileType + "/" + filePath);
                }
            }
            sysFileInfo.setFilePath(filePath);

            int newFileNameSeparatorIndex = filePath.lastIndexOf("/");
            String newFileName = filePath.substring(newFileNameSeparatorIndex + 1).toLowerCase();
            int separatorIndex = newFileName.lastIndexOf(".");
            sysFileInfo.setFileObjectName(newFileName);
            sysFileInfo.setFileSuffix(newFileName.substring(separatorIndex + 1).toLowerCase());

            sysFileInfo.setFileSizeInfo(FileUtils.calcFileSize(file.getSize()));
            // 获取文件内容信息
            try {
                ContentInfoUtil util = new ContentInfoUtil();
                ContentInfo contentInfo = util.findMatch(file.getInputStream());
                if (contentInfo != null) {
                    sysFileInfo.setContentType(contentInfo.getContentType().name());
                    sysFileInfo.setContentTypeName(contentInfo.getName());
                    sysFileInfo.setContentTypeMessage(contentInfo.getMessage());
                    sysFileInfo.setMimeType(contentInfo.getMimeType());
                    sysFileInfo.setIsPartialMatch(contentInfo.isPartial() ? 1 : 0);
                }
            } catch (Exception e) {
                logger.error("获取文件内容信息出现异常:{0}", e);
                throw new ServiceException("获取文件内容信息出现异常(文件名[%s])", filePath);
            }
            sysFileInfo.setFileOriginName(file.getOriginalFilename());
            sysFileInfo.setDelFlag("N");
            insertSysFileInfo(sysFileInfo);
            fileId = sysFileInfo.getFileId();
            //当上传路径为默认路径时，标识支持通过静态资源访问
            String url;
            if (isCommonFileType) {
                url = EscapeUtil.removeExtraSlashOfUrl(domain + "/statics/" + filePath);
            } else if (StringUtils.isNotEmpty(sysFileInfo.getFileIdentifier())) {
                url = EscapeUtil.removeExtraSlashOfUrl(domain + "/common/downloadByIdentifier/" + sysFileInfo.getFileIdentifier());
            } else {
                url = EscapeUtil.removeExtraSlashOfUrl(domain + "/open/download/" + fileId);
            }
            sysFileInfo.setDownloadUrl(url);
        }

        return sysFileInfo;
    }

    @Override
    public String packagedFile(HashMap<String, String> packagedStructure, String zipFileName, String type, String hierarchy) {
        String fileIdentifier = genFileIdentifier();
        fileInfoAsyncService.asyncPackagedFile(fileIdentifier, packagedStructure, zipFileName, type, hierarchy);
        return fileIdentifier;
    }

    @Override
    public List<String> uploadFileToForge(String fileIdentifiers) {
        List<SysFileInfo> fileInfoList = getFileListByIdentifier(fileIdentifiers);
        try {
            List<String> forgeFileIdentifierList = new ArrayList<>();
            for (SysFileInfo fileInfo : fileInfoList) {
                String filePath = localFilePath + fileInfo.getFilePath();
                File file = new File(filePath);
                if (!file.exists()) {
                    throw new ServiceException("该文件不存在");
                }
                // 拷贝文件到打包目录
                FileInputStream fileInputStream = new FileInputStream(file);
                MultipartFile multipartFile = FileUtils.createMultipartFile(fileInputStream, fileInfo.getFileOriginName());
                SysFileInfo sysFileInfo = upload(multipartFile, "pms-gitlink", null);
                forgeFileIdentifierList.add(sysFileInfo.getFileIdentifier());
            }
            return forgeFileIdentifierList;
        } catch (IOException e) {
            logger.error("上传本地文件到Forge失败{}", e.getMessage());
            throw new ServiceException("上传本地文件到Forge失败");
        }
    }

    private void fileDownload(SysFileInfo sysFileInfo, HttpServletResponse response) {
        try {
            String fileName = sysFileInfo.getFileOriginName();
            String filePath = localFilePath + sysFileInfo.getFilePath();
            if (StringUtils.isNotEmpty(sysFileInfo.getMimeType())) {
                response.setContentType(sysFileInfo.getMimeType());
            } else {
                response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            }
            FileUtils.setAttachmentResponseHeader(response, fileName);
            FileUtils.writeBytes(filePath, response.getOutputStream());
            int downloadCount = 1;
            if (sysFileInfo.getDownloadCount() != null) {
                downloadCount = sysFileInfo.getDownloadCount() + 1;
            }
            sysFileInfo.setDownloadCount(downloadCount);
            updateSysFileInfo(sysFileInfo);

        } catch (Exception e) {
            throw new ServiceException("文件下载异常：%s", e.getMessage());
        }
    }

    /**
     * 文件大小校验
     *
     * @param file 上传的文件
     */
    public final void assertAllowed(MultipartFile file, String[] allowedExtension) {
        long size = file.getSize();
        long maxSizeByte = fileMaxSize * 1024 * 1024;
        if (size > maxSizeByte) {
            throw new ServiceException(String.format("单个文件限制%dMB以内", fileMaxSize));
        }

        String fileName = file.getOriginalFilename();
        String extension = FileTypeUtils.getExtension(file);
        if (allowedExtension != null && !FileUploadUtils.isAllowedExtension(extension, allowedExtension)) {
            throw new ServiceException(String.format("当前上传的文件格式不支持(文件名[%s]，文件格式[%s],支持的文件格式[%s])"
                    , fileName, extension, Arrays.toString(allowedExtension)));
        }
    }

    /**
     * 校验路径安全性
     *
     * @param hierarchy 文件层级
     * @return 校验后的hierarchy
     */
    private String validPathSafety(String hierarchy) {

        // 定义允许的字符集合，包括字母、数字、下划线、短横线和斜杠
        String pattern = "^[a-zA-Z0-9_/\\-]+$";
        if (!Pattern.matches(pattern, hierarchy)) {
            throw new ServiceException("hierarchy参数非法");
        }
        return hierarchy;
    }

    private String genFileIdentifier() {
        String fileIdentifier = null;
        boolean identifierIsExist = true;
        while (identifierIsExist) {
            // 项目管理下需生成唯一标识，防止用户通过id递增访问
            fileIdentifier = IdUtils.fastShortUUID();
            SysFileInfo sysFileInfoByIdentifier = sysFileInfoMapper.selectSysFileInfoByIdentifier(fileIdentifier);
            if (sysFileInfoByIdentifier == null) {
                identifierIsExist = false;
            }
        }
        return fileIdentifier;
    }
}
