package com.bonus.file.service.impl;

import com.bonus.common.core.utils.file.MimeTypeUtils;
import com.bonus.file.entity.FileDetails;
import com.bonus.file.service.ISysFileService;
import com.bonus.file.utils.FileUploadUtils;
import com.bonus.system.api.domain.SysFile;
import com.mongodb.client.gridfs.model.GridFSFile;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.bson.Document;
import org.bson.types.Binary;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author wangvivi
 */
@Service
@ConditionalOnProperty(name = "storage.type", havingValue = "mongodb")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class MongodbServiceImpl implements ISysFileService {

    @Value("${spring.data.mongodb.gridfs-size-threshold}")
    private long gridFsSizeThreshold;

    final static String COLLECTION_NAME = "smallFiles";
    /**
     * GridFsTemplate 用于大文件存储
     */
    private final GridFsTemplate gridFsTemplate;
    /**
     * MongoTemplate 用于小文件存储
     */
    private final MongoTemplate mongoTemplate;

    @Override
    public SysFile uploadFile(MultipartFile file) throws Exception {
        //验证文件扩展名和大小
        FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        String fileName = Objects.requireNonNull(file.getOriginalFilename(), "文件名不能为空");
        fileName = fileName.replace(" ", "");
        long fileSize = file.getSize();
        // 判断是否使用 GridFS 存储
        if (fileSize >= gridFsSizeThreshold) {
            // 使用 GridFS 存储大文件
            ObjectId fileId = gridFsTemplate.store(file.getInputStream(), fileName, file.getContentType());
            return SysFile.builder().name(fileName).url(fileId.toHexString()).build();
        } else {
            // 小文件直接存储为二进制数据
            // 创建一个Map存储文件信息
            Map<String, Object> fileData = new HashMap<>();
            fileData.put("fileName", fileName);
            fileData.put("fileSize", file.getSize());
            fileData.put("contentType", file.getContentType());
            // 将文件内容以byte[]存储
            fileData.put("fileData", file.getBytes());

            // 插入文件信息到MongoDB的集合中（可以使用指定的集合名）
            Document insertedFile = mongoTemplate.insert(new Document(fileData), COLLECTION_NAME);
            // 返回文件的唯一标识符（MongoDB的ObjectId）
            return SysFile.builder().name(fileName).url(insertedFile.getObjectId("_id").toString()).build();
        }
    }

    @Override
    public List<SysFile> uploadFiles(MultipartFile[] files) throws Exception {
        List<SysFile> uploadedFiles = new ArrayList<>();
        for (MultipartFile file : files) {
            uploadedFiles.add(uploadFile(file));
        }
        return uploadedFiles;
    }

    @Override
    public void downloadFile(HttpServletResponse response, String fileId) throws Exception {
        // 先尝试从 GridFS 中读取
        GridFSFile gridFSFile = gridFsTemplate.findOne(new org.springframework.data.mongodb.core.query.Query()
                .addCriteria(org.springframework.data.mongodb.core.query.Criteria.where("_id").is(new ObjectId(fileId))));

        if (gridFSFile != null) {
            // 设置响应头，告知客户端文件下载信息
            String encodedFileName = URLEncoder.encode(gridFSFile.getFilename(), StandardCharsets.UTF_8.toString());
            response.setContentType(gridFSFile.getMetadata().getString("_contentType"));
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
            // GridFS 文件下载
            InputStream inputStream = gridFsTemplate.getResource(gridFSFile).getInputStream();
            IOUtils.copy(inputStream, response.getOutputStream());
            response.flushBuffer();
        }
        else {
            downloadFromCollection(response, fileId);
        }
    }

    @Override
    public String getFullFileUrl(String urlStr) throws Exception {
        return urlStr;
    }

    /**
     * 从给定的URL下载文件并将其保存到指定的目标位置。
     *
     * @param response 请求响应。
     * @param fileIds   要下载文件的URL地址。
     * @throws Exception 如果在下载过程中遇到任何异常，例如网络问题、文件写入问题等。
     */
    @Override
    public void downloadFilesAsZip(HttpServletResponse response, List<String> fileIds) throws Exception {
        String zipFileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".zip";
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment; filename=" + zipFileName);
        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            byte[] buffer = new byte[8192];

            for (String fileId : fileIds) {
                // 先尝试从 GridFS 中读取
                GridFSFile gridFSFile = gridFsTemplate.findOne(new org.springframework.data.mongodb.core.query.Query()
                        .addCriteria(org.springframework.data.mongodb.core.query.Criteria.where("_id").is(new ObjectId(fileId))));
                if (gridFSFile != null) {
                    try (InputStream inputStream = gridFsTemplate.getResource(gridFSFile).getInputStream()) {
                        // 创建一个新的 ZipEntry，并使用文件名称
                        zos.putNextEntry(new ZipEntry(gridFSFile.getFilename()));
                        int len;
                        while ((len = inputStream.read(buffer)) > 0) {
                            zos.write(buffer, 0, len);
                        }
                        zos.closeEntry();
                    } catch (Exception e) {
                        System.err.println("无法压缩文件: " + fileId + " 错误: " + e.getMessage());
                    }
                } else {
                    System.err.println("文件不存在，文件 ID: " + fileId);
                }
            }
            zos.finish();
        }
    }

    @Override
    public void deleteFile(String fileId) throws Exception {
        try {
            // 尝试从 GridFS 中删除
            gridFsTemplate.delete(new org.springframework.data.mongodb.core.query.Query()
                    .addCriteria(org.springframework.data.mongodb.core.query.Criteria.where("_id").is(new ObjectId(fileId))));

            // 尝试删除普通集合中的文件
            mongoTemplate.findAllAndRemove(new org.springframework.data.mongodb.core.query.Query().addCriteria(org.springframework.data.mongodb.core.query.Criteria.where("_id").is(fileId)), Document.class, COLLECTION_NAME);
        } catch (Exception e) {
            throw new Exception("删除文件失败" );
        }
    }

    /**
     * 删除指定URL所指向的文件。
     *
     * @param fileId 要删除文件的URL地址。
     * @return 布尔值，指示文件删除是否成功。如果文件被成功删除，则返回true；否则返回false。
     * @throws Exception 如果在删除文件的过程中遇到错误，例如网络问题、文件不存在或没有删除权限等。
     */
    @Override
    public void deleteFiles(List<String> fileId) throws Exception {
            for (String url : fileId) {

            }
    }

    /**
     * 获取指定文件夹下的所有文件夹和文件。
     *
     * @param folderPath 要获取文件夹和文件的父文件夹路径（例如 "folder1/"）
     * @return 返回一个包含所有文件夹和文件路径的集合
     */
    @Override
    public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
        return Collections.emptySet();
    }

    /**
     * 创建文件夹（通过上传带有路径的文件）
     *
     * @param folderPath 文件夹路径（例如 "folder1/subfolder/"）
     */
    @Override
    public void createFolder(String folderPath) {

    }

    /**
     * 删除指定路径的所有对象，从而删除“文件夹”。
     *
     * @param folderPath 文件夹路径（例如 "folder1/subfolder/"）
     */
    @Override
    public void deleteFolder(String folderPath) {

    }

    private void downloadFromCollection(HttpServletResponse response, String fileId) throws IOException {
        // 尝试从普通集合中获取二进制文件
        Document fileDocument = mongoTemplate.findById(fileId, Document.class, COLLECTION_NAME);
        if (fileDocument == null) {
            // 如果文件不存在，返回错误信息
            throw new IOException("文件不存在");
        }
        // 获取 Binary 对象并转换为 byte[]
        Binary binaryData = fileDocument.get("fileData", Binary.class);
        // 从 Binary 中提取 byte[]
        byte[] fileData = binaryData.getData();
        // 假设文件名保存在 fileName 字段
        String fileName = fileDocument.getString("fileName");
        // 假设文件类型保存在 contentType 字段
        String contentType = fileDocument.getString("contentType");

        // 如果 contentType 为空，默认设置为 application/octet-stream
        if (contentType == null || contentType.isEmpty()) {
            contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        }
        // 设置响应头，包含文件名和内容类型
        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodedFileName);
        response.setContentType(contentType);
        response.setContentLength(fileData.length);
        // 将文件的二进制数据写入到响应输出流中
        try (OutputStream os = response.getOutputStream()) {
            os.write(fileData);
            os.flush();
        } catch (IOException e) {
            // 如果写入过程出现异常，返回错误信息
            throw new IOException("Failed to download file");
        }
    }
}
