package com.fuyuanshen.web.util;

import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

/**
 * 视频处理工具类
 */
@Slf4j
@Component
public class VideoProcessUtil {

    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    /**
     * 创建临时视频文件
     */
    public File createTempVideoFile(MultipartFile file) throws Exception {
        File tempFile = Files.createTempFile("upload-", ".mp4").toFile();
        file.transferTo(tempFile);
        log.debug("创建临时视频文件: {}", tempFile.getAbsolutePath());
        return tempFile;
    }

    /**
     * 处理视频并转换为Hex字符串列表
     */
    public List<String> processVideoToHex(File videoFile, int frameRate, int duration, int width, int height, int code) throws Exception {
        // 1. 提取视频帧
        List<BufferedImage> frames = extractFramesFromVideo(videoFile, frameRate, duration, width, height);

        if (code == 1) {
            // 1. 转换为RGB565格式
            byte[] binaryData = convertFramesToRGB565(frames);

            // 2. 转换为Hex字符串列表
            return bytesToHexList(binaryData);
        } else {
            // 1. 转换为BGR565格式
            byte[] binaryData = convertFramesToBGR565(frames);

            // 新增：直接生成 mp4
            //bgr565ToMp4(binaryData, width, height, frameRate, "output.mp4");

            // 2. 转换为Hex字符串列表
            return bytesToHexList(binaryData);
        }
    }


    /**
     * 从视频中提取帧
     *
     * @param videoFile 视频文件对象
     * @param frameRate 每秒提取的帧数（帧率）
     * @param duration  需要提取的视频时长（秒）
     * @param width     提取帧的宽度
     * @param height    提取帧的高度
     * @return 提取的帧图像列表
     * @throws Exception 如果在提取过程中发生错误
     */
    private List<BufferedImage> extractFramesFromVideo(File videoFile, int frameRate, int duration, int width, int height) throws Exception {
        // 初始化帧列表
        List<BufferedImage> frames = new ArrayList<>();
        // 计算需要提取的总帧数 = 帧率 × 时长
        int totalFramesToExtract = frameRate * duration;

        // 使用FFmpegFrameGrabber从视频文件中抓取帧
        try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoFile)) {
            // 启动抓取器
            grabber.start();

            // 获取视频总帧数
            long totalFramesInVideo = grabber.getLengthInFrames();
            // 获取视频帧率，如果获取不到则默认为30fps
            int fps = (int) Math.round(grabber.getFrameRate());
            if (fps <= 0) fps = 30;

            // 计算视频总时长（秒）
            double durationSeconds = (double) totalFramesInVideo / fps;
            // 检查视频时长是否满足要求
            if (durationSeconds < duration) {
                throw new IllegalArgumentException("视频太短，至少需要 " + duration + " 秒");
            }

            // 计算帧间隔，用于均匀分布提取的帧
            double frameInterval = (double) totalFramesInVideo / totalFramesToExtract;

            // 循环提取指定数量的帧
            for (int i = 0; i < totalFramesToExtract; i++) {
                // 计算目标帧号
                int targetFrameNumber = (int) Math.round(i * frameInterval);

                // 检查目标帧号是否超出视频范围
                if (targetFrameNumber >= totalFramesInVideo) {
                    throw new IllegalArgumentException("目标帧超出范围: " + targetFrameNumber);
                }

                // 设置抓取器到目标帧
                grabber.setFrameNumber(targetFrameNumber);
                // 抓取当前帧
                Frame frame = grabber.grab();

                // 如果成功抓取到帧且帧图像不为空
                if (frame != null && frame.image != null) {
                    // 将帧转换为BufferedImage并裁剪到指定尺寸
                    BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
                    frames.add(cropImage(bufferedImage, width, height));
                } else {
                    // 如果无法获取帧则抛出异常
                    throw new IllegalArgumentException("无法获取第 " + targetFrameNumber + "帧");
                }
            }

            // 停止抓取器
            grabber.stop();
        }

        // 记录提取的帧数
        log.debug("从视频中提取了 {} 帧", frames.size());
        // 返回提取的帧列表
        return frames;
    }


    /**
     * 将所有帧转换为 RGB565 格式字节数组
     */
    private byte[] convertFramesToRGB565(List<BufferedImage> frames) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        for (BufferedImage image : frames) {
            byte[] rgb565Bytes = convertToRGB565(image);
            byteArrayOutputStream.write(rgb565Bytes);
        }

        byte[] result = byteArrayOutputStream.toByteArray();
        log.debug("转换RGB565数据完成，总字节数: {}", result.length);
        return result;
    }

    /**
     * 将所有帧转换为 BGR565 格式字节数组
     */
    private byte[] convertFramesToBGR565(List<BufferedImage> frames) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        for (BufferedImage image : frames) {
            byte[] bgr565Bytes = convertToBGR565(image);
            byteArrayOutputStream.write(bgr565Bytes);
        }

        byte[] result = byteArrayOutputStream.toByteArray();
        log.debug("转换BGR565数据完成，总字节数: {}", result.length);
        return result;
    }

    /**
     * 将BufferedImage转换为真正的BGR565格式字节数组
     */
    private byte[] convertToBGR565(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();
        byte[] bgr565Data = new byte[width * height * 2];

        int index = 0;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int rgb = image.getRGB(x, y);

                // 提取RGB分量
                int red = (rgb >> 16) & 0xFF;
                int green = (rgb >> 8) & 0xFF;
                int blue = rgb & 0xFF;

                int b = (blue >> 3) & 0x1F;   // 5位蓝色
                int g = (green >> 2) & 0x3F;  // 6位绿色
                int r = (red >> 3) & 0x1F;    // 5位红色

                // 正确的BGR565组合：红色在高位，蓝色在低位
                int bgr565 = (b << 11) | (g << 5) | r;

                bgr565Data[index++] = (byte) ((bgr565 >> 8) & 0xFF);
                // 小端序存储
                bgr565Data[index++] = (byte) (bgr565 & 0xFF);
            }
        }
        return bgr565Data;
    }

    /**
     * 将字节数组转换为Hex字符串列表
     */
    private List<String> bytesToHexList(byte[] bytes) {
        List<String> hexList = new ArrayList<>();
        for (byte b : bytes) {
            int value = b & 0xFF;
            char high = HEX_ARRAY[value >>> 4];
            char low = HEX_ARRAY[value & 0x0F];
            hexList.add(String.valueOf(high) + low);
        }
        return hexList;
    }

    /**
     * 删除临时文件
     */
    public void deleteTempFile(File file) {
        if (file != null && file.exists()) {
            if (file.delete()) {
                log.debug("删除临时文件成功: {}", file.getAbsolutePath());
            } else {
                log.warn("无法删除临时文件: {}", file.getAbsolutePath());
            }
        }
    }

    /**
     * 裁剪图像到目标尺寸
     */
    private BufferedImage cropImage(BufferedImage img, int targetWidth, int targetHeight) {
        int w = Math.min(img.getWidth(), targetWidth);
        int h = Math.min(img.getHeight(), targetHeight);
        return img.getSubimage(0, 0, w, h);
    }

    /**
     * 将 BufferedImage 转换为 RGB565 格式的字节数组
     */
    private byte[] convertToRGB565(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();
        byte[] result = new byte[width * height * 2];
        int index = 0;

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int rgb = image.getRGB(x, y);
                int r = ((rgb >> 16) & 0xFF) >> 3;
                int g = ((rgb >> 8) & 0xFF) >> 2;
                int b = (rgb & 0xFF) >> 3;
                short pixel = (short) ((r << 11) | (g << 5) | b);

                result[index++] = (byte) (pixel >> 8);
                result[index++] = (byte) pixel;
            }
        }

        return result;
    }

    /**
     * 保存帧到本地（用于调试）
     */
    public void saveFramesToLocal(List<BufferedImage> frames, String prefix) {
        File outputDir = new File("output_frames");
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }

        int index = 0;
        for (BufferedImage frame : frames) {
            try {
                File outputImage = new File(outputDir, prefix + "_" + (index++) + ".png");
                ImageIO.write(frame, "png", outputImage);
                log.debug("保存帧图片成功: {}", outputImage.getAbsolutePath());
            } catch (Exception e) {
                log.error("保存帧图片失败", e);
            }
        }
    }

    /**
     * 把 BGR565 字节流直接写成 MP4（H.264）
     * @param bgr565  完整的 BGR565 裸帧流（每像素 2 字节）
     * @param width   帧宽
     * @param height  帧高
     * @param fps     帧率
     * @param outMp4  输出 mp4 文件绝对路径
     * @throws IOException 进程启动 / IO 失败
     */
    public static void bgr565ToMp4(byte[] bgr565,
                                   int width,
                                   int height,
                                   int fps,
                                   String outMp4) throws IOException {

        int framePixels = width * height;
        int frameBytes  = framePixels * 2;
        if (bgr565.length % frameBytes != 0) {
            throw new IllegalArgumentException("字节数组长度不是整帧");
        }

        /* 1. 构造 FFmpeg 命令 */
        String[] cmd = {
                "ffmpeg",
                "-y",                                 // 覆盖输出
                "-f", "rawvideo",
                "-pixel_format", "bgr24",
                "-video_size", width + "x" + height,
                "-framerate", String.valueOf(fps),
                "-i", "-",                            // 从 stdin 读
                "-c:v", "libx264",
                "-pix_fmt", "yuv420p",
                "-crf", "23",                         // 画质可自己调
                outMp4
        };

        /* 2. 启动进程 */
        ProcessBuilder pb = new ProcessBuilder(cmd);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT); // 把 FFmpeg 日志打到控制台
        Process p = pb.start();
        try (OutputStream ffmpegIn = p.getOutputStream()) {

            /* 3. 逐帧转换并写入管道 */
            byte[] bgr24 = new byte[framePixels * 3];
            for (int off = 0; off < bgr565.length; off += frameBytes) {
                for (int i = 0, j = 0; i < frameBytes; i += 2, j += 3) {
                    int u = ((bgr565[off + i + 1] & 0xFF) << 8)
                            |  (bgr565[off + i]     & 0xFF);
                    int b = (u & 0x1F) << 3;
                    int g = ((u >> 5)  & 0x3F) << 2;
                    int r = ((u >> 11) & 0x1F) << 3;
                    bgr24[j]     = (byte) b;
                    bgr24[j + 1] = (byte) g;
                    bgr24[j + 2] = (byte) r;
                }
                ffmpegIn.write(bgr24);
            }
            ffmpegIn.flush();
        }

        /* 4. 等待编码结束 */
        try {
            int exit = p.waitFor();
            if (exit != 0) {
                throw new IOException("FFmpeg 异常退出，code=" + exit);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("等待 FFmpeg 被中断", e);
        }
    }
}