package cn.iocoder.byzs.module.ai.framework.ai.core.model.suno.api;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import kong.unirest.HttpResponse;
import kong.unirest.RequestBodyEntity;
import kong.unirest.Unirest;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Suno API
 * <p>
 * 对接 Suno Proxy：<a href="https://github.com/gcui-art/suno-api">suno-api</a>
 *
 * @author xiaoxin
 */
@Slf4j
public class SunoApi {

    // 用于发送 HTTP 请求的 WebClient 实例
    private final WebClient webClient;

    // 状态判断条件，用于判断 HTTP 响应状态码是否非 2xx 成功状态
    private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful();

    /**
     * 异常处理函数，当 HTTP 响应状态码非 2xx 时触发
     * 会记录请求信息和响应数据，并抛出 IllegalStateException 异常
     */
    private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
            reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> {
                HttpRequest request = response.request();
                // 记录请求失败的详细信息，包括请求方式、地址、参数和响应数据
                log.error("[suno-api] 调用失败！请求方式:[{}]，请求地址:[{}]，请求参数:[{}]，响应数据: [{}]",
                        request.getMethod(), request.getURI(), reqParam, responseBody);
                // 抛出异常表示请求失败
                sink.error(new IllegalStateException("[suno-api] 调用失败！"));
            });

    /**
     * 构造函数，初始化 WebClient 实例
     *
     * @param baseUrl Suno API 的基础 URL
     */
    public SunoApi(String baseUrl) {
        this.webClient = WebClient.builder()
                .baseUrl(baseUrl)
                .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
                .build();
    }

    /**
     * 根据请求参数生成音乐列表
     *
     * @param request 音乐生成请求参数对象
     * @return 音乐数据列表
     */
    public List<MusicData> generate(MusicGenerateRequest request) {

//        Unirest.config().connectTimeout(0).socketTimeout(0);  // 连接超时和套接字超时均设为 0（无限制）
//        HttpResponse<String> response = Unirest.post("https://apibox.erweima.ai/api/v1/generate")
//                .header("Content-Type", "application/json")
//                .header("Accept", "application/json")
//                .header("Authorization", "Bearer 1723a5683447c298cf6ea1362c084977")
//                .body("{\n  \"prompt\": \"一段平静舒缓的钢琴曲，带有柔和的旋律\",\n  \"style\": \"古典\",\n  \"title\": \"宁静钢琴冥想\",\n  \"customMode\": true,\n  \"instrumental\": true,\n  \"model\": \"V3_5\",\n  \"negativeTags\": \"重金属, 强节奏鼓点\",\n  \"callBackUrl\": \"https://api.example.com/callback\"\n}")
//                .asString();
//
//        String body = response.getBody();
//        // 使用 Jackson 反序列化
//        com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
//        SunoApi.ResMusicData apiResponse = objectMapper.readValue(body, SunoApi.ResMusicData.class);
//
//        // 判断 code 是否为 200
//        if (apiResponse.getCode() == 200) {
//            String taskId = apiResponse.getData().getTaskId();
//            System.out.println("请求成功，taskId：" + taskId);
//        } else {
//            System.out.println("请求失败，code：" + apiResponse.getCode() + "，message：" + apiResponse.getMsg());
//        }

        //【存在问题】原写法请求不同，官方写法无法直接转成多个taskid，只有请求主taskid，成功后会出现两条子taskid，库中只有子taskid


        return this.webClient.post()          // 使用 WebClient 发起 POST 请求
                .uri("/api/v1/generate")          // 设置请求 URI 为 "/api/v1/generate"（生成音乐的接口路径）
                .body(Mono.just(request), MusicGenerateRequest.class)  // 将请求参数对象 request 作为请求体（Mono 是 Reactor 的响应式类型）
                .retrieve()                       // 执行请求并获取响应处理器
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))  // 当响应状态码非 2xx 时，触发异常处理（记录日志并抛异常）
                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() {})  // 将响应体反序列化为 MusicData 列表（处理泛型类型）
                .block();                         // 阻塞等待响应结果（同步获取 List<MusicData>）

    }

    /**
     * 根据请求参数定制生成音乐列表
     *
     * @param request 音乐生成请求参数对象
     * @return 音乐数据列表
     */
    public List<MusicData> customGenerate(MusicGenerateRequest request) {
        return this.webClient.post()
                .uri("/api/v1/custom_generate")
                .body(Mono.just(request), MusicGenerateRequest.class)
                .retrieve()
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() {
                })
                .block();
    }

    /**
     * 根据提示生成歌词
     *
     * @param prompt 生成歌词的提示信息
     * @return 歌词数据对象
     */
    public LyricsData generateLyrics(String prompt) {
        return this.webClient.post()
                .uri("/api/generate_lyrics")
                .body(Mono.just(new MusicGenerateRequest(prompt)), MusicGenerateRequest.class)
                .retrieve()
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(prompt))
                .bodyToMono(LyricsData.class)
                .block();
    }

    /**
     * 根据音乐 ID 列表获取音乐列表
     *
     * @param ids 音乐 ID 列表
     * @return 音乐数据列表
     */
    public List<MusicData> getMusicList(List<String> ids) {
        return this.webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/api/get")
                        .queryParam("ids", CollUtil.join(ids, StrPool.COMMA))
                        .build())
                .retrieve()
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(ids))
                .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() {
                })
                .block();
    }

    /**
     * 获取 API 使用限额信息
     *
     * @return 限额使用数据对象
     */
    public LimitUsageData getLimitUsage() {
        return this.webClient.get()
                .uri("/api/get_limit")
                .retrieve()
                .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(null))
                .bodyToMono(LimitUsageData.class)
                .block();
    }

    /**
     * 根据提示生成音频的请求参数类
     *
     * @param prompt           用于生成音乐音频的提示
     * @param style             音乐风格
     * @param title            音乐名称
     * @param model            模型
     * @param waitAudio        false 表示后台模式，仅返回音频任务信息，需要调用 get API 获取详细的音频信息。
     *                         true 表示同步模式，API 最多等待 100s，音频生成完毕后直接返回音频链接等信息，建议在 GPT 等 agent 中使用。
     * @param instrumental      指示音乐音频是否为定制，如果为 true，则从歌词生成，否则从提示生成
     * @param customMode     是否定制音乐
     * @param negativeTags       用于过滤的负标签，多个标签之间用逗号分隔
     * @param callBackUrl      回调路径
     */
    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    public record MusicGenerateRequest(
            String prompt,
            String style,
            String title,
            String model,
            @JsonProperty("wait_audio") boolean waitAudio,
            boolean instrumental,
            boolean customMode,
            String negativeTags,
            String callBackUrl
    ) {

        /**
         * 只使用提示信息的构造函数
         *
         * @param prompt 生成音乐的提示信息
         */
        public MusicGenerateRequest(String prompt) {
            this(prompt, null, null, null, false, false, false, null, null);
        }

        /**
         * 使用提示信息、模型和是否定制标志的构造函数
         *
         * @param prompt             生成音乐的提示信息
         * @param model              模型名称
         * @param makeInstrumental   是否定制音乐
         */
        public MusicGenerateRequest(String prompt, String model, boolean makeInstrumental) {
            this(prompt, null, null, model, true, makeInstrumental, false, null, null);
        }

        /**
         * 使用提示信息、模型、音乐风格和标题的构造函数
         *
         * @param prompt 生成音乐的提示信息
         * @param model  模型名称
         * @param tags   音乐风格
         * @param title  音乐标题
         */
        public MusicGenerateRequest(String prompt, String model, String tags, String title) {
            this(prompt, tags, title, model, true, false, false, null, null);
        }

    }

    /**
     * Suno API 响应的限额数据类，目前每日免费 50
     *
     * @param creditsLeft   剩余可用额度
     * @param period        时间周期
     * @param monthlyLimit  每月限额
     * @param monthlyUsage  每月已使用额度
     * Suno API 响应的歌词数据类。
     * @param errorMessage         错误信息
     * Suno API 响应的音频数据类
     * Suno API 响应的音频数据
     *
     * @param id                   音乐数据的 ID
     * @param title                音乐音频的标题
     * @param imageUrl             音乐音频的图片 URL
     * @param lyric                音乐音频的歌词
     * @param audioUrl             音乐音频的 URL
     * @param videoUrl             音乐视频的 URL
     * @param createdAt            音乐音频的创建时间
     * @param modelName            模型名称
     * @param status               submitted、queued、streaming、complete
     * @param gptDescriptionPrompt 描述词
     * @param prompt               生成音乐音频的提示
     * @param type                 操作类型
     * @param tags                 音乐类型标签
     * @param duration             音乐时长
     */
    public record MusicData(
            String id,
            String title,
            @JsonProperty("image_url") String imageUrl,
            String lyric,
            @JsonProperty("audio_url") String audioUrl,
            @JsonProperty("video_url") String videoUrl,
            @JsonProperty("created_at") String createdAt,
            @JsonProperty("model_name") String modelName,
            String status,
            @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt,
            @JsonProperty("error_message") String errorMessage,
            String prompt,
            String type,
            String tags,
            Double duration
    ) {
    }

    /**
     * Suno API 响应的歌词数据。
     *
     * @param text   歌词
     * @param title  标题
     * @param status 状态
     */
    public record LyricsData(
            String text,
            String title,
            String status
    ) {
    }

    /**
     * Suno API 响应的限额数据，目前每日免费 50
     */
    public record LimitUsageData(
            @JsonProperty("credits_left") Long creditsLeft,
            String period,
            @JsonProperty("monthly_limit") Long monthlyLimit,
            @JsonProperty("monthly_usage") Long monthlyUsage
    ) {
    }




    /**
     * Suno API 返回状态值
     */
    @Data
     public class ResMusicData {
        Integer code;
        String msg;
        Data data;

        @lombok.Data
        public class Data {
            String taskId;
        }
    }
}
