package com.ruoyi.aicall.controller;

import com.alibaba.fastjson.JSONObject;
import com.ruoyi.aicall.domain.CcTtsAliyun;
import com.ruoyi.aicall.service.ICcTtsAliyunService;
import com.ruoyi.cc.service.IFsConfService;
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.StringUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/aicall/vtvoiceclone")
@Slf4j
public class VtVoiceCloneController extends BaseController {
    private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
    private static final DateTimeFormatter UTC_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
            .withZone(ZoneOffset.UTC);
    private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .build();
    private static final String DEFAULT_REGISTER_URL = "https://tts.ilivedata.com/api/v1/speech/synthesis/voice/register";
    private static final String DEFAULT_REGISTER_PATH = "/api/v1/speech/synthesis/voice/register";
    private static final String DEFAULT_HOST = "tts.ilivedata.com";
    private final String prefix = "aicall/vtvoiceclone";

    @Autowired
    private IFsConfService fsConfService;

    @Autowired
    private ICcTtsAliyunService ttsAliyunService;

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

    @RequiresPermissions("aicall:vtvoiceclone:registerVoice")
    @Log(title = "ViiTorTTS音色克隆", businessType = BusinessType.INSERT)
    @PostMapping("/registerVoice")
    @ResponseBody
    public AjaxResult registerVoice(@RequestParam("audio_url") String audioUrl,
                                    @RequestParam(value = "voice_name", required = false) String voiceName,
                                    @RequestParam(value = "language", required = false) String language,
                                    @RequestParam(value = "text", required = false) String text,
                                    @RequestParam(value = "gender", required = false) Integer gender) {
        try {
            String audio = trimToEmpty(audioUrl);
            if (StringUtils.isBlank(audio)) {
                return AjaxResult.error("请填写可公网访问的音频 URL");
            }
            if (!audio.startsWith("http://") && !audio.startsWith("https://")) {
                return AjaxResult.error("音频 URL 必须以 http:// 或 https:// 开头");
            }

            JSONObject account = loadViiTorAccount();
            JSONObject body = new JSONObject(true);
            body.put("audio", audio);
            if (StringUtils.isNotBlank(voiceName)) {
                body.put("voiceName", trimToEmpty(voiceName));
            }
            if (StringUtils.isNotBlank(language)) {
                body.put("language", normalizeLanguage(language));
            }
            if (StringUtils.isNotBlank(text)) {
                body.put("text", trimToEmpty(text));
            }
            if (gender != null && (gender == 0 || gender == 1)) {
                body.put("gender", gender);
            }

            JSONObject response = doRegisterRequest(account, body);
            Integer errorCode = response.getInteger("errorCode");
            if (errorCode != null && errorCode != 0) {
                return AjaxResult.error("ViiTorTTS 返回错误: " + response.toJSONString());
            }
            JSONObject data = response.getJSONObject("data");
            if (data == null) {
                return AjaxResult.error("ViiTorTTS 返回结果为空");
            }

            String registeredVoiceName = trimToEmpty(data.getString("voiceName"));
            if (StringUtils.isBlank(registeredVoiceName)) {
                return AjaxResult.error("ViiTorTTS 未返回音色名称:\n" + response.toJSONString());
            }
            CcTtsAliyun speaker = saveCloneVoice(
                    registeredVoiceName,
                    data.getString("language"),
                    data.getInteger("gender")
            );

            JSONObject result = new JSONObject(true);
            result.put("voiceName", speaker.getVoiceName());
            result.put("voiceCode", speaker.getVoiceCode());
            result.put("languageCode", speaker.getLanguageCode());
            result.put("languageName", speaker.getLanguageName());
            result.put("gender", data.getInteger("gender"));
            result.put("audioToTrain", trimToEmpty(data.getString("audioToTrain")));
            result.put("textToTrain", trimToEmpty(data.getString("textToTrain")));
            return AjaxResult.success("ViiTorTTS 音色注册成功，已写入音色表。", result);
        } catch (Exception e) {
            log.error("registerVoice error", e);
            return AjaxResult.error("ViiTorTTS 音色注册失败:\n" + e.getMessage());
        }
    }

    private JSONObject loadViiTorAccount() {
        JSONObject conf = fsConfService.getAsrConf("/autoload_configs/vt_tts.conf.xml");
        String appId = trimToEmpty(conf.getString("appid"));
        String secretKey = trimToEmpty(conf.getString("secret-key"));
        if (StringUtils.isBlank(appId) || StringUtils.isBlank(secretKey)) {
            throw new IllegalStateException("请先在“ViiTorTTS配置”中填写 AppId 和 SecretKey。");
        }

        JSONObject account = new JSONObject(true);
        account.put("appid", appId);
        account.put("secret-key", secretKey);
        account.put("verify-peer", config(conf, "verify-peer", "false"));
        return account;
    }

    private JSONObject doRegisterRequest(JSONObject account, JSONObject bodyJson) throws Exception {
        String body = bodyJson.toJSONString();
        String timestamp = UTC_FORMATTER.format(Instant.now());
        String authorization = buildAuthorization(account, body, timestamp, DEFAULT_HOST, DEFAULT_REGISTER_PATH);
        boolean verifyPeer = configBool(account, "verify-peer", false);
        OkHttpClient client = verifyPeer ? HTTP_CLIENT : HTTP_CLIENT.newBuilder()
                .hostnameVerifier((hostname, session) -> true)
                .build();

        Request request = new Request.Builder()
                .url(DEFAULT_REGISTER_URL)
                .post(RequestBody.create(JSON_TYPE, body))
                .addHeader("Content-Type", "application/json;charset=UTF-8")
                .addHeader("Accept", "application/json;charset=UTF-8")
                .addHeader("X-AppId", config(account, "appid", ""))
                .addHeader("X-TimeStamp", timestamp)
                .addHeader("Authorization", authorization)
                .build();

        try (Response response = client.newCall(request).execute()) {
            String responseBody = response.body() == null ? "" : response.body().string();
            log.info("vtvoiceclone request={}, status={}, body={}", body, response.code(), responseBody);
            if (!response.isSuccessful()) {
                throw new IOException("HTTP " + response.code() + ": " + responseBody);
            }
            return JSONObject.parseObject(responseBody);
        }
    }

    private CcTtsAliyun saveCloneVoice(String voiceName, String language, Integer gender) {
        String voiceCode = trimToEmpty(voiceName);
        String languageCode = normalizeLanguageCode(language);
        String languageName = toLanguageName(languageCode);
        String storedVoiceName = buildStoredVoiceName(voiceName, gender, languageName);

        CcTtsAliyun speaker = ttsAliyunService.selectCcTtsAliyunByVoiceCode(voiceCode);
        boolean update = speaker != null;
        if (!update) {
            speaker = new CcTtsAliyun();
        }
        speaker.setVoiceName(storedVoiceName);
        speaker.setVoiceCode(voiceCode);
        speaker.setVoiceEnabled(1);
        speaker.setVoiceSource("vt_tts");
        speaker.setPriority(0);
        speaker.setProvider("vt_tts");
        speaker.setLanguageCode(languageCode);
        speaker.setLanguageName(languageName);
        speaker.setTtsModels("clone");

        if (update) {
            ttsAliyunService.updateCcTtsAliyun(speaker);
        } else {
            ttsAliyunService.insertCcTtsAliyun(speaker);
        }
        return speaker;
    }

    private String buildStoredVoiceName(String voiceName, Integer gender, String languageName) {
        String cleanName = trimToEmpty(voiceName).replace("'", "").replace("\"", "");
        if (cleanName.length() > 30) {
            cleanName = cleanName.substring(0, 30);
        }
        String genderText = gender != null && gender == 1 ? "男声" : "女声";
        return "ViiTor克隆-" + cleanName + "-" + languageName + "-" + genderText;
    }

    private String buildAuthorization(JSONObject account,
                                      String body,
                                      String timestamp,
                                      String host,
                                      String requestPath) throws Exception {
        String appId = config(account, "appid", "");
        String secretKey = config(account, "secret-key", "");
        String bodyHash = sha256Hex(body);
        String stringToSign = "POST\n" +
                host + "\n" +
                requestPath + "\n" +
                bodyHash + "\n" +
                "X-AppId:" + appId + "\n" +
                "X-TimeStamp:" + timestamp;
        return Base64.getEncoder().encodeToString(hmacSha256(secretKey, stringToSign));
    }

    private byte[] hmacSha256(String key, String message) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        return mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
    }

    private String sha256Hex(String value) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(value.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder(hash.length * 2);
        for (byte b : hash) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    private String config(JSONObject obj, String key, String defaultValue) {
        if (obj == null) {
            return defaultValue;
        }
        String value = trimToEmpty(obj.getString(key));
        return StringUtils.isBlank(value) ? defaultValue : value;
    }

    private boolean configBool(JSONObject obj, String key, boolean defaultValue) {
        String value = config(obj, key, String.valueOf(defaultValue));
        if (StringUtils.isBlank(value)) {
            return defaultValue;
        }
        return "1".equals(value) || "true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value);
    }

    private String trimToEmpty(String value) {
        return value == null ? "" : value.trim();
    }

    private String normalizeLanguage(String value) {
        String input = trimToEmpty(value).replace('_', '-');
        if (StringUtils.isBlank(input)) {
            return "zh-CN";
        }
        if ("zh".equalsIgnoreCase(input)) return "zh-CN";
        if ("en".equalsIgnoreCase(input)) return "en-US";
        if ("ja".equalsIgnoreCase(input)) return "ja-JP";
        if ("ko".equalsIgnoreCase(input)) return "ko-KR";
        if ("yue".equalsIgnoreCase(input)) return "yue-HK";
        return input;
    }

    private String normalizeLanguageCode(String value) {
        return normalizeLanguage(value);
    }

    private String toLanguageName(String languageCode) {
        if ("en-US".equalsIgnoreCase(languageCode)) return "英文";
        if ("ja-JP".equalsIgnoreCase(languageCode)) return "日语";
        if ("ko-KR".equalsIgnoreCase(languageCode)) return "韩语";
        if ("yue-HK".equalsIgnoreCase(languageCode)) return "粤语";
        return "中文";
    }
}
