package com.ylx.massage.service.impl;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 视频封面截取服务
 * 依赖 FFmpeg，需要提前在服务器安装 FFmpeg
 */
@Slf4j
@Service
public class VideoThumbnailService {

    // 视频临时存储路径
    private static final String VIDEO_TEMP_PATH = "E:/tmp/videos/";
    // 封面图片存储路径
    private static final String THUMBNAIL_SAVE_PATH = "E:/tmp/thumbnails/";
    // FFmpeg 命令路径（Windows 下需要使用 .exe 扩展名）
    private static final String FFMPEG_PATH = "E:\\ffmpeg-8.0.1-essentials_build\\bin\\ffmpeg.exe";
    // 截取时间点（秒）
    private static final int CAPTURE_TIME_SECOND = 1;
    // 命令执行超时时间（秒）
    private static final int COMMAND_TIMEOUT = 30;

    /**
     * 从上传的视频文件中截取封面图
     *
     * @param videoFile 上传的视频文件
     * @return 封面图片的保存路径
     * @throws VideoProcessException 视频处理异常
     */
    public String extractThumbnail(MultipartFile videoFile) throws VideoProcessException {
        validateVideoFile(videoFile);
        String videoTempPath = null;
        try {
            // 1. 保存上传的视频文件到临时目录
            videoTempPath = saveVideoToTemp(videoFile);
            log.info("视频文件已保存到临时目录: {}", videoTempPath);

            // 2. 生成封面图片文件（该方法内部会执行 FFmpeg 命令并验证）
            String thumbnailFilePath = generateThumbnailFilePath(videoTempPath, videoFile.getOriginalFilename());
            return thumbnailFilePath;
        } catch (Exception e) {
            log.error("视频封面截取失败", e);
            throw new VideoProcessException("视频封面截取失败: " + e.getMessage(), e);
        } finally {
            // 清理临时视频文件
            if (videoTempPath != null) {
                deleteFileQuietly(videoTempPath);
            }
        }
    }

    /**
     * 验证上传的视频文件
     * <p>
     * 验证上传的视频文件是否符合要求，包括非空、文件扩展名和文件大小。
     * </p>
     *
     * @param videoFile 上传的视频文件
     * @throws VideoProcessException 如果视频文件为空、文件名无效、格式不支持或文件大小超过限制
     */
    private void validateVideoFile(MultipartFile videoFile) throws VideoProcessException {
        if (videoFile == null || videoFile.isEmpty()) {
            throw new VideoProcessException("视频文件不能为空");
        }

        String originalFilename = videoFile.getOriginalFilename();
        if (originalFilename == null || originalFilename.trim().isEmpty()) {
            throw new VideoProcessException("视频文件名不能为空");
        }

        // 验证文件扩展名
        String extension = getFileExtension(originalFilename).toLowerCase();
        if (!isValidVideoFormat(extension)) {
            throw new VideoProcessException("不支持的视频格式: " + extension);
        }

        // 验证文件大小（例如限制 500MB）
        long maxSize = 50 * 1024 * 1024L;
        if (videoFile.getSize() > maxSize) {
            throw new VideoProcessException("视频文件过大，最大支持 50MB");
        }
    }

    /**
     * 检查是否为支持的视频格式
     */
    private boolean isValidVideoFormat(String extension) {
        return extension.matches("mp4|avi|mov|mkv|flv|wmv|webm|m4v");
    }

    /**
     * 保存视频到临时目录
     * <p>
     * 将上传的视频文件保存到临时目录，确保目录存在并生成唯一的文件名。
     * </p>
     *
     * @param videoFile 上传的视频文件
     * @return String 视频文件在临时目录中的路径
     * @throws IOException 如果文件操作失败
     */
    private String saveVideoToTemp(MultipartFile videoFile) throws IOException {
        // 确保临时目录存在
        Path tempDir = Paths.get(VIDEO_TEMP_PATH);
        if (!Files.exists(tempDir)) {
            Files.createDirectories(tempDir);
        }

        // 生成唯一的临时文件名
        String extension = getFileExtension(videoFile.getOriginalFilename());
        String tempFileName = UUID.randomUUID().toString() + "." + extension;
        log.info("临时文件名: {}", tempFileName);
        Path tempFilePath = tempDir.resolve(tempFileName);

        // 保存文件
        try (InputStream inputStream = videoFile.getInputStream();
             OutputStream outputStream = Files.newOutputStream(tempFilePath)) {

            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
        }

        return tempFilePath.toString();
    }

    /**
     * 生成封面图片保存路径
     *
     * @param originalFilename 原始文件名
     * @return String 封面图片保存路径
     */
    private String generateThumbnailPath(String originalFilename) throws IOException {
        // 确保保存目录存在
        Path saveDir = Paths.get(THUMBNAIL_SAVE_PATH);
        if (!Files.exists(saveDir)) {
            Files.createDirectories(saveDir);
        }

        // 生成唯一的封面文件名
        String baseFilename = getFileNameWithoutExtension(originalFilename);
        String thumbnailFileName = baseFilename + "_" + System.currentTimeMillis() + ".jpg";

        return saveDir.resolve(thumbnailFileName).toString();
    }

    /**
     * 生成封面图片文件到指定路径
     * <p>
     * 该方法会完整地执行以下操作：
     * 1. 确保保存目录存在
     * 2. 生成唯一的封面文件路径
     * 3. 使用 FFmpeg 从视频中截取封面图
     * 4. 验证生成的图片文件有效性
     * </p>
     *
     * @param videoPath 视频文件路径
     * @param originalFilename 原始视频文件名（用于生成缩略图文件名）
     * @return 生成的封面图片文件绝对路径
     * @throws IOException 如果目录创建失败
     * @throws VideoProcessException 如果封面生成失败或验证失败
     */
    private String generateThumbnailFilePath(String videoPath, String originalFilename) throws IOException, VideoProcessException {
        // 1. 确保保存目录存在
        Path saveDir = Paths.get(THUMBNAIL_SAVE_PATH);
        if (!Files.exists(saveDir)) {
            Files.createDirectories(saveDir);
            log.info("创建封面图片保存目录: {}", saveDir.toAbsolutePath());
        }

        // 2. 生成唯一的封面文件路径
        String baseFilename = getFileNameWithoutExtension(originalFilename);
        String thumbnailFileName = baseFilename + "_" + System.currentTimeMillis() + "_thumbnail.jpg";
        Path thumbnailPath = saveDir.resolve(thumbnailFileName).toAbsolutePath();
        String thumbnailPathStr = thumbnailPath.toString();
        log.info("开始生成封面图片，视频: {}, 目标路径: {}", videoPath, thumbnailPathStr);

        // 3. 执行 FFmpeg 命令生成封面
        try {
            executeFfmpegCommand(videoPath, thumbnailPathStr);
            log.info("FFmpeg 命令执行成功");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new VideoProcessException("封面生成过程被中断", e);
        }

        // 4. 验证生成的封面文件
        validateThumbnailFile(thumbnailPathStr);
        log.info("封面图片生成成功: {}", thumbnailPathStr);
        return thumbnailPathStr;
    }

    /**
     * 执行 FFmpeg 命令截取封面
     */
    private void executeFfmpegCommand(String videoPath, String thumbnailPath) throws IOException, InterruptedException, VideoProcessException {
        // 构建 FFmpeg 命令
        // -ss: 截取时间点
        // -i: 输入文件
        // -vframes 1: 只截取一帧
        // -q:v 2: 图片质量（1-31，数字越小质量越高）
        // -y: 覆盖输出文件
        List<String> command = new ArrayList<>();
        command.add(FFMPEG_PATH);
        command.add("-ss");
        command.add(String.valueOf(CAPTURE_TIME_SECOND));
        command.add("-i");
        command.add(videoPath);
        command.add("-vframes");
        command.add("1");
        command.add("-q:v");
        command.add("2");
        command.add("-y");
        command.add(thumbnailPath);
        log.info("执行FFmpeg命令: {}", String.join(" ", command));

        // 执行命令
        ProcessBuilder processBuilder = new ProcessBuilder(command);
        processBuilder.redirectErrorStream(true);
        Process process = processBuilder.start();

        // 读取命令输出（用于日志和调试）
        StringBuilder output = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
        }
        // 等待命令执行完成（设置超时）
        boolean finished = process.waitFor(COMMAND_TIMEOUT, TimeUnit.SECONDS);
        if (!finished) {
            process.destroyForcibly();
            throw new VideoProcessException("FFmpeg 命令执行超时");
        }
        int exitCode = process.exitValue();
        if (exitCode != 0) {
            log.error("FFmpeg 执行失败，输出: {}", output);
            throw new VideoProcessException("FFmpeg 执行失败，退出码: " + exitCode);
        }
        log.debug("FFmpeg 输出: {}", output);
    }

    /**
     * 验证生成的封面文件
     */
    private void validateThumbnailFile(String thumbnailPath) throws VideoProcessException {
        File thumbnailFile = new File(thumbnailPath);
        if (!thumbnailFile.exists()) {
            throw new VideoProcessException("封面文件生成失败");
        }
        if (thumbnailFile.length() == 0) {
            throw new VideoProcessException("生成的封面文件为空");
        }
        // 可选：验证文件是否为有效的图片
        try {
            String mimeType = Files.probeContentType(thumbnailFile.toPath());
            if (mimeType == null || !mimeType.startsWith("image/")) {
                throw new VideoProcessException("生成的文件不是有效的图片");
            }
        } catch (IOException e) {
            log.warn("无法验证文件 MIME 类型", e);
        }
    }

    /**
     * 静默删除文件
     */
    private void deleteFileQuietly(String filePath) {
        try {
            Files.deleteIfExists(Paths.get(filePath));
            log.debug("已删除文件: {}", filePath);
        } catch (Exception e) {
            log.warn("删除文件失败: {}", filePath, e);
        }
    }

    /**
     * 获取文件扩展名
     *
     * @param filename 文件名
     * @return String 文件扩展名（不包含点号），如果文件名没有扩展名则返回空字符串
     */
    private String getFileExtension(String filename) {
        if (filename == null || filename.isEmpty()) {
            return "";
        }
        int lastDotIndex = filename.lastIndexOf('.');
        return lastDotIndex > 0 ? filename.substring(lastDotIndex + 1) : "";
    }

    /**
     * 获取不带扩展名的文件名
     */
    private String getFileNameWithoutExtension(String filename) {
        if (filename == null || filename.isEmpty()) {
            return UUID.randomUUID().toString();
        }
        int lastDotIndex = filename.lastIndexOf('.');
        return lastDotIndex > 0 ? filename.substring(0, lastDotIndex) : filename;
    }

    /**
     * 视频处理异常
     */
    public static class VideoProcessException extends Exception {
        public VideoProcessException(String message) {
            super(message);
        }

        public VideoProcessException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
