import axios from 'axios';
import * as fs from 'fs';
import * as path from 'path';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import logger from '../log/main';
import { getFFmpegPath, getFFprobePath } from '../shell';

const DASHSCOPE_API_URL = 'https://dashscope.aliyuncs.com/api/v1';
const MIN_ENROLLMENT_SAMPLE_RATE = 16000;
const execFileAsync = promisify(execFile);

async function ensureEnrollmentAudio(filePath: string): Promise<string> {
    if (!fs.existsSync(filePath)) {
        throw new Error(`文件不存在: ${filePath}`);
    }

    const ffprobePath = await getFFprobePath();
    const ffmpegPath = await getFFmpegPath();

    let sampleRate = 0;
    let channels = 0;
    try {
        const { stdout } = await execFileAsync(ffprobePath, [
            '-v', 'error',
            '-select_streams', 'a:0',
            '-show_entries', 'stream=sample_rate,channels',
            '-of', 'json',
            filePath,
        ]);
        const probe = JSON.parse(stdout || '{}');
        const stream = probe?.streams?.[0] || {};
        sampleRate = parseInt(stream.sample_rate || '0', 10) || 0;
        channels = parseInt(stream.channels || '0', 10) || 0;
    } catch (error: any) {
        logger.warn('[Aliyun:Enrollment] ffprobe 检测失败，直接转码兜底', {
            filePath,
            error: error.message,
        });
    }

    const needsConvert = !sampleRate || sampleRate < MIN_ENROLLMENT_SAMPLE_RATE || channels !== 1 || path.extname(filePath).toLowerCase() !== '.wav';
    if (!needsConvert) {
        logger.info('[Aliyun:Enrollment] 音频格式符合要求，直接使用', { filePath, sampleRate, channels });
        return filePath;
    }

    const parsed = path.parse(filePath);
    const normalizedPath = (p: string) => process.platform === 'win32' ? p.replace(/\\/g, '/') : p;
    const outputPath = path.join(parsed.dir, `${parsed.name}_enroll_16k.wav`);

    logger.info('[Aliyun:Enrollment] 开始转码复刻音频', {
        input: filePath,
        output: outputPath,
        sourceSampleRate: sampleRate,
        sourceChannels: channels,
        targetSampleRate: MIN_ENROLLMENT_SAMPLE_RATE,
        targetChannels: 1,
    });

    await execFileAsync(ffmpegPath, [
        '-y',
        '-i', normalizedPath(filePath),
        '-vn',
        '-acodec', 'pcm_s16le',
        '-ar', String(MIN_ENROLLMENT_SAMPLE_RATE),
        '-ac', '1',
        normalizedPath(outputPath),
    ]);

    logger.info('[Aliyun:Enrollment] 音频转码完成', { outputPath });
    return outputPath;
}

/**
 * 阿里云 DashScope Voice Enrollment (声音复刻) 服务
 */

/**
 * 将本地音频文件转换为 Base64 Data URI
 * DashScope create_voice 接口支持 data:{mime_type};base64,{base64_str} 格式
 * 这完全绕过了需要公网URL的问题！
 */
export async function uploadFileToLease(apiKey: string, filePath: string): Promise<string> {
    if (!fs.existsSync(filePath)) {
        throw new Error(`文件不存在: ${filePath}`);
    }

    const enrollmentAudioPath = await ensureEnrollmentAudio(filePath);
    const fileName = path.basename(enrollmentAudioPath);
    const ext = path.extname(enrollmentAudioPath).toLowerCase();

    // 根据扩展名确定 MIME 类型
    const mimeTypes: Record<string, string> = {
        '.wav': 'audio/wav',
        '.mp3': 'audio/mp3',
        '.m4a': 'audio/m4a',
        '.flac': 'audio/flac',
        '.ogg': 'audio/ogg',
        '.aac': 'audio/aac',
        '.wma': 'audio/wma',
        '.amr': 'audio/amr',
        '.pcm': 'audio/pcm'
    };

    const mimeType = mimeTypes[ext] || 'audio/wav';

    try {
        logger.info(`[Aliyun:Enrollment] 将音频文件转换为 Base64 Data URI: ${fileName}`);

        // 读取文件并转换为 Base64
        const fileBuffer = fs.readFileSync(enrollmentAudioPath);
        const base64Str = fileBuffer.toString('base64');

        // 构造 Data URI
        const dataUri = `data:${mimeType};base64,${base64Str}`;

        logger.info(`[Aliyun:Enrollment] 文件转换成功, 大小: ${Math.round(fileBuffer.length / 1024)} KB, MIME: ${mimeType}`);

        return dataUri;

    } catch (error: any) {
        logger.error('[Aliyun:Enrollment] 文件转换失败', error.message);
        throw error;
    }
}

// 废弃旧的 applyLease 和 uploadToOss 函数
// async function applyLease ... 
// async function uploadToOss ...

/*
interface LeaseResponse {
    url: string; // 用于后续API调用的文件URL
    param: {
        url: string; // 上传地址
        method: string;
        headers: any;
    };
    upload_method: string;
}

async function applyLease(apiKey: string, extension: string): Promise<LeaseResponse> {
    const url = `${DASHSCOPE_API_URL}/uploads`;
    const payload = {
        action: 'getPolicy', // 注意：DashScope不同服务的上传接口可能不同，这里使用通用的uploads/getPolicy ? 
        // 修正：DashScope有一个通用的 `uploads` 接口用于申请 lease
        // 文档: POST https://dashscope.aliyuncs.com/api/v1/uploads
        model: 'voice-enrollment' // 指定模型
    };

    try {
        const response = await axios.get(url, {
            params: payload,
            headers: {
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/json'
            }
        });

        // 响应结构:
        // {
        //   "data": {
        //     "url": "https://...",
        //     "param": { ... },
        //     "upload_method": "PUT" / "POST"
        //   }
        // }

        if (response.data && response.data.data) {
            const data = response.data.data;
            logger.info('[Aliyun:Enrollment] Apply Lease 响应:', JSON.stringify(data));

            // 适配 OSS PostObject 格式
            // https://help.aliyun.com/zh/oss/developer-reference/postobject
            if (data.policy && data.signature && data.upload_host) {
                // 构造文件名（使用随机UUID或保留原扩展名）
                // 上传后的 URL = upload_host + / + upload_dir + / + filename
                // 但是 DashScope 可能要求特定的 key 格式？
                // data.upload_dir 是前缀。
                // 我们需要自己生成 key。

                return {
                    url: '', // 这里的 URL 需要在上传后拼接，或者 DashScope 不需要? 
                    // 文档说 create_voice 需要 public url.
                    // OSS PostObject 不会自动返回 URL。
                    // 通常是 upload_host + '/' + key
                    param: {
                        url: data.upload_host,
                        method: 'POST',
                        headers: {}, // PostObject 不需要 Authorization header，签名在 body 里
                        formData: {
                            'key': data.upload_dir + '/${filename}', // 这是一个模板，实际上传时替换
                            'policy': data.policy,
                            'Signature': data.signature,
                            'OSSAccessKeyId': data.oss_access_key_id,
                            'x-oss-object-acl': data.x_oss_object_acl,
                            'x-oss-forbid-overwrite': data.x_oss_forbid_overwrite,
                            // 'file': ... (在 uploadToOss 中添加)
                        },
                        // 用于拼接最终 URL
                        uploadDir: data.upload_dir,
                        uploadHost: data.upload_host
                    },
                    upload_method: 'POST'
                } as LeaseResponse; // 临时通过 any 绕过类型检查，稍后修正 Interface
            }

            // 兼容性处理：如果 param 不存在，可能是扁平结构，或者结构有所不同
            if (!data.param) {
                // 如果没有 param，看看有没有 url
                if (data.url) {
                    // 构造一个伪 param
                    return {
                        url: data.url,
                        upload_method: data.upload_method || 'POST', // 默认 POST?
                        param: {
                            url: data.url,
                            method: data.upload_method || 'PUT', // 大多数预签名URL是PUT
                            headers: data.headers || {} // 扁平结构可能有 headers
                        }
                    } as LeaseResponse;
                }
            }
            return data as LeaseResponse;
        } else {
            logger.error('[Aliyun:Enrollment] Apply Lease 响应异常:', JSON.stringify(response.data));
            throw new Error('申请上传凭证失败: ' + JSON.stringify(response.data));
        }

    } catch (error: any) {
        logger.error('[Aliyun:Enrollment] Apply Lease 失败', error.response?.data || error.message);
        throw error;
    }
}

async function uploadToOss(filePath: string, param: any): Promise<string> {
    const fileBuffer = fs.existsSync(filePath) ? fs.readFileSync(filePath) : null;
    if (!fileBuffer) throw new Error(`文件读取失败: ${filePath}`);

    const fileName = path.basename(filePath);

    try {
        const method = param.method || 'PUT';
        logger.info(`[Aliyun:Enrollment] 上传文件到临时存储 URL: ${param.url}`);

        if (method.toUpperCase() === 'POST' && param.formData) {
            logger.info('[Aliyun:Enrollment] 使用 FormData (OSS PostObject) 上传');

            // 使用 Node.js 全局 FormData (Node 18+)
            // 如果环境不支持，这里会报错。Electron 29 (Node 20) 应该支持。
            const FormDataClass = (global as any).FormData;
            if (!FormDataClass) {
                throw new Error('当前环境不支持全局 FormData，无法执行 POST 上传');
            }

            const form = new FormDataClass();

            // 构造 Key (FilePath in OSS)
            // 上面的 param.formData['key'] 是个模板: .../${filename}
            // 我们生成一个唯一文件名，或者直接用 fileName
            const finalKey = param.formData['key'].replace('${filename}', fileName);

            // 必须按照顺序添加吗？OSS 对顺序通常不敏感，但 file 最好在最后
            form.append('key', finalKey);
            form.append('policy', param.formData['policy']);
            form.append('Signature', param.formData['Signature']);
            form.append('OSSAccessKeyId', param.formData['OSSAccessKeyId']);
            if (param.formData['x-oss-object-acl']) form.append('x-oss-object-acl', param.formData['x-oss-object-acl']);
            if (param.formData['x-oss-forbid-overwrite']) form.append('x-oss-forbid-overwrite', param.formData['x-oss-forbid-overwrite']);

            // 添加文件
            // Node.js 的 FormData 需要 Blob 或 File。
            // 我们可以用 Blob (Node 18+)
            const BlobClass = (global as any).Blob;
            if (!BlobClass) {
                throw new Error('当前环境不支持全局 Blob');
            }
            const blob = new BlobClass([fileBuffer]);
            form.append('file', blob, fileName);

            // 发送请求
            // axios 在 Node 中支持传入 FormData 实例，并自动设置 headers (Content-Type: multipart/form-data; boundary=...)
            await axios.post(param.url, form, {
                headers: {
                    // 让 axios/FormData 自动计算 Content-Type
                    // 'Content-Type': 'multipart/form-data' // 不要手动设置，否则丢失 boundary
                }
            });

            // 更新 leaseData.url，因为 PostObject 不会返回，我们需要自己算
            // param 是引用传递吗？不是，这里是 uploadToOss。
            // 我们需要一种方式把 URL 传回去。
            // 这里我们无法修改外部的 leaseData。
            // 所以 uploadToOss 应该返回 url。

            const objectUrl = `${param.uploadHost}/${finalKey}`;
            logger.info(`[Aliyun:Enrollment] OSS 上传成功, URL: ${objectUrl}`);
            return objectUrl;
        }

        // PUT 逻辑保持不变
        else if (method.toUpperCase() === 'PUT') {
            logger.info(`[Aliyun:Enrollment] 上传 Method: ${method}, Headers:`, param.headers);
            await axios.put(param.url, fileBuffer, {
                headers: param.headers || {}
            });
            logger.info('[Aliyun:Enrollment] 上传 OSS 成功');
            return param.url.split('?')[0]; // 去除签名参数得到纯URL? 不一定，视情况而定
        } else {
            logger.warn(`[Aliyun:Enrollment] 未知上传方法: ${method}, 尝试 Generic Request`);
            await axios({
                method: method,
                url: param.url,
                headers: param.headers,
                data: fileBuffer
            });
            logger.info('[Aliyun:Enrollment] 上传 OSS 成功');
            return param.url.split('?')[0]; // 假设通用请求也返回带签名的URL
        }

    } catch (error: any) {
        // 详细错误日志
        const errInfo = {
            status: error.response?.status,
            statusText: error.response?.statusText,
            data: error.response?.data,
            headers: error.response?.headers,
            message: error.message
        };
        logger.error('[Aliyun:Enrollment] 上传 OSS 失败', JSON.stringify(errInfo));
        throw error;
    }
}
*/

/**
 * 创建复刻音色
 */
export async function createVoice(apiKey: string, audioUrl: string, prefix: string = 'custom_voice'): Promise<string> {
    const url = `${DASHSCOPE_API_URL}/services/audio/tts/customization`;

    const payload = {
        model: 'qwen-voice-enrollment',
        input: {
            action: 'create',
            target_model: 'qwen3-tts-vc-2026-01-22',
            preferred_name: prefix,
            audio: {
                data: audioUrl
            }
        }
    };

    logger.info('[Aliyun:Enrollment] 开始复刻音色', payload);

    try {
        const response = await axios.post(url, payload, {
            headers: {
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/json'
            }
        });

        const data = response.data;
        const voiceId = data.output?.voice || data.output?.voice_id;
        if (voiceId) {
            logger.info('[Aliyun:Enrollment] 复刻成功', data.output);
            return voiceId;
        } else {
            throw new Error('复刻响应格式错误: ' + JSON.stringify(data));
        }
    } catch (error: any) {
        logger.error('[Aliyun:Enrollment] 复刻失败', error.response?.data || error.message);
        throw error;
    }
}
