package net.northking.cctp.common.s3.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.s3.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.lokra.seaweedfs.core.FileSource;
import org.lokra.seaweedfs.core.FileTemplate;
import org.lokra.seaweedfs.core.file.FileHandleStatus;
import org.lokra.seaweedfs.core.http.StreamResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import java.io.*;
import java.util.*;

public class SeaweedFileStorage implements SimpleStorageService {
    private static final Logger logger = LoggerFactory.getLogger(SeaweedFileStorage.class);

    private AmazonS3Properties properties;

    private FileTemplate fileTemplate;

    private AttachmentProducer attachmentProducer;


    private RedisTemplate redisTemplate;



    public SeaweedFileStorage(AmazonS3Properties properties, AttachmentProducer attachmentProducer, RedisTemplate redisTemplate) {
        this.properties = properties;
        this.attachmentProducer = attachmentProducer;
        this.redisTemplate = redisTemplate;
        FileSource fileSource = new FileSource();
        fileSource.setHost(properties.getHost());
        fileSource.setPort(properties.getPort());
        fileSource.setConnectionTimeout(60000);
        try{
            fileSource.startup();
        }catch (Exception e){
            logger.error("创建SeaweedFS连接失败",e);
        }

        this.fileTemplate = new FileTemplate(fileSource.getConnection());

        logger.info("初始化Seaweed客户端： host={}, port={},bucket:{},useMQ={}",
                properties.getHost(), properties.getPort(), properties.getBucket(), properties.isUseMsgQueue());
        logger.info("注册公共组件提示：统一配置 SimpleStorageService 使用 S3云存储服务,文件服务模式【seaweed】。");
        //checkBucket(properties.getBucket());
        if (StringUtils.hasText(properties.getBucket())){
            fileTemplate.setCollection(properties.getBucket());
        }
    }

    public NKFile upload(String tenant, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode) throws FileUploadException, IOException
    {
        logger.debug("配置动态刷新：AmazonS3Properties:{}", JSON.toJSONString(properties));
        S3File s3File = new S3File(properties.getBucket(), tenant, multipartFile, objId, businessCode);
        putObject(s3File, multipartFile.getInputStream());

        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.process(s3File);
        }
        return s3File;
    }

    public NKFile uploadChunk(String tenantId, MultipartFile multipartFile, String uploadId, String chunkIndex) throws FileUploadException, IOException
    {
        logger.debug("tenantId:{},uploadId:{},chunkIndex:{},fileName:{},size:{}",tenantId, uploadId, chunkIndex,  multipartFile.getOriginalFilename(), multipartFile.getSize());
        S3File s3File = new S3File(properties.getBucket(), tenantId, multipartFile, uploadId, chunkIndex);
        try {
            putObject(s3File, multipartFile.getInputStream());
            logger.debug("fileId:{}", s3File.getId());
            redisTemplate.opsForHash().put(uploadId, chunkIndex, s3File.getId());
            if (properties.isUseMsgQueue() && attachmentProducer != null)
            {
                attachmentProducer.process(s3File);
            }

        } catch (Exception e) {
            logger.error("上传分片文件失败！", e);
        }
        return s3File;
    }

    public NKFile mergeChunkFiles(String tenantId, String uploadId, String suffixName) throws FileUploadException {
        S3File s3File = new S3File(properties.getBucket(), tenantId, suffixName);
        mergeChunkFiles(s3File, uploadId);

        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.process(s3File);
        }
        return s3File;
    }

    public NKFile upload(String tenant, File file, String objId, FileBusinessTypeEnum businessCode) throws FileUploadException
    {
        S3File s3File = new S3File(properties.getBucket(), tenant, file, objId, businessCode);
        uploadObject(s3File, file);

        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.process(s3File);
        }
        return s3File;
    }

    /**
     * 上传文件
     *
     * @param tenant
     * @param file
     * @param idDashFlag
     * @return
     * @throws FileUploadException
     */
    @Override
    public NKFile upload(String tenant, File file, boolean idDashFlag, String obj, FileBusinessTypeEnum businessCode) throws FileUploadException {
        NKFile uploadFile = upload(tenant, file, obj, businessCode);
        if (idDashFlag) {
            String id = uploadFile.getId();
            id = tenant + id.replace("/", "_");
            uploadFile.setId(id);
        }
        return uploadFile;
    }

    /**
     * 更新文件
     *
     * @param resourceUrl 文件id（tenantId_yyyy_mm_dd_id.xxx）
     * @param file   文件
     * @return
     */
    @Override
    public NKFile update(String resourceUrl, File file) throws FileUploadException {
        S3File s3File = new S3File();
        if (null != resourceUrl && file != null && resourceUrl.contains("_")) {
            // 判断是否同类型文件，不同类型不能覆盖

            String contentType = ContentType.findCode(null);  // 默认 Application_Octet_Stream
//            // 对后缀进行判断
            String tenantId = resourceUrl.substring(0, resourceUrl.indexOf("_"));
            String temp = resourceUrl.replace("_", "/");
            String fileId = getRealPathDn(temp.substring(temp.indexOf("/")+1));
            s3File.setBucket(properties.getBucket());
            s3File.setId(fileId);
            s3File.setTenantId(tenantId);
            s3File.setContentType(contentType);
            try {
                fileTemplate.setCollection(s3File.getBucket());
                FileHandleStatus fileStatus = fileTemplate.getFileStatus(fileId);
                logger.debug("fileStatus => {}", JSONObject.toJSON(fileStatus));
                fileTemplate.updateFileByStream(fileId,s3File.getFileName(),new FileInputStream(file), org.apache.http.entity.ContentType.DEFAULT_BINARY);
            }catch(IllegalArgumentException e1){
                String msg = "上传文件未发生改变，更新失败";
                return null;
            }catch (Exception e) {
                String msg = "上传文件:" + file.getAbsolutePath() + "，发生异常." + s3File.toString();
                throw new FileUploadException(msg, e);
            }
        }
        return s3File;
    }



    @Override
    public InputStream downloadAsStream(String tenant, String fileId) throws FileDownloadException
    {
        String objectId = tenant + fileId;
        fileId=getRealPathDn(fileId.substring(1));
        try
        {
            StreamResponse streamResponse = fileTemplate.getFileStream(fileId);
            return streamResponse.getInputStream();
        } catch (Exception e)
        {
            throw new FileDownloadException("下载文件流发生异常.文件ID=" + fileId, e);
        }
    }

    @Override
    public File downloadAsFile(String tenant, String fileId) throws FileDownloadException
    {
        File file = new File(FileUtils.getTempDirectory(), String.format("%s-%s.tmp", tenant, System.currentTimeMillis()));
        String objectId = tenant + fileId;
        fileId=getRealPathDn(fileId.substring(1));
        try
        {
            StreamResponse streamResponse =  fileTemplate.getFileStream(fileId);
            if (streamResponse!=null && streamResponse.getInputStream()!=null){
                FileCopyUtils.copy(streamResponse.getInputStream(),new FileOutputStream(file));
            }

            logger.debug("下载文件到本地：{} , 文件ID={}", file.getAbsolutePath(), fileId);
            return file;
        } catch (Exception e)
        {
            throw new FileDownloadException("下载文件发生异常. 文件ID=" + fileId, e);
        }
    }

    @Override
    public void getFileAndDirectResponseToHttp(ServletOutputStream out, String fileId) throws FileDownloadException {
        fileId=getRealPathDn(fileId);
        try {
            fileTemplate.getFileAndDirectResponseToHttp(out, fileId);
        } catch (Exception e) {
            {
                throw new FileDownloadException("下载文件流发生异常. 文件ID=" + fileId, e);
            }
        }
    }

    /*  private File createTempFileName(String tenant, String fileId)
    {
        int idx = fileId.lastIndexOf(".");
        String postfix = "";
        if (idx > 0)
        {
            postfix = fileId.substring(idx + 1);
        }
        SimpleDateFormat format = new SimpleDateFormat("/yyyy/MM/dd/");
        String datePath = format.format(new Date());
        String filePath = properties.getPath() + properties.getBucket() + "/" + tenant + datePath;
        File path = new File(filePath);
        path.mkdirs();
        String fileFullPath = filePath + UUIDUtil.create32UUID() + "." + postfix;
        return new File(fileFullPath);
    }*/

    private void uploadObject(S3File s3File, File file) throws FileUploadException
    {
        String objectId = s3File.getTenantId() + s3File.getId();
        try
        {
            fileTemplate.setCollection(s3File.getBucket());
            FileHandleStatus fileHandleStatus = fileTemplate.saveFileByStream(objectId,new FileInputStream(file));
            s3File.setId(getRealPathUp(fileHandleStatus.getFileId(),s3File));
            logger.debug("上传文件到云存储成功，fileId:{}" + s3File.getId());
        } catch (Exception e)
        {
            String msg = "上传文件:" + file.getAbsolutePath() + ",发生异常." + s3File.toString();
            throw new FileUploadException(msg, e);
        }
    }

    private void putObject(S3File s3File, InputStream inputStream) throws FileUploadException
    {
        String objectId = s3File.getTenantId() + s3File.getId();
        try (
                InputStream stream = inputStream
        )
        {
            fileTemplate.setCollection(s3File.getBucket());
            FileHandleStatus fileHandleStatus = fileTemplate.saveFileByStream(objectId,stream);
            s3File.setId(getRealPathUp(fileHandleStatus.getFileId(),s3File));
            logger.debug("上传文件到云存储成功，fileId:{}" + s3File.getId());

        } catch (Exception e)
        {
            throw new FileUploadException("上传文件到云存储发生异常。" + s3File.toString(), e);
        }
    }


    private void mergeChunkFiles(S3File s3File, String uploadId) throws FileUploadException
    {
        logger.debug("ChunkFiles-UploadId:{},fileName:{},fileInfo：{}", uploadId, s3File.getFileName(), s3File);
        try {
            String objectId = s3File.getTenantId() + s3File.getId();
            Map<String, Object> chunkInfos = redisTemplate.opsForHash().entries(uploadId);
            logger.debug("{}", chunkInfos);
            File file = new File(FileUtils.getTempDirectory(), String.format("%s-%s", uploadId, System.currentTimeMillis()));


            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");

            if (chunkInfos != null && chunkInfos.size() > 0) {
                logger.debug("tmp file:{}", file.getAbsolutePath());
                for (int i = 0; i < chunkInfos.size(); i++) {//保证按照分片顺序处理
                    String fileId = (String) chunkInfos.get(String.valueOf(i));
                    logger.debug("chunk fileId:{}", fileId);
                    StreamResponse streamResponse = fileTemplate.getFileStream(fileId);
                    if (streamResponse != null) {
                        InputStream is = streamResponse.getInputStream();
                        if (is != null) {
                            byte[] data = FileCopyUtils.copyToByteArray(is);
                            randomAccessFile.write(data);
                        }
                    }
                    delete(s3File.getTenantId(), fileId);//清理文件服务器上的分片文件
                }
                redisTemplate.delete(uploadId);//清理redis上的临时分片信息
                IOUtils.closeQuietly(randomAccessFile);
                uploadObject(s3File,file);
            }

        } catch (Exception e) {
            logger.error("合并文件出错", e);
            throw new FileUploadException("上传文件到云存储发生异常" + s3File.toString(), e);
        }

    }



    @Override
    public void delete(String tenant, String fileId) throws Exception
    {
        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.delete(tenant, fileId);
        } else
        {
            try
            {
                if (fileId.startsWith("/")) {
                    fileId = fileId.substring(fileId.indexOf("/") + 1);
                }
                fileTemplate.deleteFile(fileId);
            } catch (Exception e)
            {
                logger.error("删除云存储里的文件发生异常。ObjectId=" + fileId, e);
                throw new Exception("删除云存储里的文件发生异常。ObjectId=" + fileId, e);
            }
        }
    }

    private String getRealPathDn(String substring) {
        if(substring.contains(".")){
            int index = substring.lastIndexOf(".");
            substring = substring.substring(0,index);
        }
        return substring;
    }
    private String getRealPathUp(String fileId,S3File s3File) {
        String result = "/"+fileId;
        String fileName = s3File.getFileName();
        if(fileName.contains(".")){
            int index = fileName.lastIndexOf(".");
            String postfix = fileName.substring(index);
            result = result + postfix;
        }
        return result;
    }

    public void deleteFile(String fileId) throws Exception{
        try
        {
            fileTemplate.deleteFile(fileId);
        } catch (Exception e)
        {
            throw new Exception("删除云存储里的文件发生异常。ObjectId=" + fileId, e);
        }
    }
}
