package shop.alien.dining.util;

import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import shop.alien.dining.config.BaseRedisService;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * 微信小程序工具类
 * 用于调用微信小程序相关接口
 *
 * @author ssk
 * @version 1.0
 * @date 2024/12/4
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WeChatMiniProgramUtil {

    @Value("${wechat.miniprogram.appId}")
    private String appId;

    @Value("${wechat.miniprogram.appSecret}")
    private String appSecret;

    private final BaseRedisService baseRedisService;

    /**
     * access_token 缓存键前缀
     */
    private static final String ACCESS_TOKEN_CACHE_KEY = "wechat:access_token:";
    
    /**
     * access_token 有效期（秒），微信官方为7200秒，这里设置为7000秒，提前刷新
     */
    private static final long ACCESS_TOKEN_EXPIRE_TIME = 7000L;

    /**
     * 微信小程序登录接口地址
     */
    private static final String CODE2SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session";

    /**
     * 微信小程序手机号获取接口地址（新版本）
     */
    private static final String CODE2VERIFYINFO_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";

    /**
     * 通过 code 获取 openid 和 session_key
     *
     * @param code 小程序端通过 wx.login() 获取的 code
     * @return WeChatSessionInfo 包含 openid、session_key、unionid 等信息
     */
    public WeChatSessionInfo code2Session(String code) {
        String url = String.format("%s?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
                CODE2SESSION_URL, appId, appSecret, code);

        HttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);

        try {
            HttpResponse response = httpClient.execute(httpGet);
            String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
            log.info("微信 code2session 响应: {}", responseBody);

            JSONObject jsonObject = JSONObject.parseObject(responseBody);

            // 检查是否有错误
            if (jsonObject.containsKey("errcode")) {
                Integer errcode = jsonObject.getInteger("errcode");
                String errmsg = jsonObject.getString("errmsg");
                
                // 将错误信息保存到返回对象中，方便上层处理
                WeChatSessionInfo errorInfo = new WeChatSessionInfo();
                errorInfo.setErrcode(errcode);
                errorInfo.setErrmsg(errmsg);
                
                // 根据错误码记录详细的错误信息
                String errorDetail = getErrorDetail(errcode, errmsg);
                log.error("微信 code2session 失败: errcode={}, errmsg={}, 详情={}", errcode, errmsg, errorDetail);
                return errorInfo;
            }

            // 解析返回数据
            WeChatSessionInfo sessionInfo = new WeChatSessionInfo();
            sessionInfo.setOpenid(jsonObject.getString("openid"));
            sessionInfo.setSessionKey(jsonObject.getString("session_key"));
            sessionInfo.setUnionid(jsonObject.getString("unionid"));

            return sessionInfo;
        } catch (IOException e) {
            log.error("调用微信 code2session 接口异常", e);
            return null;
        }
    }

    /**
     * 获取微信错误码的详细说明
     */
    private String getErrorDetail(Integer errcode, String errmsg) {
        if (errcode == null) {
            return "未知错误";
        }
        
        switch (errcode) {
            case 40029:
                return "code无效（可能原因：1. code已使用过，每个code只能使用一次；2. code已过期，有效期5分钟；3. code格式错误；4. appId或appSecret配置错误）";
            case 45011:
                return "频率限制，每个用户每分钟最多调用5次";
            case 40226:
                return "高风险等级用户，需要用户进行验证";
            case 40013:
                return "appId无效，请检查Nacos配置中的wechat.miniprogram.appId";
            case 40125:
                return "appSecret无效，请检查Nacos配置中的wechat.miniprogram.appSecret";
            case -1:
                return "系统繁忙，请稍后重试";
            default:
                return String.format("微信接口错误，错误码：%d，错误信息：%s", errcode, errmsg);
        }
    }

    /**
     * 解密微信小程序加密数据（手机号等）
     * 使用 AES-128-CBC 算法，使用 session_key 作为密钥
     *
     * @param encryptedData 加密数据（Base64编码）
     * @param sessionKey    session_key（Base64编码）
     * @param iv           初始向量（Base64编码）
     * @return 解密后的JSON字符串，包含手机号等信息
     */
    public String decryptData(String encryptedData, String sessionKey, String iv) {
        try {
            if (encryptedData == null || encryptedData.isEmpty()) {
                log.warn("加密数据为空，无法解密");
                return null;
            }
            if (sessionKey == null || sessionKey.isEmpty()) {
                log.warn("session_key为空，无法解密");
                return null;
            }
            if (iv == null || iv.isEmpty()) {
                log.warn("初始向量为空，无法解密");
                return null;
            }

            // Base64解码
            byte[] dataByte = Base64.decodeBase64(encryptedData);
            byte[] keyByte = Base64.decodeBase64(sessionKey);
            byte[] ivByte = Base64.decodeBase64(iv);

            // AES-128-CBC 解密
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
            cipher.init(Cipher.DECRYPT_MODE, spec, ivSpec);

            byte[] decrypted = cipher.doFinal(dataByte);
            String result = new String(decrypted, StandardCharsets.UTF_8);
            
            log.info("微信加密数据解密成功");
            return result;
        } catch (Exception e) {
            log.error("微信加密数据解密失败: encryptedData={}, error={}", 
                    encryptedData != null ? encryptedData.substring(0, Math.min(20, encryptedData.length())) : "null", 
                    e.getMessage(), e);
            return null;
        }
    }

    /**
     * 从解密后的数据中提取手机号（旧版本，已废弃）
     *
     * @param decryptedData 解密后的JSON字符串
     * @return 手机号
     */
    @Deprecated
    public String extractPhoneNumber(String decryptedData) {
        try {
            if (decryptedData == null || decryptedData.isEmpty()) {
                return null;
            }
            
            JSONObject jsonObject = JSONObject.parseObject(decryptedData);
            String phoneNumber = jsonObject.getString("phoneNumber");
            
            log.info("从解密数据中提取手机号: {}", phoneNumber != null ? phoneNumber.substring(0, Math.min(7, phoneNumber.length())) + "****" : "null");
            return phoneNumber;
        } catch (Exception e) {
            log.error("提取手机号失败: {}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 通过手机号凭证 code 获取手机号（新版本API）
     * 使用 code2Verifyinfo 接口
     *
     * @param phoneCode 手机号凭证 code（通过wx.getPhoneNumber()获取）
     * @return 手机号，如果获取失败返回null
     */
    public String getPhoneNumberByCode(String phoneCode) {
        if (phoneCode == null || phoneCode.isEmpty()) {
            log.warn("手机号凭证code为空");
            return null;
        }

        try {
            // 获取 access_token
            String accessToken = getAccessToken();
            if (accessToken == null || accessToken.isEmpty()) {
                log.error("获取access_token失败，无法调用手机号接口");
                return null;
            }

            // 构建请求URL
            String url = String.format("%s?access_token=%s", CODE2VERIFYINFO_URL, accessToken);
            
            // 构建请求体
            JSONObject requestBody = new JSONObject();
            requestBody.put("code", phoneCode);

            // 使用HttpClient发送POST请求
            HttpClient httpClient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "application/json");
            httpPost.setEntity(new StringEntity(requestBody.toJSONString(), "UTF-8"));

            HttpResponse response = httpClient.execute(httpPost);
            String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
            log.info("微信 code2Verifyinfo 响应: {}", responseBody);

            JSONObject jsonObject = JSONObject.parseObject(responseBody);

            // 检查是否有错误（errcode 为 0 表示成功，非 0 才视为失败）
            if (jsonObject.containsKey("errcode")) {
                Integer errcode = jsonObject.getInteger("errcode");
                if (errcode != null && errcode != 0) {
                    String errmsg = jsonObject.getString("errmsg");
                    log.error("微信 code2Verifyinfo 失败: errcode={}, errmsg={}", errcode, errmsg);
                    return null;
                }
            }

            // 解析返回数据
            JSONObject phoneInfo = jsonObject.getJSONObject("phone_info");
            if (phoneInfo != null) {
                String phoneNumber = phoneInfo.getString("phoneNumber");
                log.info("成功获取手机号");
                return phoneNumber;
            }

            return null;
        } catch (Exception e) {
            log.error("调用微信 code2Verifyinfo 接口异常", e);
            return null;
        }
    }

    /**
     * 获取微信 access_token（带缓存机制）
     * access_token有效期为7200秒，这里缓存7000秒，提前刷新避免过期
     * 
     * @return access_token，获取失败返回null
     */
    private String getAccessToken() {
        // 1. 先从缓存获取
        String cacheKey = ACCESS_TOKEN_CACHE_KEY + appId;
        String cachedToken = baseRedisService.getString(cacheKey);
        if (cachedToken != null && !cachedToken.isEmpty()) {
            log.debug("从缓存获取access_token成功");
            return cachedToken;
        }

        // 2. 缓存未命中，请求微信接口
        try {
            String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
                    appId, appSecret);
            
            HttpClient httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(url);
            HttpResponse response = httpClient.execute(httpGet);
            String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
            
            log.info("微信获取access_token响应: {}", responseBody);
            
            JSONObject jsonObject = JSONObject.parseObject(responseBody);
            if (jsonObject.containsKey("access_token")) {
                String accessToken = jsonObject.getString("access_token");
                
                // 3. 存入缓存（有效期7000秒，提前刷新）
                baseRedisService.setString(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_TIME);
                log.info("成功获取access_token并存入缓存");
                return accessToken;
            } else {
                Integer errcode = jsonObject.getInteger("errcode");
                String errmsg = jsonObject.getString("errmsg");
                log.error("获取access_token失败: errcode={}, errmsg={}", errcode, errmsg);
                return null;
            }
        } catch (Exception e) {
            log.error("获取access_token异常", e);
            return null;
        }
    }

    /**
     * 微信会话信息
     */
    public static class WeChatSessionInfo {
        private String openid;
        private String sessionKey;
        private String unionid;
        private Integer errcode;
        private String errmsg;

        public String getOpenid() {
            return openid;
        }

        public void setOpenid(String openid) {
            this.openid = openid;
        }

        public String getSessionKey() {
            return sessionKey;
        }

        public void setSessionKey(String sessionKey) {
            this.sessionKey = sessionKey;
        }

        public String getUnionid() {
            return unionid;
        }

        public void setUnionid(String unionid) {
            this.unionid = unionid;
        }

        public Integer getErrcode() {
            return errcode;
        }

        public void setErrcode(Integer errcode) {
            this.errcode = errcode;
        }

        public String getErrmsg() {
            return errmsg;
        }

        public void setErrmsg(String errmsg) {
            this.errmsg = errmsg;
        }
    }
}

