package com.zg.project.wisdom.controller;

import com.zg.framework.web.domain.AjaxResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.unit.DataSize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;

/**
 * 多媒体上传控制器（支持图片与视频并行落盘）
 */
@RestController
@RequestMapping("/system/media")
public class MediaUploadController {

    /** 根目录（如需配置化，可改为从 application.yml 读取） */
    private static final Path ROOT_DIR = Paths.get("D:/uploads");

    /** 本次上传总大小限制：100MB */
    private static final long MAX_TOTAL_BYTES = DataSize.ofMegabytes(100).toBytes();

    /** 图片/视频数量限制 */
    private static final int MAX_IMAGE_COUNT = 100;
    private static final int MAX_VIDEO_COUNT = 5;

    /** 单次请求内的并发上限（建议 2~4；默认 4） */
    private static final int PER_REQUEST_CONCURRENCY = 4;

    /** 注入线程池 Bean：threadPoolTaskExecutor */
    @Resource(name = "threadPoolTaskExecutor")
    private ThreadPoolTaskExecutor executor;

    /**
     * 上传接口
     */
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public AjaxResult upload(
            @RequestParam("username") String username,
            @RequestParam("nameBase") String nameBase,
            @RequestPart(value = "images", required = false) MultipartFile[] images,
            @RequestPart(value = "videos", required = false) MultipartFile[] videos
    ) {
        try {
            // 1. 基础校验
            if (StringUtils.isBlank(username)) {
                return AjaxResult.error("username 不能为空");
            }
            if (StringUtils.isBlank(nameBase)) {
                return AjaxResult.error("nameBase（命名信息）不能为空");
            }

            int imageCount = images == null ? 0 : images.length;
            int videoCount = videos == null ? 0 : videos.length;

            if (imageCount == 0 && videoCount == 0) {
                return AjaxResult.error("请至少上传一张图片或一段视频");
            }
            if (imageCount > MAX_IMAGE_COUNT) {
                return AjaxResult.error("图片最多支持上传 " + MAX_IMAGE_COUNT + " 张");
            }
            if (videoCount > MAX_VIDEO_COUNT) {
                return AjaxResult.error("视频最多支持上传 " + MAX_VIDEO_COUNT + " 段");
            }

            // 2. 总体积校验
            long totalBytes = 0L;
            if (images != null) for (MultipartFile f : images) totalBytes += safeSize(f);
            if (videos != null) for (MultipartFile f : videos) totalBytes += safeSize(f);
            if (totalBytes > MAX_TOTAL_BYTES) {
                return AjaxResult.error("本次上传总大小超过 100 MB（约 " + (totalBytes / 1024 / 1024) + " MB）");
            }

            // 3. 目标目录
            String base = sanitize(nameBase);
            String user = sanitize(username);
            String folderName = base + "_" + user;
            Path targetDir = ROOT_DIR.resolve(folderName).normalize();
            if (!targetDir.startsWith(ROOT_DIR)) {
                return AjaxResult.error("非法路径");
            }
            Files.createDirectories(targetDir);

            // 4. 并发落盘任务
            int seq = 1;
            List<CompletableFuture<Map<String, Object>>> futures = new ArrayList<>();
            Semaphore semaphore = new Semaphore(PER_REQUEST_CONCURRENCY);

            // 4.1 图片
            if (images != null) {
                for (MultipartFile img : images) {
                    if (img == null || img.isEmpty()) continue;

                    String ext = getExtension(img.getOriginalFilename()).toLowerCase(Locale.ROOT);
                    final String filename = String.format(
                            "%s%s%03d%s", base, user, seq++, StringUtils.isBlank(ext) ? "" : "." + ext
                    );
                    final Path dest = targetDir.resolve(filename).normalize();

                    futures.add(CompletableFuture.supplyAsync(() -> {
                        acquire(semaphore);
                        try { return saveOne(img, dest, "image"); }
                        finally { semaphore.release(); }
                    }, executor));
                }
            }

            // 4.2 视频
            if (videos != null) {
                for (MultipartFile vid : videos) {
                    if (vid == null || vid.isEmpty()) continue;

                    String ext = getExtension(vid.getOriginalFilename()).toLowerCase(Locale.ROOT);
                    final String filename = String.format(
                            "%s%s%03d%s", base, user, seq++, StringUtils.isBlank(ext) ? "" : "." + ext
                    );
                    final Path dest = targetDir.resolve(filename).normalize();

                    futures.add(CompletableFuture.supplyAsync(() -> {
                        acquire(semaphore);
                        try { return saveOne(vid, dest, "video"); }
                        finally { semaphore.release(); }
                    }, executor));
                }
            }

            // 5. 等待完成并返回
            List<Map<String, Object>> files = new ArrayList<>(futures.size());
            for (CompletableFuture<Map<String, Object>> f : futures) {
                files.add(f.join());
            }
            files.sort(Comparator.comparing(m -> String.valueOf(m.get("path"))));

            Map<String, Object> data = new LinkedHashMap<>();
            data.put("folder", targetDir.toString());
            data.put("username", username);
            data.put("nameBase", nameBase);
            data.put("totalBytes", totalBytes);
            data.put("count", files.size());
            data.put("files", files);

            return AjaxResult.success("上传成功", data);

        } catch (Exception e) {
            return AjaxResult.error("保存文件失败：" + e.getMessage());
        }
    }

    // ========================= 辅助方法 =========================

    /** 安全获取文件大小，避免 NPE */
    private static long safeSize(MultipartFile f) {
        try { return (f == null) ? 0L : f.getSize(); }
        catch (Exception ignored) { return 0L; }
    }

    /** 获取扩展名（无则返回空字符串） */
    private static String getExtension(String originalName) {
        if (StringUtils.isBlank(originalName)) return "";
        int i = originalName.lastIndexOf('.');
        return (i >= 0 && i < originalName.length() - 1) ? originalName.substring(i + 1) : "";
    }

    /** 单文件保存（JDK8 兼容实现） */
    private Map<String, Object> saveOne(MultipartFile src, Path dest, String type) {
        try {
            Path parent = dest.getParent();
            if (parent == null) throw new IOException("父目录为空");
            if (!dest.normalize().startsWith(parent.normalize())) throw new IOException("非法路径");

            try (InputStream in = src.getInputStream()) {
                Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
            }

            Map<String, Object> m = new LinkedHashMap<>();
            m.put("type", type);
            m.put("path", dest.toString());
            m.put("sizeBytes", src.getSize());
            m.put("sizeMB", String.format(Locale.ROOT, "%.2f", src.getSize() / 1024.0 / 1024.0));
            return m;

        } catch (Exception ex) {
            throw new RuntimeException("保存失败：" + dest.getFileName() + "，原因：" + ex.getMessage(), ex);
        }
    }

    /** 获取信号量许可证（受控并发） */
    private static void acquire(Semaphore sem) {
        try { sem.acquire(); }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("线程被中断", e);
        }
    }

    /** 文件名/目录名清洗 */
    private static String sanitize(String s) {
        String cleaned = s.replaceAll("[^\\p{L}\\p{N}_-]", "_");
        return cleaned.length() > 64 ? cleaned.substring(0, 64) : cleaned;
    }
}
