package com.boyue.file.controller;

import java.io.OutputStream;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.boyue.common.annotation.Anonymous;
import com.boyue.common.core.domain.AjaxResult;
import com.boyue.common.exception.ServiceException;
import com.boyue.common.utils.SecurityUtils;
import com.boyue.common.utils.StringUtils;
import com.boyue.common.utils.file.FileUtils;
import com.boyue.common.utils.sign.Md5Utils;
import com.boyue.file.domain.SysFileInfo;
import com.boyue.file.domain.SysFilePartETag;
import com.boyue.file.service.ISysFileInfoService;
import com.boyue.file.storage.StorageBucket;
import com.boyue.file.storage.StorageEntity;
import com.boyue.file.storage.StorageManagement;
import com.boyue.file.storage.StorageManagements;
import com.boyue.file.utils.FileOperateUtils;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Tag(name = "默认文件存储")
@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    private ISysFileInfoService sysFileInfoService;

    @Autowired
    StorageManagements storageManagement;

    /**
     * 获取所有可用存储渠道及其client列表
     */
    @GetMapping("/client-list")
    public AjaxResult getClientList() {
        Map<String, List<String>> result = new HashMap<>();
        Map<String, StorageManagement> configs = storageManagement.getStorageTypes();
        for (String storageType : configs.keySet()) {
            StorageManagement config = configs.get(storageType);
            result.put(storageType, new ArrayList<>(config.getClient().keySet()));
        }
        return AjaxResult.success(result);
    }

    /**
     * 统一上传接口：/file/{storageType}/{clientName}/upload
     */
    @PostMapping("/{storageType}/{clientName}/upload")
    public AjaxResult uploadUnified(
            @PathVariable("storageType") String storageType,
            @PathVariable("clientName") String clientName,
            @RequestParam("file") MultipartFile file) {
        try {
            String md5 = Md5Utils.getMd5(file);
            String fileType = null;
            if (file.getOriginalFilename() != null && file.getOriginalFilename().contains(".")) {
                fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.') + 1);
            }
            String filePath = "upload/" + System.currentTimeMillis() + "_" + file.getOriginalFilename();
            StorageBucket storageBucket = storageManagement.getStorageBucket(storageType, clientName);
            storageBucket.put(filePath, file);
            String url = storageBucket.getUrl(filePath).toString();
            SysFileInfo info = new SysFileInfo();
            info.setFileName(file.getOriginalFilename());
            info.setFilePath(filePath);
            info.setStorageType(storageType);
            info.setFileType(fileType);
            info.setFileSize(file.getSize());
            info.setMd5(md5);
            sysFileInfoService.insertSysFileInfo(info);
            AjaxResult ajax = AjaxResult.success();
            ajax.put("url", url);
            ajax.put("info", info);
            return ajax;
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }

    /**
     * 统一下载接口：/file/{storageType}/{clientName}/download?filePath=xxx
     */
    @GetMapping("/{storageType}/{clientName}/download")
    public void downloadUnified(
            @PathVariable("storageType") String storageType,
            @PathVariable("clientName") String clientName,
            @RequestParam("filePath") String filePath,
            HttpServletResponse response) throws Exception {
        try {
            StorageBucket storageBucket = storageManagement.getStorageBucket(storageType, clientName);
            StorageEntity fileEntity = storageBucket.get(filePath);
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition",
                    "attachment; filename=" + URLEncoder.encode(filePath, "UTF-8"));
            IOUtils.copy(fileEntity.getInputStream(), response.getOutputStream());
        } catch (Exception e) {
            response.setContentType("text/plain;charset=UTF-8");
            response.getWriter().write("下载失败: " + e.getMessage());
        }
    }

    /**
     * 统一预览接口：/file/{storageType}/{clientName}/preview?filePath=xxx
     */
    @Anonymous
    @GetMapping("/{storageType}/{clientName}/preview")
    public void preview(
            @PathVariable("storageType") String storageType,
            @PathVariable("clientName") String clientName,
            @RequestParam("filePath") String filePath,
            HttpServletResponse response) throws Exception {
        try {
            filePath = URLDecoder.decode(filePath, "UTF-8");
            StorageBucket storageBucket = storageManagement.getStorageBucket(storageType, clientName);
            StorageEntity fileEntity = storageBucket.get(filePath);
            String contentType = URLConnection.guessContentTypeFromName(FileUtils.getName(filePath));
            if (contentType == null) {
                contentType = "application/octet-stream";
            }
            response.setContentType(contentType);
            IOUtils.copy(fileEntity.getInputStream(), response.getOutputStream());
            response.flushBuffer();
        } catch (Exception e) {
            response.setContentType("text/plain;charset=UTF-8");
            response.getWriter().write("预览失败: " + e.getMessage());
        }
    }

    /**
     * 本地资源通用下载
     */
    @Operation(summary = "本地资源通用下载")
    @GetMapping("/resource")
    @Anonymous
    public void resourceDownload(@RequestParam String filePath,
                                 HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        OutputStream outputStream = response.getOutputStream();
        try {
            if (!FileUtils.checkAllowDownload(filePath)) {
                throw new Exception(StringUtils.format("资源文件({})非法，不允许下载。 ", filePath));
            }
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            FileUtils.setAttachmentResponseHeader(response, filePath);
            FileOperateUtils.downLoad(filePath, outputStream);
        } catch (Exception e) {
            response.reset();
            response.setContentType(MediaType.TEXT_HTML_VALUE);
            response.setCharacterEncoding("UTF-8");
            String errorMessage = "下载文件失败: " + e.getMessage();
            outputStream.write(errorMessage.getBytes("UTF-8"));
            outputStream.flush();
        } finally {
            outputStream.close();
        }
    }

    private static final long MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB
    // 存储上传会话ID与文件名的映射，用于通过uploadId查找原始文件名。
    private final ConcurrentHashMap<String, String> uploadIdToFileName = new ConcurrentHashMap<>();
    // 存储上传会话ID与文件大小（字节）的映射，记录分片上传任务对应的文件总大小。
    private final ConcurrentHashMap<String, Long> uploadIdToFileSize = new ConcurrentHashMap<>();
    // 存储上传会话ID与文件路径的映射，标识该上传任务在存储系统中的具体位置。
    private final ConcurrentHashMap<String, String> uploadIdToFilePath = new ConcurrentHashMap<>();
    // 存储上传会话ID与存储桶名称的映射，确定文件最终上传到哪个存储桶中。
    private Map<String, String> uploadIdToBucketName = new ConcurrentHashMap<>();

    /**
     * 初始化分片上传
     */
    @PostMapping("/initUpload")
    public AjaxResult initMultipartUpload(@RequestParam("fileName") String fileName,
                                          @RequestParam("fileSize") Long fileSize,
                                          @RequestParam(value = "fileType", required = false) String fileType,
                                          @RequestParam("bucketName") String bucketName) {
        try {
            if (fileName == null || fileName.isEmpty() || fileSize == null || fileSize <= 0) {
                throw new ServiceException("文件名或文件大小不能为空");
            }
            if (fileSize > MAX_FILE_SIZE)
                throw new ServiceException("文件不能超过500MB");
            String currentDate = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
            String timestamp = String.valueOf(System.currentTimeMillis());
            String objectName = String.format("%s/%s/%s_%s", "/upload", currentDate, timestamp, fileName);
            String uploadId = FileOperateUtils.initMultipartUpload(objectName);
            // 存储上传会话信息
            uploadIdToFileName.put(uploadId, fileName);
            uploadIdToFileSize.put(uploadId, fileSize);
            uploadIdToFilePath.put(uploadId, objectName);
            uploadIdToBucketName.put(uploadId, bucketName);
            return AjaxResult.success(Map.of("uploadId", uploadId, "filePath", objectName,
                    "fileName", fileName, "bucketName", bucketName));
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }

    /**
     * 上传文件分片
     */
    @PostMapping("/uploadChunk")
    public AjaxResult uploadFileChunk(@RequestParam("uploadId") String uploadId,
                                      @RequestParam("filePath") String filePath, @RequestParam("chunkIndex") int chunkIndex,
                                      @RequestParam("chunk") MultipartFile chunk) {
        try {
            if (chunk == null || chunk.isEmpty())
                throw new ServiceException("分片数据不能为空");
            // 检查上传会话是否存在
            String fileName = uploadIdToFileName.get(uploadId);
            Long fileSize = uploadIdToFileSize.get(uploadId);
            if (fileName == null || fileSize == null)
                throw new ServiceException("文件上传会话不存在或已过期");
            // 上传分片
            String etag = FileOperateUtils.uploadPart(filePath, uploadId, chunkIndex + 1, chunk.getSize(),
                    chunk.getInputStream());
            if (etag == null || etag.isEmpty())
                throw new ServiceException("上传分片失败：未获取到ETag");
            return AjaxResult.success(Map.of("etag", etag, "chunkIndex",
                    chunkIndex, "partNumber", chunkIndex + 1));
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }

    /**
     * 完成分片上传并合并文件
     */
    @PostMapping("/completeUpload")
    public AjaxResult completeMultipartUpload(@RequestParam("uploadId") String uploadId,
                                              @RequestParam("filePath") String filePath, @RequestBody List<SysFilePartETag> partETags) {
        try {
            String bucketName = uploadIdToBucketName.get(uploadId);
            if (bucketName == null || bucketName.trim().isEmpty())
                throw new ServiceException("无法获取存储桶");
            if (partETags == null || partETags.isEmpty())
                throw new ServiceException("分片信息不能为空");
            String fileName = uploadIdToFileName.get(uploadId);
            Long fileSize = uploadIdToFileSize.get(uploadId);
            if (fileName == null || fileSize == null)
                throw new ServiceException("文件上传会话不存在或已过期");
            // 验证并排序分片信息
            List<SysFilePartETag> validParts = partETags.stream()
                    .filter(part -> part != null && part.getPartNumber() != null && part.getETag() != null)
                    .peek(part -> {
                        if (part.getPartNumber() <= 0 || StringUtils.isEmpty(part.getETag())) {
                            throw new ServiceException("分片序号或ETag无效");
                        }
                    })
                    .collect(Collectors.toList());
            if (validParts.size() != partETags.size()) {
                throw new ServiceException("分片信息格式不正确");
            }
            validParts.sort(Comparator.comparingInt(p -> p.getPartNumber()));
            // 完成分片上传并合并文件
            String finalPath = FileOperateUtils.completeMultipartUpload(filePath, uploadId, validParts);
            if (finalPath == null || finalPath.isEmpty()) {
                throw new ServiceException("合并分片失败：未获取到最终文件路径");
            }
            // 创建文件信息记录
            SysFileInfo fileInfo = new SysFileInfo();
            fileInfo.setFileName(fileName);
            fileInfo.setFilePath(finalPath);
            fileInfo.setFileSize(fileSize);
            int dotIndex = fileName.lastIndexOf('.');
            fileInfo.setFileType(dotIndex >= 0 ? fileName.substring(dotIndex + 1) : "");
            fileInfo.setStorageType(bucketName);
            fileInfo.setCreateBy(SecurityUtils.getUsername());
            fileInfo.setCreateTime(new Date());
            fileInfo.setUpdateBy(SecurityUtils.getUsername());
            fileInfo.setUpdateTime(new Date());
            fileInfo.setDelFlag("0");
            sysFileInfoService.insertSysFileInfo(fileInfo);
            // 清理上传会话信息
            uploadIdToFileName.remove(uploadId);
            uploadIdToFileSize.remove(uploadId);
            uploadIdToFilePath.remove(uploadId);
            uploadIdToBucketName.remove(uploadId);
            return AjaxResult.success(Map.of("fileId", fileInfo.getFileId(), "fileName", fileName,
                    "filePath", finalPath, "fileSize", fileSize, "storageType", bucketName));
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }
}
