package shop.alien.store.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import shop.alien.store.config.BaseRedisService;
import shop.alien.store.config.WxJsSdkProperties;
import shop.alien.store.dto.WxJsSdkConfigVo;
import shop.alien.store.service.WxJsSdkService;

import java.io.IOException;
import java.security.SecureRandom;
import java.util.concurrent.TimeUnit;

/**
 * 微信公众号 JS-SDK 签名实现
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WxJsSdkServiceImpl implements WxJsSdkService {

    private static final String ACCESS_TOKEN_CACHE_KEY = "wx:jsapi:access_token";
    private static final String JSAPI_TICKET_CACHE_KEY = "wx:jsapi:jsapi_ticket";
    private static final long CACHE_SECONDS = 7000L;

    private static final String ACCESS_TOKEN_URL =
            "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    private static final String JSAPI_TICKET_URL =
            "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi";

    private static final SecureRandom RANDOM = new SecureRandom();
    private static final char[] NONCE_CHARS =
            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();

    private final WxJsSdkProperties wxJsSdkProperties;
    private final BaseRedisService redisService;

    private final OkHttpClient httpClient = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .build();

    @Override
    public WxJsSdkConfigVo buildJsSdkConfig(String url) {
        if (!StringUtils.hasText(url)) {
            throw new IllegalArgumentException("url 不能为空");
        }
        String pageUrl = stripHash(url.trim());

        String ticket = getJsapiTicket();
        String nonceStr = randomNonceStr(15);
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);

        String signStr = "jsapi_ticket=" + ticket
                + "&noncestr=" + nonceStr
                + "&timestamp=" + timestamp
                + "&url=" + pageUrl;
        String signature = DigestUtils.sha1Hex(signStr);

        return new WxJsSdkConfigVo(
                wxJsSdkProperties.getAppId(),
                timestamp,
                nonceStr,
                signature
        );
    }

    private String getAccessToken() {
        String cached = redisService.getString(ACCESS_TOKEN_CACHE_KEY);
        if (StringUtils.hasText(cached)) {
            return cached;
        }

        String appId = wxJsSdkProperties.getAppId();
        String appSecret = wxJsSdkProperties.getAppSecret();
        if (!StringUtils.hasText(appId) || !StringUtils.hasText(appSecret)) {
            throw new IllegalStateException("未配置 wx.appId / wx.appSecret");
        }

        String requestUrl = String.format(ACCESS_TOKEN_URL, appId, appSecret);
        JSONObject body = getWechatJson(requestUrl, "获取 access_token");
        String accessToken = body.getString("access_token");
        if (!StringUtils.hasText(accessToken)) {
            throw new RuntimeException("获取 access_token 失败，返回数据异常: " + body);
        }

        redisService.setString(ACCESS_TOKEN_CACHE_KEY, accessToken, CACHE_SECONDS);
        return accessToken;
    }

    private String getJsapiTicket() {
        String cached = redisService.getString(JSAPI_TICKET_CACHE_KEY);
        if (StringUtils.hasText(cached)) {
            return cached;
        }

        String accessToken = getAccessToken();
        String requestUrl = String.format(JSAPI_TICKET_URL, accessToken);
        JSONObject body = getWechatJson(requestUrl, "获取 jsapi_ticket");
        String ticket = body.getString("ticket");
        if (!StringUtils.hasText(ticket)) {
            throw new RuntimeException("获取 jsapi_ticket 失败，返回数据异常: " + body);
        }

        redisService.setString(JSAPI_TICKET_CACHE_KEY, ticket, CACHE_SECONDS);
        return ticket;
    }

    private JSONObject getWechatJson(String url, String action) {
        Request request = new Request.Builder().url(url).get().build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new RuntimeException(action + "失败，HTTP状态码: " + response.code());
            }
            String responseBody = response.body() != null ? response.body().string() : "";
            JSONObject jsonObject = JSON.parseObject(responseBody);
            if (jsonObject.containsKey("errcode") && jsonObject.getIntValue("errcode") != 0) {
                log.error("{}失败，errcode={}, errmsg={}, body={}",
                        action, jsonObject.getIntValue("errcode"), jsonObject.getString("errmsg"), responseBody);
                throw new RuntimeException(action + "失败: " + jsonObject.getString("errmsg"));
            }
            return jsonObject;
        } catch (IOException e) {
            log.error("{}网络请求异常", action, e);
            throw new RuntimeException(action + "网络请求失败: " + e.getMessage(), e);
        }
    }

    private static String stripHash(String url) {
        int hashIndex = url.indexOf('#');
        return hashIndex >= 0 ? url.substring(0, hashIndex) : url;
    }

    private static String randomNonceStr(int length) {
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            sb.append(NONCE_CHARS[RANDOM.nextInt(NONCE_CHARS.length)]);
        }
        return sb.toString();
    }
}
