package com.fs.watch.utils;

import com.fs.system.oss.CloudStorageService;
import com.fs.system.oss.OSSFactory;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

//import static sun.misc.IOUtils.readNBytes;

public class AudioConvertUtil {

    // 之前的方法 convertHardwareCompatibleAudio、uploadFileToCOS 等假设已经存在

    /**
     * 转换wav到硬件兼容 opuswb，上传到 COS，并删除临时文件
     *
     * @param inputWavPath wav 文件路径
     * @param deviceId 设备ID，用于命名
     * @param recordTime 录音时间字符串 "yyyy-MM-dd HH:mm:ss"
     * @param audioBasePath 本地生成音频文件根目录
     * @return 上传后的 COS URL
     * @throws Exception 转换或上传失败抛出异常
     */
    public static String convertUploadAndClean(
            String inputWavPath,
            String deviceId,
            String recordTime,
            String audioBasePath) throws Exception {

        // 1. 转换生成硬件兼容 opuswb 文件路径
        String outputOpuswbPath = convertHardwareCompatibleAudio(inputWavPath, deviceId, recordTime, audioBasePath);

        // 2. 计算上传到云存储的路径 cosPath
        String datePart = recordTime.substring(0, 10); // 例如 "2025-07-22"
        String[] ymd = datePart.split("-"); // ["2025", "07", "22"]

        String fileName = deviceId + "-" +
                recordTime.substring(11, 13) +
                recordTime.substring(14, 16) +
                recordTime.substring(17, 19) +
                ".opuswb";

        String cosPath = String.format("sendAudio/%s%s%s/%s", ymd[0],ymd[1], ymd[2], fileName);

        // 3. 上传文件到云存储
        try (InputStream inputStream = new FileInputStream(outputOpuswbPath)) {
            CloudStorageService storage = OSSFactory.build();
            storage.upload(inputStream, cosPath);
        }

        // 4. 删除本地临时文件
        File tempFile = new File(outputOpuswbPath);
        if (tempFile.exists() && !tempFile.delete()) {
            System.err.println("临时文件删除失败：" + outputOpuswbPath);
        }

        // 5. 构造并返回访问URL，示例写法请根据你实际域名修改
        return "https://cos.his.cdwjyyh.com/" + cosPath;
    }



    public static String convertHardwareCompatibleAudio(
            String inputWavPath, String deviceId, String recordTime, String audioBasePath) throws Exception {

        String subdir = recordTime.substring(0, 10);
        String dirPath = Paths.get(audioBasePath, subdir).toString();
        Files.createDirectories(Paths.get(dirPath));

        String fileTimePart = recordTime.substring(11, 13) + recordTime.substring(14, 16) + recordTime.substring(17, 19);
        String outputOpusFile = Paths.get(dirPath, deviceId + "-" + fileTimePart + ".opus").toString();
        convertWavToOpus(inputWavPath, outputOpusFile);

        String hardwareOpusFile = Paths.get(dirPath, deviceId + "-" + fileTimePart + ".opuswb").toString();
        File hwOpus = new File(hardwareOpusFile);
        if (hwOpus.exists()) hwOpus.delete();

        decodeOggOpusToRawOpus(new File(outputOpusFile), hwOpus);

        return hardwareOpusFile;
    }

    public static void convertWavToOpus(String inputWavPath, String outputOpusPath) throws IOException, InterruptedException {
        File outputFile = new File(outputOpusPath);
        if (outputFile.exists()) {
            outputFile.delete();
        }
        List<String> command = Arrays.asList(
                "ffmpeg",
                "-i", inputWavPath,
                "-c:a", "libopus",
                "-b:a", "16k",
                "-ar", "16000",
                "-ac", "1",
                "-vbr", "off",
                "-frame_duration", "20",
                "-application", "voip",
                outputOpusPath
        );
        ProcessBuilder pb = new ProcessBuilder(command);
        Process process = pb.start();
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line;
                while ((line = errorReader.readLine()) != null) {
                    System.err.println(line);
                }
            }
            throw new RuntimeException("ffmpeg 转码失败");
        }
    }

    public static void decodeOggOpusToRawOpus(File inputFile, File outputFile) throws IOException {
        try (InputStream in = new FileInputStream(inputFile);
             OutputStream out = new FileOutputStream(outputFile)) {

            int packetsSkipped = 0;
            byte[] headerBuf = new byte[27];
            while (true) {
                int readBytes = in.read(headerBuf);
                if (readBytes < 27) break;

                if (!(headerBuf[0] == 'O' && headerBuf[1] == 'g' && headerBuf[2] == 'g' && headerBuf[3] == 'S')) {
                    throw new IOException("Invalid Ogg file");
                }

                int segments = headerBuf[26] & 0xFF;
                byte[] lacingValues = new byte[segments];
                if (in.read(lacingValues) < segments) break;

                List<Integer> packetSizes = new ArrayList<>();
                int currentSize = 0;
                for (byte b : lacingValues) {
                    currentSize += (b & 0xFF);
                    if ((b & 0xFF) < 255) {
                        packetSizes.add(currentSize);
                        currentSize = 0;
                    }
                }

                for (int size : packetSizes) {
                    byte[] packet = readNBytes(in,size);

                    if (packetsSkipped < 2) {
                        packetsSkipped++;
                        continue;
                    }

                    out.write(ByteBuffer.allocate(4).putInt(size).array());
                    out.write(ByteBuffer.allocate(4).putInt(size).array());
                    out.write(packet);
                }
            }
        }
    }

    /**
     * 从输入流中读取最多 len 个字节，返回读取到的字节数组（等价于 readNBytes）
     * @param in 输入流（如 FileInputStream、ByteArrayInputStream）
     * @param len 要读取的最大字节数
     * @return 实际读取到的字节数组
     * @throws IOException 读取 IO 异常
     */
    public static byte[] readNBytes(InputStream in, int len) throws IOException {
        // 参数合法性校验
        if (len < 0) {
            throw new IllegalArgumentException("读取字节长度不能为负数");
        }
        if (in == null) {
            throw new NullPointerException("输入流不能为 null");
        }

        byte[] buffer = new byte[len];
        int totalReadBytes = 0; // 累计已读取的字节数

        // 循环读取，直到读取到指定长度或输入流结束
        while (totalReadBytes < len) {
            // 每次读取剩余未完成的字节数
            int readBytes = in.read(buffer, totalReadBytes, len - totalReadBytes);

            // 输入流已到达末尾（返回 -1），终止循环
            if (readBytes == -1) {
                break;
            }

            totalReadBytes += readBytes;
        }

        // 若实际读取的字节数小于指定长度，截取有效字节数组返回
        if (totalReadBytes < len) {
            byte[] result = new byte[totalReadBytes];
            System.arraycopy(buffer, 0, result, 0, totalReadBytes);
            return result;
        }

        return buffer;
    }

}

