package cn.novalon.gym.manage.member.service.impl;

import cn.novalon.gym.manage.common.exception.ErrorCode;
import cn.novalon.gym.manage.common.exception.SystemException;
import cn.novalon.gym.manage.member.config.WechatProperties;
import cn.novalon.gym.manage.member.service.WechatApiService;
import cn.novalon.gym.manage.common.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信API服务实现
 *
 * @author 付嘉
 * @date 2026-05-01
 */

@Slf4j
@Service
@RequiredArgsConstructor
public class WechatApiServiceImpl implements WechatApiService {

    private final WechatProperties wechatProperties;
    private final RedisUtil redisUtil;

    private static final String ACCESS_TOKEN_CACHE_PREFIX = "wechat:access_token:";
    private static final long ACCESS_TOKEN_EXPIRE_SECONDS = 7000;  // 比官方过期时间短100秒

    private final WebClient webClient = WebClient.builder()
            .baseUrl("https://api.weixin.qq.com")
            .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024))
            .build();

    @Override
    public Mono<Map<String, String>> jsCode2Session(String code) {
        log.info("微信jsCode2Session API");
        log.info("信息 - AppID: {}, AppSecret {}",
                wechatProperties.getMiniapp().getAppId(),
                wechatProperties.getMiniapp().getAppSecret() != null ?
                    wechatProperties.getMiniapp().getAppSecret().substring(0, Math.min(4, wechatProperties.getMiniapp().getAppSecret().length())) + "***" : "null");
        log.info(" - code: {}", code);

        return webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/sns/jscode2session")
                        .queryParam("appid", wechatProperties.getMiniapp().getAppId())
                        .queryParam("secret", wechatProperties.getMiniapp().getAppSecret())
                        .queryParam("js_code", code)
                        .queryParam("grant_type", "authorization_code")
                        .build())
                .retrieve()
                .bodyToMono(String.class)
                .map(responseBody -> {
                    log.info("微信API响应: {}", responseBody);

                    Map<String, Object> response;
                    try {
                        com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
                        response = mapper.readValue(responseBody, Map.class);
                    } catch (Exception e) {
                        log.error("微信API响应失败", e);
                        throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "微信API响应失败: " + e.getMessage());
                    }

                    Map<String, String> result = new HashMap<>();

                    if (response.containsKey("errcode")) {
                        Integer errcode = (Integer) response.get("errcode");
                        String errmsg = (String) response.get("errmsg");
                        log.error("微信API失败, errcode: {}, errmsg: {}", errcode, errmsg);
                        throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "微信API失败 [" + errcode + "]: " + errmsg);
                    }

                    result.put("session_key", (String) response.get("session_key"));
                    result.put("openid", (String) response.get("openid"));
                    result.put("unionid", (String) response.get("unionid"));

                    log.info("微信API响应成功, openid: {}, unionid: {}",
                            result.get("openid"), result.get("unionid"));
                    return result;
                })
                .onErrorResume(e -> {
                    log.error("微信API响应异常 - URL: https://api.weixin.qq.com/sns/jscode2session");
                    log.error("异常: {}", e.getClass().getName());
                    log.error("异常信息: {}", e.getMessage());
                    if (e.getCause() != null) {
                        log.error("异常原因: {}", e.getCause().getMessage());
                    }
                    if (e instanceof SystemException) {
                        return Mono.error(e);
                    }
                    throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "微信API响应异常 " + e.getMessage());
                });
    }

    @Override
    public Mono<String> getPhoneNumber(String code) {
        log.debug("微信getPhoneNumber API, code: {}", code);

        return getAccessToken("miniapp")
                .flatMap(accessToken -> {

                    Map<String, String> requestBody = new HashMap<>();
                    requestBody.put("code", code);

                    return webClient.post()
                            .uri(uriBuilder -> uriBuilder
                                    .path("/wxa/business/getuserphonenumber")
                                    .queryParam("access_token", accessToken)
                                    .build())
                            .contentType(MediaType.APPLICATION_JSON)
                            .bodyValue(requestBody)
                            .retrieve()
                            .bodyToMono(Map.class)
                            .map(response -> {
                                if (response.containsKey("errcode") &&
                                    (Integer) response.get("errcode") == 0) {

                                    Map<String, Object> phoneInfo =
                                        (Map<String, Object>) response.get("phone_info");
                                    String phoneNumber = (String) phoneInfo.get("purePhoneNumber");

                                    log.info("获取手机号成功{}", phoneNumber);
                                    return phoneNumber;
                                } else {
                                    String errmsg = (String) response.get("errmsg");
                                    log.error("获取手机号失败 {}", errmsg);
                                    throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "获取手机号失败 " + errmsg);
                                }
                            });
                })
                .onErrorResume(e -> {
                    log.error("获取手机号失败", e);
                    if (e instanceof SystemException) {
                        return Mono.error(e);
                    }
                    throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "获取手机号失败 " + e.getMessage());
                });
    }

    @Override
    public Mono<String> getAccessToken(String appType) {
        log.debug("获取access_token, appType: {}", appType);

        String cacheKey = ACCESS_TOKEN_CACHE_PREFIX + appType;

        return redisUtil.get(cacheKey, String.class)
                .flatMap(cachedToken -> {
                    if (cachedToken != null) {
                        log.debug("从缓存获取access_token, appType: {}", appType);
                        return Mono.just(cachedToken);
                    }

                    String appId, appSecret;
                    if ("miniapp".equals(appType)) {
                        appId = wechatProperties.getMiniapp().getAppId();
                        appSecret = wechatProperties.getMiniapp().getAppSecret();
                    } else {
                        appId = wechatProperties.getMp().getAppId();
                        appSecret = wechatProperties.getMp().getAppSecret();
                    }

                    return webClient.get()
                            .uri(uriBuilder -> uriBuilder
                                    .path("/cgi-bin/token")
                                    .queryParam("grant_type", "client_credential")
                                    .queryParam("appid", appId)
                                    .queryParam("secret", appSecret)
                                    .build())
                            .retrieve()
                            .bodyToMono(Map.class)
                            .flatMap(response -> {
                                if (response.containsKey("access_token")) {
                                    String accessToken = (String) response.get("access_token");
                                    Integer expiresIn = (Integer) response.get("expires_in");
                                    log.info("获取access_token成功, expires_in: {}s", expiresIn);
                                    return redisUtil.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
                                            .then(Mono.just(accessToken));
                                } else {
                                    String errmsg = (String) response.get("errmsg");
                                    log.error("获取access_token失败: {}", errmsg);
                                    throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "获取access_token失败: " + errmsg);
                                }
                            });
                });
    }

    @Override
    public boolean checkSignature(String signature, String timestamp, String nonce) {
        log.debug("验证微信消息签名, signature: {}, timestamp: {}, nonce: {}",
                signature, timestamp, nonce);

        try {
            String token = wechatProperties.getMp().getToken();

            String[] arr = new String[]{token, timestamp, nonce};
            Arrays.sort(arr);

            StringBuilder content = new StringBuilder();
            for (String s : arr) {
                content.append(s);
            }

            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.toString().getBytes(StandardCharsets.UTF_8));

            StringBuilder hexString = new StringBuilder();
            for (byte b : digest) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }

            String calculatedSignature = hexString.toString();

            boolean isValid = calculatedSignature.equals(signature);
            log.debug("验证微信消息签名结果: {}", isValid ? "通过" : "失败");

            return isValid;
        } catch (Exception e) {
            log.error("验证微信消息签名异常", e);
            return false;
        }
    }
}
