/*
 * Copyright (c) 2023 codvision.com All Rights Reserved.
 */

package com.codvision.base.service.impl;

import cn.hutool.core.img.Img;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.services.s3.model.S3Object;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.codvision.authcore.util.AuthUtil;
import com.codvision.base.entity.FileUploadResult;
import com.codvision.base.entity.SysConfig;
import com.codvision.base.entity.SysFile;
import com.codvision.base.entity.vo.SysFileVO;
import com.codvision.base.enums.MimeTypeEnum;
import com.codvision.base.enums.StorageConstants;
import com.codvision.base.enums.StorageType;
import com.codvision.base.mapper.SysFileMapper;
import com.codvision.base.service.ISysConfigService;
import com.codvision.base.service.ISysFileService;
import com.codvision.base.support.OssTemplate;
import com.codvision.commoncore.common.ExpiryMap;
import com.codvision.commoncore.enums.TrueOrFalse;
import com.codvision.commoncore.exception.BusinessException;
import com.codvision.commoncore.utils.Md5Utils;
import com.codvision.webcore.service.impl.BaseServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import static com.codvision.base.support.BaseConst.OSS_FILE;

/**
 * 系统文件 服务实现类
 *
 * @author lingee
 * @date 2023/11/16
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class SysFileServiceImpl extends BaseServiceImpl<SysFileMapper, SysFile, SysFileVO> implements ISysFileService {

    private final static ExpiryMap<String, OssTemplate> ossTemplateMap = new ExpiryMap<>();

    @Resource
    private ISysConfigService configService;

    @Override
    public void updateIsValidByFileName(String fileName) {
        this.update(Wrappers.<SysFile>lambdaUpdate().set(SysFile::getIsValid, TrueOrFalse.TRUE.getIntVal())
                .eq(SysFile::getFileName, fileName));
    }

    @Override
    @SneakyThrows
    public void deleteFile(List<Integer> ids) {
        for (Integer id : ids) {
            SysFile file = this.getById(id);
            //上传到文件服务器和ECS服务器的删除
            if (OSS_FILE.equals(file.getPosition())) {
                getOssTemplate().removeObject(configService.getValueThrowException("OSS_BUCKET_NAME"), file.getFileName());
            } else {
                String property = System.getProperty("os.name");
                if (property.toLowerCase().startsWith("win")) {
                    log.info("当前系统为Windows不进行文件清理");
                    return;
                }
                log.info("开始删除文件 destDirPath:{}", file.getFileName());
                Process p;
                InputStreamReader inputStreamReader = null;
                BufferedReader br = null;
                String fileName = StrUtil.replace(file.getFileName(), StorageConstants.FILE_DOWNLOAD_PATH_PREFIX, "");
                String co = "rm -rf " + StorageConstants.UPLOAD_PATH + fileName;
                try {
                    p = Runtime.getRuntime().exec(co);
                    inputStreamReader = new InputStreamReader(p.getInputStream());
                    br = new BufferedReader(inputStreamReader);
                    int returnCode = p.waitFor();
                    log.info("执行删除任务完毕 删除命令: {}时间:{}", co, returnCode);
                } catch (IOException e) {
                    log.error("clearFile IOException:", e);
                } catch (InterruptedException e) {
                    log.error("clearFile InterruptedException:", e);
                } finally {
                    br.close();
                }
            }
        }
        this.removeByIds(ids);
    }

    @Override
    public FileUploadResult uploadFile(MultipartFile file, StorageType storageType) {
        String type = getStorageType(storageType);

        if (StorageType.local.getType().equals(type)) {
            FileUploadResult result = uploadFileToLocal(file);
            // 记录文件上传记录
            fileLog(file, result.getDownloadUrl(), result.getBucketName(), StorageType.local);
            return result;
        } else {
            FileUploadResult result = uploadFileToOss(file);
            if (Objects.requireNonNull(file.getContentType()).contains(StorageConstants.FILE_IMAGE_TYPE)) {
                compressAndUploadFileToOss(result, file);
            }
            // 记录文件上传记录
            fileLog(file, result.getDownloadUrl(), result.getBucketName(), StorageType.oss);
            return result;
        }
    }

    @Override
    public void getFile(String bucket, String fileName, HttpServletResponse response) {
        String filePath = String.format(StorageConstants.FILE_DOWNLOAD_PATH, bucket, fileName);
        SysFile sysFile = this.getOne(Wrappers.<SysFile>lambdaQuery().eq(SysFile::getFileName, filePath).last("limit 1"));
        Assert.notNull(sysFile, "文件不存在");
        response.setContentType("application/octet-stream; charset=UTF-8");

        readSysFile(bucket, fileName, response, sysFile);
    }

    @Override
    public void download(String bucket, String fileName, HttpServletResponse response) {
        String filePath = String.format(StorageConstants.FILE_DOWNLOAD_PATH, bucket, fileName);
        SysFile sysFile = this.getOne(Wrappers.<SysFile>lambdaQuery().eq(SysFile::getFileName, filePath).last("limit 1"));
        Assert.notNull(sysFile, "文件不存在");

        response.setContentType("application/octet-stream; charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + fileName);

        readSysFile(bucket, fileName, response, sysFile);
    }

    @Override
    public void preview(String bucket, String fileName, HttpServletResponse response) {
        String filePath = String.format(StorageConstants.FILE_DOWNLOAD_PATH, bucket, fileName);
        SysFile sysFile = this.getOne(Wrappers.<SysFile>lambdaQuery().eq(SysFile::getFileName, filePath).last("limit 1"));
        Assert.notNull(sysFile, "文件不存在");

        response.setContentType("application/octet-stream; charset=UTF-8");
        String ext = FileUtil.extName(fileName);
        String contentType = MimeTypeEnum.getContentType(ext);
        response.setHeader("Content-Type", contentType);

        readSysFile(bucket, fileName, response, sysFile);
    }

    @Override
    public InputStream getFileStream(String filePath) {
        SysFile sysFile = this.getOne(Wrappers.<SysFile>lambdaQuery().eq(SysFile::getFileName, filePath).last("limit 1"));
        Assert.notNull(sysFile, "文件不存在");

        String bucketName = configService.getValueThrowException("OSS_BUCKET_NAME");
        String fileName;
        if (StorageType.oss.getPosition().equals(sysFile.getPosition())) {
            fileName = StrUtil.replace(filePath, StorageConstants.FILE_DOWNLOAD_PATH_PREFIX + bucketName + "/", "");
            S3Object s3Object = getOssTemplate().getObject(bucketName, fileName);
            return s3Object.getObjectContent();
        }
        fileName = StrUtil.replace(filePath, StorageConstants.FILE_DOWNLOAD_PATH_PREFIX, "");
        return FileUtil.getInputStream(StorageConstants.UPLOAD_PATH + fileName);
    }

    /**
     * 文件管理数据记录,收集管理追踪文件
     *
     * @param file     上传文件格式
     * @param fileName 文件名
     */
    private void fileLog(MultipartFile file, String fileName, String bucketName, StorageType storageType) {
        SysFile sysFile = new SysFile();
        sysFile.setFileName(fileName);
        sysFile.setOriginal(file.getOriginalFilename());
        sysFile.setFileSize(file.getSize());
        sysFile.setSource(0);
        sysFile.setPosition(storageType.getPosition());
        sysFile.setType(FileUtil.extName(file.getOriginalFilename()));
        sysFile.setBucketName(bucketName);
        sysFile.setCreateUser(AuthUtil.getCurrentUser().getName());
        this.save(sysFile);
    }

    /**
     * 读取文件
     *
     * @param bucket   文件夹
     * @param fileName 文件名
     * @param response 响应
     * @param sysFile  文件信息
     */
    private void readSysFile(String bucket, String fileName, HttpServletResponse response, SysFile sysFile) {
        try {
            if (StorageType.local.getPosition().equals(sysFile.getPosition())) {
                FileUtil.writeToStream(StorageConstants.UPLOAD_PATH + bucket + "/" + fileName, response.getOutputStream());
            } else {
                S3Object s3Object = getOssTemplate().getObject(bucket, fileName);
                IoUtil.copy(s3Object.getObjectContent(), response.getOutputStream());
            }
        } catch (Exception e) {
            log.error("文件读取异常: {}", e.getLocalizedMessage());
            throw new BusinessException(e.getMessage());
        }
    }

    /**
     * 上传文件至对象存储
     *
     * @param file 文件
     * @return 上传结果
     */
    private FileUploadResult uploadFileToOss(MultipartFile file) {
        FileUploadResult fileUploadResult = new FileUploadResult();

        String fileName = IdUtil.simpleUUID() + file.getOriginalFilename();
        String ext = FileUtil.extName(file.getOriginalFilename());

        String bucketName = configService.getValueThrowException("OSS_BUCKET_NAME");
        fileUploadResult.setBucketName(bucketName);
        fileUploadResult.setFileName(fileName);

        try {
            getOssTemplate().putObject(bucketName, fileName, file.getInputStream(), MimeTypeEnum.getContentType(ext));
        } catch (Exception e) {
            log.error("上传失败", e);
            throw new BusinessException(e.getMessage());
        }
        fileUploadResult.setDownloadUrl(String.format(StorageConstants.FILE_DOWNLOAD_PATH, bucketName, fileName));
        if (isPreviewFile(ext)) {
            fileUploadResult.setPreviewUrl(String.format(StorageConstants.FILE_PREVIEW_PATH, bucketName, fileName));
        }
        return fileUploadResult;
    }

    /**
     * 上传缩略图
     *
     * @param fileUploadResult 上传结果
     * @param file             文件
     */
    private void compressAndUploadFileToOss(FileUploadResult fileUploadResult, MultipartFile file) {
        long maxFileSize = Long.parseLong(configService.getValueByKey("MAX_FILE_SIZE"));
        if (file.getSize() / 1024 / 1024 > maxFileSize) {
            throw new BusinessException("上传图片过大");
        }
        String ext = FileUtil.extName(file.getOriginalFilename());
        String fileName = IdUtil.simpleUUID() + file.getOriginalFilename();
        String bucketName = configService.getValueThrowException("OSS_BUCKET_NAME");

        ByteArrayInputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        try {
            outputStream = new ByteArrayOutputStream();
            Img.from(file.getInputStream()).scale(0.5f).setQuality(0.5f).write(outputStream);
            inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            getOssTemplate().putObject(bucketName, fileName, inputStream, MimeTypeEnum.getContentType(ext));
        } catch (Exception e) {
            log.error("图片上传失败", e);
            throw new BusinessException(e.getMessage());
        } finally {
            try {
                if (null != inputStream) {
                    inputStream.close();
                }
                if (null != outputStream) {
                    outputStream.close();
                }
            } catch (IOException ex) {
                log.error("文件流关闭失败", ex);
                throw new BusinessException(ex.getMessage());
            }
        }
        fileUploadResult.setCompressUrl(String.format(StorageConstants.FILE_PREVIEW_PATH, bucketName, fileName));
    }

    /**
     * 上传文件至本地
     *
     * @param file 文件
     * @return 上传结果
     */
    private FileUploadResult uploadFileToLocal(MultipartFile file) {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
        FileUploadResult fileUploadResult = new FileUploadResult();
        String day = format.format(new Date());
        File savePath = new File(StorageConstants.UPLOAD_PATH + day + "/");
        if (!savePath.exists()) {
            savePath.mkdirs();
            savePath.setWritable(true);
            savePath.setReadable(true);
        }

        String mime = file.getContentType();
        String mimeList = configService.getValueByKey("MIME_LIST");
        if (!mimeList.contains(mime)) {
            throw new BusinessException(30004);
        }

        String originalFilename = file.getOriginalFilename();
        String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        String extNameList = configService.getValueByKey("EXT_NAME_LIST");
        if (!extNameList.contains(extName)) {
            throw new BusinessException(30004);
        }

        if (file.isEmpty()) {
            throw new BusinessException(30003);
        }

        try {
            byte[] bytes = file.getBytes();
            String uuid = UUID.randomUUID().toString().replaceAll("-", "").trim();
            String fileName = uuid + originalFilename;
            File saveFile = new File(savePath, fileName);
            try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(saveFile))) {
                stream.write(bytes);
            }
            fileUploadResult.setFileName(fileName);
            fileUploadResult.setDownloadUrl(String.format(StorageConstants.FILE_DOWNLOAD_PATH, day, fileName));
            if (isPreviewFile(extName)) {
                fileUploadResult.setPreviewUrl(String.format(StorageConstants.FILE_PREVIEW_PATH, day, fileName));
            }
        } catch (Exception e) {
            log.error("文件上传失败", e);
            throw new BusinessException(10000);
        }
        fileUploadResult.setBucketName(day);
        return fileUploadResult;
    }

    /**
     * 是否可预览文件
     *
     * @param ext 文件扩展名
     * @return true or false
     */
    private boolean isPreviewFile(String ext) {
        ext = StringUtils.lowerCase(ext);
        if (ext.contains("png")
                || ext.contains("jpeg")
                || ext.contains("jpg")
                || ext.contains("gif")
                || ext.contains("pdf")
                || ext.contains("mp4")) {
            return true;
        }
        return false;
    }

    /**
     * 获取对象存储
     *
     * @return 对象存储
     */
    private OssTemplate getOssTemplate() {
        String endpoint = configService.getValueThrowException("OSS_ENDPOINT");
        String region = configService.getValueThrowException("OSS_REGION");
        String accessKey = configService.getValueThrowException("OSS_ACCESS_KEY");
        String secretKey = configService.getValueThrowException("OSS_SECRET");

        if (StrUtil.isBlank(endpoint)
                || StrUtil.isBlank(accessKey)
                || StrUtil.isBlank(secretKey)) {
            throw new BusinessException("对象存储未配置完整");
        }

        String key = Md5Utils.getMd5(endpoint + "|" + region + "|" + accessKey + "|" + secretKey);

        OssTemplate ossTemplate = ossTemplateMap.get(key);
        if (ObjectUtil.isNotEmpty(ossTemplate)) {
            return ossTemplate;
        }
        ossTemplate = new OssTemplate(endpoint, region, accessKey, secretKey);
        ossTemplateMap.put(key, ossTemplate, 3600 * 24 * 1000);

        return ossTemplate;
    }

    /**
     * 获取存储类型
     *
     * @param storageType 存储类型
     * @return 存储类型
     */
    private String getStorageType(StorageType storageType) {
        String type = StorageType.local.getType();
        if (ObjectUtil.isNotEmpty(storageType)) {
            type = storageType.getType();
        } else {
            //获取系统文件上传方式
            SysConfig config = configService.getByKey("FILE_UPLOAD_TYPE");
            if (ObjectUtil.isNotEmpty(config)) {
                type = config.getV();
            }
        }
        return type;
    }
}
