package cn.teammodel.ai;

import cn.hutool.json.JSONUtil;
import cn.teammodel.ai.cache.HistoryCache;
import cn.teammodel.ai.domain.SparkChatRequestParam;
import cn.teammodel.ai.listener.SparkGptStreamListener;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * spark Gpt client
 * @author winter
 * @create 2023-12-15 14:29
 */
@Component
@Data
@Slf4j
public class SparkGptClient implements InitializingBean {
    @Resource
    private SparkGptProperties sparkGptProperties;
    private OkHttpClient okHttpClient;
//    private String authUrl;

    /**
    * 静态构造对象方法
    */
    public void init() {
        // 初始化缓存
        HistoryCache.init(sparkGptProperties.getCache_timeout(), sparkGptProperties.getCache_context());
        // 初始化 authUrl
        // log.info("[SPARK CHAT] 鉴权 endpoint : {}", this.authUrl);
        this.okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(180, TimeUnit.SECONDS)
                .readTimeout(180, TimeUnit.SECONDS) // sse 接口的 readTimeout 不能设置小了
                .writeTimeout(180, TimeUnit.SECONDS)
                .build();
    }

    /**
    * 流式的生成结果,以 sse 的方式向客户端推送
    */
    public void streamChatCompletion(SparkChatRequestParam param, SparkGptStreamListener listener) {
        try {
            param.setAppId(sparkGptProperties.getAppId());
            String authUrl = genAuthUrl(sparkGptProperties.getEndpoint(), sparkGptProperties.getApiKey(), sparkGptProperties.getApiSecret());
            authUrl = authUrl.replace("http://", "ws://").replace("https://", "wss://");
            Request request = new Request.Builder().url(authUrl).build();
            // 设置请求参数
            listener.setRequestJson(param.toJsonParams());
            log.info("[SPARK CHAT] 请求参数 {}", JSONUtil.parseObj(param.toJsonParams()).toStringPretty());
            // 不能使用单例 client
            okHttpClient.newWebSocket(request, listener);
        } catch (Exception e) {
            log.error("[SPARK CHAT] Spark AI 请求异常: {}", e.getMessage());
            e.printStackTrace();
        }
    }
    /**
     * 生成鉴权URL
     */
    public static String genAuthUrl(String endpoint, String apiKey, String apiSecret) {
        URL url = null;
        String date = null;
        String preStr = null;
        Mac mac = null;
        try {
            url = new URL(endpoint);
            // 时间
            SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
            format.setTimeZone(TimeZone.getTimeZone("GMT"));
            date = format.format(new Date());
            // 拼接
            preStr = "host: " + url.getHost() + "\n" +
                    "date: " + date + "\n" +
                    "GET " + url.getPath() + " HTTP/1.1";
            // SHA256加密
            mac = Mac.getInstance("hmacsha256");
            SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");
            mac.init(spec);
        } catch (Exception e) {
            log.error("[SPARK CHAT] 生成鉴权URL失败, endpoint: {}, apiKey: {}, apiSecret: {}", endpoint, apiKey, apiSecret);
            throw new RuntimeException(e);
        }

        byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
        // Base64加密
        String sha = Base64.getEncoder().encodeToString(hexDigits);
        // 拼接
        String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
        // 拼接地址
        HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().
                addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).
                addQueryParameter("date", date).//
                addQueryParameter("host", url.getHost()).//
                build();
        return httpUrl.toString();
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }
}
