package com.chushang.oss.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chushang.common.core.exception.ResultException;
import com.chushang.common.core.util.DateUtils;
import com.chushang.common.core.util.FileUtils;
import com.chushang.common.core.util.IPUtils;
import com.chushang.common.core.util.ServletUtils;
import com.chushang.common.mybatis.enums.Operator;
import com.chushang.oss.config.UploadConfig;
import com.chushang.oss.entity.FileSourceInfo;
import com.chushang.oss.entity.dto.OcrDTO;
import com.chushang.oss.entity.vo.FileSourceVo;
import com.chushang.oss.entity.vo.FileVo;
import com.chushang.oss.enums.OcrTypeEnum;
import com.chushang.oss.mapper.FileSourceMapper;
import com.chushang.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.tika.Tika;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @auther: zhao
 * @date: 2024/4/29 10:25
 */
@Slf4j
@Service
public class FileSourceService
        extends ServiceImpl<FileSourceMapper, FileSourceInfo>
        implements IService<FileSourceInfo> {

    @Resource
    OssService ossService;
    @Resource
    RedissonClient redissonClient;
    @Resource
    OcrService ocrService;
    @Resource
    UploadConfig uploadConfig;

    private String generateFid() {
        String uuid = IdUtil.simpleUUID();
        uuid = Integer.toHexString(Integer.parseInt(DateUtils.format(ZonedDateTime.now(), DatePattern.PURE_DATE_PATTERN))) + uuid;
        return uuid;
    }

    /**
     * 上传 到minio 或者 oss 服务中
     *
     * @param file     文件
     * @param sealFlag
     * @param formats
     * @param fileType
     */
    public FileSourceVo addFile(MultipartFile file, Boolean sealFlag, String formats, String fileType)
    {
        String ip = IPUtils.clientIp(ServletUtils.getRequest());
        String fid = generateFid();
        String fName = file.getOriginalFilename();
        Tika t = new Tika();
        String mimetype = null;
        try {
            mimetype = t.detect(file.getBytes());
            if (fName.indexOf(".") < 0) {//如果文件名字没有后缀
                String ext = MimeTypes.getDefaultMimeTypes().forName(mimetype).getExtension();
                fName += ext;
            }
        } catch (IOException ignore) {
        } catch (MimeTypeException e) {
            log.error("文件后缀识别失败");
        }
        FileSourceInfo info = new FileSourceInfo();
        try {
            info.setFid(fid);
            info.setName(fName);
            // mimeType 还不是 后缀 类型
            info.setMimeType(mimetype);
            info.setSize((long) file.getBytes().length);
            String md5 = FileUtils.getMd5(file.getInputStream());
            info.setMd5(md5);
            String path = ossService.getPrefixPath(md5,fName);
            // 不带https 的路径
            info.setPath(path);
            info.setStorage(uploadConfig.getStorage());
            info.setUploadIp(ip);
            // 上传到 oss 或者 minio
            String realPath = ossService.upload(file.getBytes(), info, sealFlag, formats);
            // 带https 的路径
            info.setRealPath(realPath);
            info.setFileType(fileType);
            info.setBucketName(uploadConfig.getBucketName());
            RMap<String, FileSourceInfo> map = redissonClient.getMap("OSS-Cache");
            map.put(fid, info);
            // 入库
            save(info);
            map.remove(info.getFid());
        } catch (Exception e) {
            log.error("上传文件失败", e);
        }
        FileSourceVo vo = new FileSourceVo();
        vo.setFid(info.getFid());
        vo.setFilePath(info.getRealPath());
        vo.setName(fName);
        vo.setSize(info.getSize());
        return vo;
    }

    public List<FileSourceVo> addFile(MultipartFile[] files, String ocrType, Boolean sealFlag, String formats, String fileType)
    {
        List<FileSourceVo> result = new ArrayList<>();
        for (MultipartFile file : files) {
            FileSourceVo info = addFile(file, sealFlag, formats, fileType);
            if (StrUtil.isNotEmpty(ocrType)) {
                OcrDTO ocr = new OcrDTO();
                OcrTypeEnum ocrTypeEnum = OcrTypeEnum.findByCode(ocrType);
                if (ocrTypeEnum != OcrTypeEnum.NONE) {
                    ocr.setOcrType(ocrTypeEnum);
                    ocr.setImgPath(info.getFilePath());
                    JSONObject ocrInfo = ocrService.ocr(ocr);
                    info.setOcr(ocrInfo);
                }
            }
            result.add(info);
        }
        return result;
    }


    public void getFileStream(String fid, HttpServletResponse response, boolean convertText)
    {
        // 文件信息
        FileSourceInfo info = getOne(new LambdaQueryWrapper<FileSourceInfo>()
                .eq(FileSourceInfo::getFid, fid)
                .last(Operator.LIMIT_ONE.getCharacter()));
        if (ObjUtil.isEmpty(info)) return;
//        InputStream fileStream = ossService.getFileStream(info);
        byte[] fileBytes = ossService.getFileBytes(info);
        response.setContentType(info.getMimeType());
        try (OutputStream os = response.getOutputStream()){
            response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(info.getName(), StandardCharsets.UTF_8));
            response.addHeader("Content-Length", "" +  fileBytes.length);
            response.setContentType("application/octet-stream");
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(fileBytes);
            if (FileUtils.isText(byteArrayInputStream) && convertText){
                //是文本则读取内容输出
                OutputStreamWriter osw = new OutputStreamWriter(os);
                osw.write(IOUtils.toString(byteArrayInputStream, FileUtils.getStreamCharset(byteArrayInputStream)));
                osw.flush();
            }else {
//                byte[] readByte = new byte[10240];
//                int len = 0;
//                while((len = fileStream.read(readByte, 0, readByte.length)) != -1) {
//                    os.write(readByte,0,len);
//                }
                os.write(fileBytes);
                os.flush();
            }
        }catch (Exception e){
            log.error("文件流获取失败");
        }
    }

    @Transactional
    public void delFile(String fid)
    {
        // 文件信息
        FileSourceInfo info = getOne(new LambdaQueryWrapper<FileSourceInfo>()
                .eq(FileSourceInfo::getFid, fid)
                .last(Operator.LIMIT_ONE.getCharacter()));
        if (ObjUtil.isEmpty(info)) return;
        ossService.delFile(info.getPath());
        removeById(fid);
    }

    @Transactional
    public String delFileBatch(Set<String> fids)
    {
        // 文件信息
        List<FileSourceInfo> list = list(new LambdaQueryWrapper<FileSourceInfo>()
                .in(FileSourceInfo::getFid, fids));
        if (CollectionUtil.isEmpty(list)) return "success";
        List<String> strings
                = ossService.delFileBatch(list.stream().map(FileSourceInfo::getPath).toList());
        Set<String> removeFids = fids.stream().filter(f -> !strings.contains(f)).collect(Collectors.toSet());
        removeBatchByIds(removeFids);
        if (CollectionUtil.isNotEmpty(strings)){
            String key = String.join(",", strings);
            return "以下文件未删除: [" + key + "].";
        }
        return "success";
    }

    public String uploadQcCode() throws Exception
    {


        String url = StrUtil.format("{}?userId={}&channelId={}&channelName={}&" +
                        "authorName={}&authorPhone={}",
                "https://mini.program.chually.com/uniapp/index.html", SecurityUtils.getUserId(), SecurityUtils.getDeptId(),
                "内蒙古",
                SecurityUtils.getLoginUser().getSysUser().getNickName(), SecurityUtils.getLoginUser().getSysUser().getPhone());

        File file = QrCodeUtil.generate(url, new QrConfig(600, 600),
                FileUtil.createTempFile(ImgUtil.IMAGE_TYPE_PNG, Boolean.TRUE));

        FileInputStream fileInputStream = new FileInputStream(file);
        String ip = IPUtils.clientIp(ServletUtils.getRequest());
        String fid = generateFid();
        String fName = file.getName();
        String md5 = FileUtils.getMd5(fileInputStream);
        // image/jpeg mimeType
        FileSourceInfo fileSourceInfo = new FileSourceInfo();
        fileSourceInfo.setUploadIp(ip);
        fileSourceInfo.setFid(fid);
        fileSourceInfo.setName(fName);
        fileSourceInfo.setSize((long) fileInputStream.available());
        fileSourceInfo.setPath("qcCode/"+fName);
        fileSourceInfo.setMd5(md5);
        fileSourceInfo.setRealPath(url);
        fileSourceInfo.setMimeType("image/jpeg");
        fileSourceInfo.setStorage(uploadConfig.getStorage());
        String upload = ossService.upload(new FileInputStream(file), fileSourceInfo);
        fileSourceInfo.setRealPath(upload);
        save(fileSourceInfo);
        FileUtil.del(file);
        return upload;
    }

    public FileVo uploadContract(MultipartFile file) throws Exception
    {
        String ip = IPUtils.clientIp(ServletUtils.getRequest());
        String fid = generateFid();
        String fName = file.getOriginalFilename();
        String md5 = FileUtils.getMd5(file.getInputStream());
        // image/jpeg mimeType
        FileSourceInfo fileSourceInfo = new FileSourceInfo();
        fileSourceInfo.setUploadIp(ip);
        fileSourceInfo.setFid(fid);
        fileSourceInfo.setMd5(md5);
        Tika t = new Tika();
        String mimetype = null;
        try {
            mimetype = t.detect(file.getBytes());
            if (fName.indexOf(".") < 0) {//如果文件名字没有后缀
                String ext = MimeTypes.getDefaultMimeTypes().forName(mimetype).getExtension();
                fName += ext;
            }
        } catch (IOException ignore) {
        } catch (MimeTypeException e) {
            log.error("文件后缀识别失败");
        }
        fileSourceInfo.setName(fName);
        fileSourceInfo.setSize((long) file.getBytes().length);
        fileSourceInfo.setPath("contract/"+fName);
        fileSourceInfo.setStorage(uploadConfig.getStorage());
        fileSourceInfo.setMimeType(mimetype);
        String upload = ossService.upload(file.getInputStream(), fileSourceInfo);
        fileSourceInfo.setRealPath(upload);
        save(fileSourceInfo);
        FileVo fileVo = new FileVo();
        fileVo.setRealPath(upload);
        fileVo.setFid(fid);
        fileVo.setMimeType(mimetype);
        fileVo.setFileName(fName);
        return fileVo;
    }

    public FileSourceVo addFile(byte[] bytes, String fileName, Boolean sealFlag, String formats, String fileType)
    {
        String ip = IPUtils.clientIp(ServletUtils.getRequest());
        String fid = generateFid();
        String fName = fileName;
        Tika t = new Tika();
        String mimetype = null;
        try {
            mimetype = t.detect(bytes);
            if (fName.indexOf(".") < 0) {//如果文件名字没有后缀
                String ext = MimeTypes.getDefaultMimeTypes().forName(mimetype).getExtension();
                fName += ext;
            }
        } catch (MimeTypeException e) {
            log.error("文件后缀识别失败");
        }
        FileSourceInfo info = new FileSourceInfo();
        try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)){
            info.setFid(fid);
            info.setName(fName);
            // mimeType 还不是 后缀 类型
            info.setMimeType(mimetype);
            // size 如何获取?
            info.setSize((long) bytes.length);
            String md5 = FileUtils.getMd5(in);
            info.setMd5(md5);
            String path = ossService.getPrefixPath(md5,fName);
            // 不带https 的路径
            info.setPath(path);
            info.setStorage(uploadConfig.getStorage());
            info.setUploadIp(ip);
            // 上传到 oss 或者 minio
            String realPath = ossService.upload(bytes, info, sealFlag, formats);
            // 带https 的路径
            info.setRealPath(realPath);
            info.setFileType(fileType);
            info.setBucketName(uploadConfig.getBucketName());
            RMap<String, FileSourceInfo> map = redissonClient.getMap("OSS-Cache");
            map.put(fid, info);
            // 入库
            save(info);
            map.remove(info.getFid());
        } catch (Exception e) {
            log.error("上传文件失败", e);
        }
        FileSourceVo vo = new FileSourceVo();
        vo.setFid(info.getFid());
        vo.setFilePath(info.getRealPath());
        vo.setName(fName);
        vo.setSize(info.getSize());
        return vo;
    }

    public List<FileSourceVo> uploadFile(byte[] bytes, String fileName, Boolean sealFlag, String formats, String fileType)
    {
        return List.of(addFile(bytes, fileName, sealFlag, formats, fileType));
    }

    public FileSourceVo addFile(InputStream is, String fileName, Boolean sealFlag, String formats, String fileType)
    {
        String ip = IPUtils.clientIp(ServletUtils.getRequest());
        String fid = generateFid();
        String fName = fileName;
        Tika t = new Tika();
        String mimetype = null;
        try {
            mimetype = t.detect(is.readAllBytes());
            if (fName.indexOf(".") < 0) {//如果文件名字没有后缀
                String ext = MimeTypes.getDefaultMimeTypes().forName(mimetype).getExtension();
                fName += ext;
            }
        } catch (MimeTypeException e) {
            log.error("文件后缀识别失败");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        FileSourceInfo info = new FileSourceInfo();
            info.setFid(fid);
            info.setName(fName);
            // mimeType 还不是 后缀 类型
            info.setMimeType(mimetype);
            // size 如何获取?
        try {
            info.setSize((long) is.available());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String md5 = FileUtils.getMd5(is);
            info.setMd5(md5);
            String path = ossService.getPrefixPath(md5,fName);
            // 不带https 的路径
            info.setPath(path);
            info.setStorage(uploadConfig.getStorage());
            info.setUploadIp(ip);
            // 上传到 oss 或者 minio
            String realPath = ossService.upload(is, info, sealFlag, formats);
            // 带https 的路径
            info.setRealPath(realPath);
            info.setFileType(fileType);
            info.setBucketName(uploadConfig.getBucketName());
            RMap<String, FileSourceInfo> map = redissonClient.getMap("OSS-Cache");
            map.put(fid, info);
            // 入库
            save(info);
            map.remove(info.getFid());

        FileSourceVo vo = new FileSourceVo();
        vo.setFid(info.getFid());
        vo.setFilePath(info.getRealPath());
        vo.setName(fName);
        vo.setSize(info.getSize());
        return vo;
    }

    public List<FileSourceVo> uploadFile(InputStream is, String fileName, Boolean sealFlag, String formats, String fileType)
    {
        return List.of(addFile(is, fileName, sealFlag, formats, fileType));
    }

    /**
     * 根据文件id获取二进制文件
     * @param fid 文件id
     */
    public byte[] getFile(String fid) {
        FileSourceInfo info = getOne(new LambdaQueryWrapper<FileSourceInfo>()
                .eq(FileSourceInfo::getFid, fid)
                .last(Operator.LIMIT_ONE.getCharacter()));
        if (ObjUtil.isEmpty(info)) return null;
        return ossService.getFileBytes(info);
    }
}
