package com.ruoyi.aicall.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.aicall.domain.CcTtsAliyun;
import com.ruoyi.aicall.model.DoubaoVclTtsRequest;
import com.ruoyi.aicall.service.ICcCallPhoneService;
import com.ruoyi.aicall.service.ICcCallTaskService;
import com.ruoyi.aicall.service.ICcTtsAliyunService;
import com.ruoyi.cc.service.ICcParamsService;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.CommonUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.UuidGenerator;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import sun.audio.AudioData;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * doubao voice clone
 * 
 * @author ruoyi
 * @date 2025-05-29
 */
@Controller
@RequestMapping("/aicall/doubaovcl")
@Slf4j
public class DoubaoVclController extends BaseController {
    private String prefix = "aicall/doubaovcl";

    @Autowired
    private ICcTtsAliyunService ttsAliyunService;

    @Autowired
    private ICcParamsService ccParamsService;

//    /**
//     * Add/update tts speakers
//     * @param nameParam
//     * @param speakerId
//     */
//    private synchronized void addSpeakerId(String nameParam, String speakerId) {
//        CcTtsAliyun ttsSpeaker = ttsAliyunService.selectCcTtsAliyunByVoiceCode(speakerId);
//        String name = nameParam.replace("'", "").replace(" ", "");
//        if (name.length() > 20) {
//            name = name.substring(0, 20);
//        }
//
//        boolean update = false;
//        if (null == ttsSpeaker) {
//            ttsSpeaker = new CcTtsAliyun();
//        } else {
//            update = true;
//        }
//
//        ttsSpeaker.setVoiceCode(speakerId);
//        ttsSpeaker.setVoiceEnabled(1);
//        ttsSpeaker.setVoiceName(String.format("[%s] - %s", speakerId, name));
//        ttsSpeaker.setVoiceSource("doubao_vcl_tts");
//        ttsSpeaker.setPriority(0);
//        ttsSpeaker.setProvider("doubao");
//
//        try {
//            if (update) {
//                ttsAliyunService.updateCcTtsAliyun(ttsSpeaker);
//            } else {
//                ttsAliyunService.insertCcTtsAliyun(ttsSpeaker);
//            }
//            logger.info("save doubaovcl speakerId succeed. {} {}", name, speakerId);
//        } catch (Throwable e) {
//            logger.error("save doubaovcl speakerId error: {} {}", e.toString(),
//                    CommonUtils.getStackTraceString(e.getStackTrace()));
//        }
//    }

    @RequiresPermissions("aicall:doubaovcl:view")
    @GetMapping("voiceclone")
    public String callTask() {
        return prefix + "/voiceclone";
    }

    private static final String HOST = "https://openspeech.bytedance.com";

    private static OkHttpClient _httpClient = null;
    // 创建带超时配置的OkHttpClient
    private static synchronized OkHttpClient createHttpClient() {
        if(_httpClient == null) {
            _httpClient = new OkHttpClient.Builder()
                    .connectTimeout(30, TimeUnit.SECONDS)
                    .readTimeout(60, TimeUnit.SECONDS)
                    .writeTimeout(60, TimeUnit.SECONDS)
                    .build();
        }
        return _httpClient;
    }

    public String train(String appid, String token, String audioFormat, byte[] audioBytes, String spkId,
                        String language,  String modelType) throws IOException {
        String url = HOST + "/api/v1/mega_tts/audio/upload";
        OkHttpClient client = createHttpClient();
        // 编码音频文件
        AudioData audioData = encodeAudioFile(audioFormat, audioBytes);
        JSONArray audios = new JSONArray();
        JSONObject audioObj = new JSONObject();
        audioObj.put("audio_bytes", audioData.getEncodedData());
        audioObj.put("audio_format", audioData.getAudioFormat());
        audios.add(audioObj);

        // 构建请求体
        JSONObject requestBody = new JSONObject();
        requestBody.put("appid", appid);
        requestBody.put("speaker_id", spkId);
        requestBody.put("audios", audios);
        requestBody.put("source", 2);
        requestBody.put("language", Integer.parseInt(language));
        requestBody.put("model_type", Integer.parseInt(modelType));

        okhttp3.RequestBody body = okhttp3.RequestBody.create(
                MediaType.parse("application/json"),
                requestBody.toString()
        );

        String resourceId = "seed-icl-1.0";
        if(Integer.parseInt(modelType) == 4){
            resourceId = "seed-icl-2.0";
        }
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .addHeader("Content-Type", "application/json")
                .addHeader("Authorization", "Bearer;" + token)
                .addHeader("Resource-Id", resourceId)
                .build();

        try (Response response = client.newCall(request).execute()) {
            logger.info("doubaovcl train http response status code {} ", response.code());

            String responseStr = response.body().string();
            logger.info("doubaovcl train response: {}", responseStr);

            if(response.code() == 403){
                return "{\"BaseResp\":{ \"StatusCode\": 403 }}";
            }

            return responseStr;
        }
    }

    public boolean getStatus(String appid, String token, String spkId) throws IOException {
        String url = HOST + "/api/v1/mega_tts/status";
        OkHttpClient client = new OkHttpClient();
        JSONObject requestBody = new JSONObject();
        requestBody.put("appid", appid);
        requestBody.put("speaker_id", spkId);

        okhttp3.RequestBody body = okhttp3.RequestBody.create(
                MediaType.parse("application/json"),
                requestBody.toString()
        );

        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .addHeader("Content-Type", "application/json")
                .addHeader("Authorization", "Bearer;" + token)
                .addHeader("Resource-Id", "volc.megatts.voiceclone")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if(response.isSuccessful()) {
                String json = response.body().string();
                logger.info("doubaovcl getStatus response: {}", json);
                JSONObject jsonObject = JSON.parseObject(json);
                int status = jsonObject.getInteger("status");
                return status == 2 || status == 4;
            }
        }
        return false;
    }


    private static AudioData encodeAudioFile(String audioFormat, byte[] audioBytes) throws IOException {
        String encodedData = Base64.getEncoder().encodeToString(audioBytes);
        return new AudioData(encodedData, audioFormat);
    }

    // 辅助类用于存储音频数据
    private static class AudioData {
        private final String encodedData;
        private final String audioFormat;

        public AudioData(String encodedData, String audioFormat) {
            this.encodedData = encodedData;
            this.audioFormat = audioFormat;
        }

        public String getEncodedData() {
            return encodedData;
        }

        public String getAudioFormat() {
            return audioFormat;
        }
    }

    public static Map<Integer, String> ERROR_MAP = new HashMap<Integer, String>() {{
        put(1001, "BadRequestError: 请求参数有误");
        put(1101, "AudioUploadError: 音频上传失败");
        put(1102, "ASRError: ASR（语音识别成文字）转写失败");
        put(1103, "SIDError: SID声纹检测失败");
        put(1104, "SIDFailError: 声纹检测未通过，声纹跟名人相似度过高");
        put(1105, "GetAudioDataError: 获取音频数据失败");
        put(1106, "SpeakerIDDuplicationError: SpeakerID重复");
        put(1107, "SpeakerIDNotFoundError: SpeakerID未找到");
        put(1108, "AudioConvertError: 音频转码失败");
        put(1109, "WERError: wer检测错误，上传音频与请求携带文本对比字错率过高");
        put(1111, "AEEDerror: aed检测错误，通常由于音频不包含说话声");
        put(1112, "SNRError: SNR检测错误，通常由于信噪比过高");
        put(1113, "DenoiseError: 降噪处理失败");
        put(1114, "AudioQualityError: 音频质量低，降噪失败");
        put(1122, "ASRNoSpeakerError: 未检测到人声");
        put(1123, "MaxTrainNumLimitReached: 上传接口已经达到次数限制，目前同一个音色支持10次上传");
        put(403, "ParameterError: 参数错误：请检查 声音ID、app_id、access_token 是否正确、 检查 声音ID 是否和模型类型匹配? (也可能没有开通相关服务)");
    }};

    /**
     * uploadAndTrain
     */
    @RequiresPermissions("aicall:doubaovcl:uploadAndTrain")
    @Log(title = "uploadAndTrain", businessType = BusinessType.IMPORT)
    @PostMapping("/uploadAndTrain")
    @ResponseBody
    public AjaxResult uploadAndTrain(HttpServletRequest request, @RequestParam("file") MultipartFile file) throws Exception {

        if (file == null || file.isEmpty()) {
            String tips = MessageUtils.message("common.adminPage.upload.requiredTips");
            return AjaxResult.error(tips);
        }

        // 上传到远程API接口，成功后写入本地音色数据表中;
        String voiceName = request.getParameter("voice_name");
        String spkId = request.getParameter("speaker_id");

        String ttsAccountJson = ccParamsService.getParamValueByCode(
                "doubao-tts-account-json","{}");
        JSONObject paramValues = JSONObject.parseObject(ttsAccountJson);

        String appid = paramValues.getString("app_id");
        String token = paramValues.getString("access_token");
        String language = request.getParameter("language");
        String modelType = request.getParameter("model_type");

        int strLen = file.getOriginalFilename().length();
        String audioFormat = file.getOriginalFilename().substring(strLen - 3, strLen);
        byte[] audioBytes = file.getBytes();

        try {
             String response = train(appid, token, audioFormat, audioBytes, spkId, language, modelType);
            //String response = "{\"BaseResp\":{ \"StatusCode\": 0 }}";;
            if (!StringUtils.isEmpty(response)) {
                JSONObject jsonObject = JSON.parseObject(response);
                int statusCode = jsonObject.getJSONObject("BaseResp").getInteger("StatusCode");
                if (statusCode == 0) {
//                    addSpeakerId(voiceName, spkId);
                    boolean  modelReady = getStatus(appid, token, spkId);
                    String tips = modelReady ? MessageUtils.message("doubaovcl.admin.model-status-ready") :
                            MessageUtils.message("doubaovcl.admin.model-status-not-ready");
                    return AjaxResult.success(MessageUtils.message("doubaovcl.admin.train-success") + "\n" + tips);
                } else {
                    String errDetails =  jsonObject.getJSONObject("BaseResp").getString("StatusMessage");
                    String errMsg = ERROR_MAP.get(statusCode);
                    if (!StringUtils.isEmpty(errMsg)) {
                        return AjaxResult.error(errMsg + "\n" + errDetails);
                    } else {
                        return AjaxResult.error("UnKnown error!\n" + response);
                    }
                }
            } else {
                return AjaxResult.error("UnKnown error!\n" + response);
            }
        } catch (IOException e) {
            AjaxResult.error(String.format("Server internal error! %s \n %s", e.toString(), CommonUtils.getStackTraceString(e.getStackTrace())));
        }
        return null;
    }

    /**
     * doubaoTtsTest
     */
    @RequiresPermissions("aicall:doubaovcl:doubaoTtsTest")
    @PostMapping("/doubaoTtsTest")
    @ResponseBody
    public AjaxResult doubaoTtsTest(HttpServletRequest request) throws Exception {
        String ttsAccountJson = ccParamsService.getParamValueByCode(
                "doubao-tts-account-json","{}");
        JSONObject paramValues = JSONObject.parseObject(ttsAccountJson);
        String appid = paramValues.getString("app_id");
        String token = paramValues.getString("access_token");

        String speakerId = request.getParameter("speakerId");
        String language = request.getParameter("language");
        String text = request.getParameter("text");

        DoubaoVclTtsRequest ttsRequestConfig = DoubaoVclTtsRequest.builder()
                .app(DoubaoVclTtsRequest.App.builder()
                        .appid(appid)
                        .cluster("volcano_icl")
                        .build())
                .user(DoubaoVclTtsRequest.User.builder()
                        .uid("uid")
                        .build())
                .audio(DoubaoVclTtsRequest.Audio.builder()
                        .encoding("mp3")
                        .voiceType(speakerId)
                        .language(language)
                        .build())
                .request(DoubaoVclTtsRequest.Request.builder()
                        .reqID(UuidGenerator.GetOneUuid())
                        .operation("query")
                        .text(text)
                        .build())
                .build();

        String reqBody = JSON.toJSONString(ttsRequestConfig);
        log.info("doubaovcl tts request: {}", reqBody);

        String apiUrl = "https://openspeech.bytedance.com/api/v1/tts";
        OkHttpClient client = createHttpClient();
        okhttp3.RequestBody body = okhttp3.RequestBody.create(MediaType.get("application/json; charset=utf-8"), reqBody);
        okhttp3.Request ttsRequest = new okhttp3.Request.Builder()
                .url(apiUrl)
                .header("Authorization", "Bearer;" + token)
                .post(body)
                .build();

        Response response = client.newCall(ttsRequest).execute();
        String respStr = response.body().string();
        if(!response.isSuccessful()) {
            log.info("doubaovcl tts response: {}", respStr );
            return AjaxResult.error("语音合成失败! \n" + respStr);
        }
        return  AjaxResult.success("tts request success.", respStr);
    }

}
