package com.gxwebsoft.common.system.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gxwebsoft.common.core.config.ConfigProperties;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.security.JwtSubject;
import com.gxwebsoft.common.core.security.JwtUtil;
import com.gxwebsoft.common.core.utils.CommonUtil;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.common.core.utils.WxMiniProgramDecryptUtil;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.*;
import com.gxwebsoft.common.system.param.LoginParam;
import com.gxwebsoft.common.system.param.UserParam;
import com.gxwebsoft.common.system.result.LoginResult;
import com.gxwebsoft.common.system.service.*;
import com.gxwebsoft.websopy.service.AppConfigService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_WEIXIN;
import static com.gxwebsoft.common.core.constants.RedisConstants.ACCESS_TOKEN_KEY;

@RestController
@RequestMapping("/api/wx-login")
@Tag(name = "微信小程序登录API")
public class WxLoginController extends BaseController {
    private final StringRedisTemplate redisTemplate;
    @Resource
    private SettingService settingService;
    @Resource
    private UserService userService;
    @Resource
    private ConfigProperties configProperties;
    @Resource
    private UserRoleService userRoleService;
    @Resource
    private LoginRecordService loginRecordService;
    @Resource
    private RoleService roleService;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private ConfigProperties config;
    @Resource
    private UserRefereeService userRefereeService;
    @Resource
    private UserSyncService userSyncService;
    @Resource
    private AppConfigService appConfigService;

    public WxLoginController(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Operation(summary = "获取微信AccessToken")
    @Transactional(rollbackFor = {Exception.class})
    @PostMapping("/getAccessToken")
    public ApiResult<String> getMpAccessToken() {
        return success("操作成功", getAccessToken());
    }

    @Operation(summary = "获取微信AccessToken（支持指定租户ID）")
    @Transactional(rollbackFor = {Exception.class})
    @PostMapping("/getAccessToken/{tenantId}")
    public ApiResult<String> getMpAccessToken(@PathVariable("tenantId") Integer tenantId) {
        return success("操作成功", getAccessToken(tenantId));
    }

    @Operation(summary = "清理并统一AccessToken存储格式")
    @PostMapping("/cleanAccessTokenFormat")
    public ApiResult<String> cleanAccessTokenFormat() {
        String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString());
        String value = redisTemplate.opsForValue().get(key);

        if (value != null) {
            try {
                // 尝试解析为JSON
                JSONObject response = JSON.parseObject(value);
                String accessToken = response.getString("access_token");
                if (StrUtil.isNotBlank(accessToken)) {
                    return success("AccessToken格式已经是标准JSON格式", accessToken);
                }
            } catch (Exception e) {
                // 如果是字符串格式，转换为JSON格式
                if (StrUtil.isNotBlank(value) && !value.startsWith("{")) {
                    JSONObject tokenData = new JSONObject();
                    tokenData.put("access_token", value);
                    tokenData.put("expires_in", 7200); // 默认2小时

                    // 重新存储为JSON格式
                    redisTemplate.opsForValue().set(key, tokenData.toJSONString(), 7000L, TimeUnit.SECONDS);
                    return success("已将字符串格式转换为JSON格式", value);
                }
            }
        }

        // 如果没有缓存或格式异常，重新获取
        redisTemplate.delete(key);
        String newToken = getAccessToken();
        return success("已重新获取并统一格式", newToken);
    }

    @Operation(summary = "获取微信openId")
    @Transactional(rollbackFor = {Exception.class})
    @PostMapping("/getOpenId")
    public ApiResult<LoginResult> getOpenId(@RequestBody UserParam userParam, HttpServletRequest request) {
        // 1.获取openid
        JSONObject result = getOpenIdByCode(userParam);
        String openid = result.getString("openid");
        String unionid = result.getString("unionid");
        if (openid == null) {
            return fail("获取openid失败", null);
        }
        // 关键：把 openid/unionid 设置到 userParam，否则后续查库/注册都拿不到 openid
        userParam.setOpenid(openid);
        if (StrUtil.isNotBlank(unionid)) {
            userParam.setUnionid(unionid);
        }
        // 2.通过openid查询用户是否已存在
        User user = userService.getByOauthId(userParam);
        // 3.存在则签发token并返回登录成功,不存在则注册新用户
        if (user == null) {
            user = addUser(userParam);
        } else if (StrUtil.isBlank(user.getOpenid())) {
            // 老用户（比如手机号注册）没有 openid，补上
            user.setOpenid(openid);
            if (StrUtil.isNotBlank(unionid)) {
                user.setUnionid(unionid);
            }
            userService.updateById(user);
        }
        // 4.签发token
        loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
        String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
                configProperties.getTokenExpireTime(), configProperties.getTokenKey());
        return success("登录成功", new LoginResult(access_token, user));
    }

    @Operation(summary = "微信授权手机号码并登录")
    @Transactional(rollbackFor = {Exception.class})
    @PostMapping("/loginByMpWxPhone")
    public ApiResult<LoginResult> loginByMpWxPhone(@RequestBody UserParam userParam, HttpServletRequest request) {
        // 获取手机号码
        String phone = getPhoneByCode(userParam);
        if (phone == null) {
            String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString());
            redisTemplate.delete(key);
            throw new BusinessException("授权失败，请重试");
        }

        User user = null;

        // 超级管理员验证
        if (userParam.getIsSuperAdmin() != null) {
            final LoginParam loginParam = new LoginParam();
            loginParam.setIsAdmin(true);
            loginParam.setPhone(phone);
            final List<User> adminsByPhone = userService.getAdminsByPhone(loginParam);
            if (!CollectionUtils.isEmpty(adminsByPhone)) {
                user = adminsByPhone.get(0);
            }
        } else {
            // 先查询管理员用户
            user = userService.getAdminByPhone(userParam);

            // 如果不是管理员，再查询普通用户
            if (user == null) {
                user = userService.getByPhone(phone, getTenantId());
            }
        }

        // 如果用户存在，直接登录
        if (user != null) {
            System.out.println("用户已存在，直接登录: " + user.getPhone());

            // 检查绑定上级关系
            if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) {
                UserReferee check = userRefereeService.check(user.getUserId(), userParam.getRefereeId());
                if (check == null) {
                    UserReferee userReferee = new UserReferee();
                    userReferee.setDealerId(userParam.getRefereeId());
                    userReferee.setUserId(user.getUserId());
                    userRefereeService.save(userReferee);
                }
            }

            // 签发token
            String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
                    configProperties.getTokenExpireTime(), configProperties.getTokenKey());
            loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
            if (getTenantId() != null && getTenantId().equals(10198)) activateShopUserMemberByPhone(phone);

            return success("登录成功", new LoginResult(access_token, user));
        }

        // 用户不存在，注册新用户
        System.out.println("用户不存在，注册新用户: " + phone);

        try {
            // 确保获取openid - 优先使用已有的openid，否则通过code获取
            if (StrUtil.isBlank(userParam.getOpenid())) {
                String codeToUse = null;
                // 优先使用authCode，如果没有则使用code
                if (StrUtil.isNotBlank(userParam.getAuthCode())) {
                    codeToUse = userParam.getAuthCode();
                } else if (StrUtil.isNotBlank(userParam.getCode())) {
                    codeToUse = userParam.getCode();
                }

                if (StrUtil.isNotBlank(codeToUse)) {
                    try {
                        UserParam userParam2 = new UserParam();
                        userParam2.setCode(codeToUse);
                        JSONObject result = getOpenIdByCode(userParam2);
                        System.out.println("获取openid结果: " + result);

                        if (result != null) {
                            String openid = result.getString("openid");
                            String unionid = result.getString("unionid");
                            if (StrUtil.isNotBlank(openid)) {
                                userParam.setOpenid(openid);
                                System.out.println("成功获取openid: " + openid);
                            }
                            if (StrUtil.isNotBlank(unionid)) {
                                userParam.setUnionid(unionid);
                                System.out.println("成功获取unionid: " + unionid);
                            }
                        }
                    } catch (Exception e) {
                        System.err.println("获取openid失败，但继续注册流程: " + e.getMessage());
                        // 不抛出异常，允许没有openid的情况下继续注册
                    }
                } else {
                    System.out.println("警告：没有提供code或authCode，无法获取openid");
                }
            } else {
                System.out.println("使用已有的openid: " + userParam.getOpenid());
            }

            userParam.setPhone(phone);
            user = addUser(userParam);
            user.setRecommend(1);

            // 签发token
            String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
                    configProperties.getTokenExpireTime(), configProperties.getTokenKey());
            loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REGISTER, null, user.getTenantId(), request);
            if (getTenantId() != null && getTenantId().equals(10198)) activateShopUserMemberByPhone(phone);
            return success("注册并登录成功", new LoginResult(access_token, user));

        } catch (BusinessException e) {
            // 如果注册时提示手机号已存在，说明存在并发情况，重新查询用户并登录
            if (e.getMessage().contains("手机号已存在")) {
                System.out.println("注册时发现手机号已存在，重新查询用户进行登录");
                user = userService.getByPhone(phone, getTenantId());
                if (user != null) {
                    String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
                            configProperties.getTokenExpireTime(), configProperties.getTokenKey());
                    loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
                    activateShopUserMemberByPhone(phone);
                    return success("登录成功", new LoginResult(access_token, user));
                }
            }
            throw e;
        }
    }

    @Operation(summary = "微信授权手机号码并登录（支持指定租户ID）")
    @Transactional(rollbackFor = {Exception.class})
    @PostMapping("/loginByMpWxPhone/{tenantId}")
    public ApiResult<LoginResult> loginByMpWxPhoneWithTenant(@RequestBody UserParam userParam, HttpServletRequest request, @PathVariable("tenantId") Integer tenantId) {
        System.out.println("接收到的参数: code=" + userParam.getCode() + ", authCode=" + userParam.getAuthCode() + ", openid=" + userParam.getOpenid() + ", tenantId=" + tenantId);

        // 获取手机号码（使用指定的tenantId）
        String phone = getPhoneByCode(userParam, tenantId);
        if (phone == null) {
            String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString());
            redisTemplate.delete(key);
            throw new BusinessException("授权失败，请重试");
        }

        User user = null;

        // 超级管理员验证
        if (userParam.getIsSuperAdmin() != null) {
            final LoginParam loginParam = new LoginParam();
            loginParam.setIsAdmin(true);
            loginParam.setPhone(phone);
            final List<User> adminsByPhone = userService.getAdminsByPhone(loginParam);
            if (!CollectionUtils.isEmpty(adminsByPhone)) {
                user = adminsByPhone.get(0);
            }
        } else {
            // 先查询管理员用户
            user = userService.getAdminByPhone(userParam);

            // 如果不是管理员，再查询普通用户（使用指定的tenantId）
            if (user == null) {
                user = userService.getByPhone(phone, tenantId);
            }
        }

        // 如果用户存在，直接登录
        if (user != null) {
            System.out.println("用户已存在，直接登录: " + user.getPhone());

            // 检查绑定上级关系
            if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) {
                UserReferee check = userRefereeService.check(user.getUserId(), userParam.getRefereeId());
                if (check == null) {
                    UserReferee userReferee = new UserReferee();
                    userReferee.setDealerId(userParam.getRefereeId());
                    userReferee.setUserId(user.getUserId());
                    userRefereeService.save(userReferee);
                }
            }

            // 签发token
            String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
                    configProperties.getTokenExpireTime(), configProperties.getTokenKey());
            loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
            activateShopUserMemberByPhone(phone);

            return success("登录成功", new LoginResult(access_token, user));
        }

        // 用户不存在，注册新用户
        System.out.println("用户不存在，注册新用户: " + phone);

        try {
            // 确保获取openid - 优先使用已有的openid，否则通过code获取
            if (StrUtil.isBlank(userParam.getOpenid())) {
                String codeToUse = null;
                // 优先使用authCode，如果没有则使用code
                if (StrUtil.isNotBlank(userParam.getAuthCode())) {
                    codeToUse = userParam.getAuthCode();
                } else if (StrUtil.isNotBlank(userParam.getCode())) {
                    codeToUse = userParam.getCode();
                }

                if (StrUtil.isNotBlank(codeToUse)) {
                    try {
                        System.out.println("尝试使用code获取openid: " + codeToUse);
                        UserParam userParam2 = new UserParam();
                        userParam2.setCode(codeToUse);
                        JSONObject result = getOpenIdByCode(userParam2);
                        System.out.println("获取openid结果: " + result);

                        if (result != null) {
                            // 检查微信接口是否返回错误
                            Object errcode = result.get("errcode");
                            if (errcode != null && !errcode.equals(0)) {
                                String errmsg = result.getString("errmsg");
                                System.err.println("微信获取openid失败: errcode=" + errcode + ", errmsg=" + errmsg);

                                // 如果是code无效错误，提示前端重新获取
                                if (errcode.equals(40029)) {
                                    System.err.println("授权码已过期或无效，建议前端重新调用wx.login()获取新的code");
                                }
                            } else {
                                // 成功获取openid
                                String openid = result.getString("openid");
                                String unionid = result.getString("unionid");
                                if (StrUtil.isNotBlank(openid)) {
                                    userParam.setOpenid(openid);
                                    System.out.println("成功获取openid: " + openid);
                                }
                                if (StrUtil.isNotBlank(unionid)) {
                                    userParam.setUnionid(unionid);
                                    System.out.println("成功获取unionid: " + unionid);
                                }
                            }
                        }
                    } catch (Exception e) {
                        System.err.println("获取openid异常，但继续注册流程: " + e.getMessage());
                        e.printStackTrace();
                        // 不抛出异常，允许没有openid的情况下继续注册
                    }
                } else {
                    System.out.println("警告：没有提供code或authCode，无法获取openid，将创建没有openid的用户");
                }
            } else {
                System.out.println("使用已有的openid: " + userParam.getOpenid());
            }

            userParam.setPhone(phone);
            // 设置租户ID
            userParam.setTenantId(tenantId);
            user = addUserWithTenant(userParam, tenantId);
            user.setRecommend(1);

            // 签发token
            String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
                    configProperties.getTokenExpireTime(), configProperties.getTokenKey());
            loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REGISTER, null, user.getTenantId(), request);
            activateShopUserMemberByPhone(phone);

            return success("注册并登录成功", new LoginResult(access_token, user));

        } catch (BusinessException e) {
            // 如果注册时提示手机号已存在，说明存在并发情况，重新查询用户并登录
            if (e.getMessage().contains("手机号已存在")) {
                System.out.println("注册时发现手机号已存在，重新查询用户进行登录");
                user = userService.getByPhone(phone, tenantId);
                if (user != null) {
                    String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
                            configProperties.getTokenExpireTime(), configProperties.getTokenKey());
                    loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
                    activateShopUserMemberByPhone(phone);
                    return success("登录成功", new LoginResult(access_token, user));
                }
            }
            throw e;
        }
    }

    private void activateShopUserMemberByPhone(String phone) {
        if (StrUtil.isBlank(phone)) {
            return;
        }
        try {
            String apiUrl = "https://paopao-api.websoft.top/api/shop/shop-user-member/activate-by-phone";
            String normalizedPhone = phone.trim();
            JSONObject body = new JSONObject();
            body.put("phone", normalizedPhone);
            String response = HttpRequest.post(apiUrl)
                    .header("Content-Type", "application/json")
                    .body(body.toJSONString())
                    .execute()
                    .body();
            System.out.println("激活会员记录响应: " + response);
        } catch (Exception e) {
            System.err.println("激活会员记录失败，但不影响登录流程: " + e.getMessage());
        }
    }

    @Operation(summary = "微信授权手机号码并更新")
    @Transactional(rollbackFor = {Exception.class})
    @PostMapping("/updatePhoneByMpWx")
    public ApiResult<?> updatePhoneByMpWx(@RequestBody UserParam userParam) {
        // 获取微信授权手机号
        String phone = getPhoneByCode(userParam);
        // 查询当前用户
        User user = userService.getById(userParam.getUserId());
        if (user != null && phone != null) {
            user.setPhone(phone);
            userService.updateUser(user);
            return success("更新成功", phone);
        }
        return fail("更新失败");
    }

    /**
     * 优先读取 sys_setting (mp-weixin)，不存在或异常时回退到 db_websopy.app_config（category=wechat）
     *
     * 使用 bound_tenant_id 字段精确匹配当前请求租户绑定的小程序配置
     *
     * @param tenantId 租户ID（传 null 时使用当前请求租户）
     * @return JSONObject 配置内容（含 appId / appSecret 等字段）
     */
    private JSONObject getMpWxSetting(Integer tenantId) {
        Integer tid = tenantId != null ? tenantId : getTenantId();
        System.out.println("[WxLoginController] getMpWxSetting 开始, 传入tenantId=" + tenantId + ", 实际使用tid=" + tid);
        try {
            // 优先：sys_setting.mp-weixin（按当前租户查询）
            JSONObject setting = settingService.getBySettingKey("mp-weixin");
            if (setting != null && !setting.isEmpty()) {
                System.out.println("[WxLoginController] 从 sys_setting 读取到配置, appId=" + setting.getString("appId"));
                return setting;
            }
            System.out.println("[WxLoginController] sys_setting 无配置，准备回退到 app_config, boundTenantId=" + tid);
        } catch (Exception e) {
            System.err.println("[WxLoginController] 读取 sys_setting 失败，回退 app_config: " + e.getMessage());
            e.printStackTrace();
        }
        // 兜底：按 bound_tenant_id 精确查询 db_websopy.app_config
        System.out.println("[WxLoginController] 调用 appConfigService.getByCategory(\"wechat\", " + tid + ")");
        JSONObject result = appConfigService.getByCategory("wechat", tid);
        System.out.println("[WxLoginController] app_config 返回结果: " + result);
        return result;
    }

    /**
     * 新用户注册
     */
    private User addUser(UserParam userParam) {
        return addUserWithTenant(userParam, getTenantId());
    }

    /**
     * 新用户注册（支持指定租户ID）
     */
    private User addUserWithTenant(UserParam userParam, Integer tenantId) {
        User addUser = new User();
        // 注册用户
        addUser.setStatus(0);
        // 默认审核通过；前端传 auditStatus=0 时走审核流程，传其他值则按前端值
        addUser.setAuditStatus(userParam.getAuditStatus() != null ? userParam.getAuditStatus() : 1);
        addUser.setUsername(createUsername("wx_"));
        addUser.setNickname("微信用户");
        addUser.setPlatform(MP_WEIXIN);
        addUser.setGradeId(2);
        if (userParam.getGradeId() != null) {
            addUser.setGradeId(userParam.getGradeId());
        }
        if (userParam.getPhone() != null) {
            addUser.setPhone(userParam.getPhone());
        }
        if (StrUtil.isNotBlank(userParam.getOpenid())) {
            addUser.setOpenid(userParam.getOpenid());
        }
        if (StrUtil.isNotBlank(userParam.getUnionid())) {
            addUser.setUnionid(userParam.getUnionid());
        }
        addUser.setPassword(userService.encodePassword(CommonUtil.randomUUID16()));
        addUser.setTenantId(tenantId != null ? tenantId : getTenantId());
        addUser.setRecommend(1);
        Role role = roleService.getOne(new QueryWrapper<Role>().eq("role_code", "user"), false);
        addUser.setRoleId(role.getRoleId());
        if (userService.saveUser(addUser)) {
            // 添加用户角色
            final UserRole userRole = new UserRole();
            userRole.setUserId(addUser.getUserId());
            userRole.setTenantId(addUser.getTenantId());
            userRole.setRoleId(addUser.getRoleId());
            userRoleService.save(userRole);
            // 同步到 websopy
            userSyncService.syncUserToWebsopy(addUser);
        }
        // 绑定关系
        if (userParam.getSceneType() != null && userParam.getSceneType().equals("save_referee") && userParam.getRefereeId() != null && userParam.getRefereeId() != 0) {
            UserReferee check = userRefereeService.check(addUser.getUserId(), userParam.getRefereeId());
            if (check == null) {
                UserReferee userReferee = new UserReferee();
                userReferee.setDealerId(userParam.getRefereeId());
                userReferee.setUserId(addUser.getUserId());
                userRefereeService.save(userReferee);
            }
        }
        return addUser;
    }

    // 获取openid
    private JSONObject getOpenIdByCode(UserParam userParam) {
        try {
            // 获取微信小程序配置信息（优先 app_config，回退 sys_setting）
            JSONObject setting = getMpWxSetting(null);
            // 获取openId
            String apiUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + setting.getString("appId") + "&secret=" + setting.getString("appSecret") + "&js_code=" + userParam.getCode() + "&grant_type=authorization_code";
            // 执行get请求
            String result = HttpUtil.get(apiUrl);

            // 验证响应内容
            if (StrUtil.isBlank(result)) {
                throw new BusinessException("微信接口响应为空");
            }

            // 清理响应中的控制字符
            String cleanResult = result.trim().replaceAll("[\u0000-\u001f]", "");
            System.out.println("获取openId响应: " + cleanResult);

            // 解析响应
            return JSON.parseObject(cleanResult);
        } catch (Exception e) {
            System.err.println("获取openId异常: " + e.getMessage());
            e.printStackTrace();
            throw new BusinessException("获取openId失败，请重试");
        }
    }

    /**
     * 获取微信手机号码
     * 支持两种方式：
     * 1. 新版API方式：使用code直接获取手机号
     * 2. 旧版解密方式：使用encryptedData、iv、sessionKey解密获取手机号
     *
     * @param userParam 需要传微信凭证code或encryptedData等参数
     */
    private String getPhoneByCode(UserParam userParam) {
        return getPhoneByCode(userParam, null);
    }

    /**
     * 获取微信手机号码（支持指定租户ID）
     * 支持两种方式：
     * 1. 新版API方式：使用code直接获取手机号
     * 2. 旧版解密方式：使用encryptedData、iv、sessionKey解密获取手机号
     *
     * @param userParam 需要传微信凭证code或encryptedData等参数
     * @param tenantId 租户ID，为null时使用当前租户ID
     */
    private String getPhoneByCode(UserParam userParam, Integer tenantId) {
        // 方式1：如果有encryptedData，使用解密方式获取手机号
        if (StrUtil.isNotBlank(userParam.getEncryptedData()) &&
            StrUtil.isNotBlank(userParam.getIv()) &&
            StrUtil.isNotBlank(userParam.getSessionKey())) {
            try {
                return WxMiniProgramDecryptUtil.decryptPhoneNumber(
                    userParam.getEncryptedData(),
                    userParam.getSessionKey(),
                    userParam.getIv()
                );
            } catch (Exception e) {
                System.err.println("解密手机号失败: " + e.getMessage());
                // 解密失败，继续尝试新版API方式
            }
        }

        // 方式2：使用新版API方式获取手机号
        if (StrUtil.isNotBlank(userParam.getCode())) {
            try {
                String apiUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(tenantId);
                HashMap<String, Object> paramMap = new HashMap<>();
                paramMap.put("code", userParam.getCode());
                // 执行post请求
                String post = HttpUtil.post(apiUrl, JSON.toJSONString(paramMap));

                // 增加响应内容验证和清理
                if (StrUtil.isBlank(post)) {
                    throw new BusinessException("微信接口响应为空");
                }

                // 更全面地清理响应中的特殊字符
                String cleanResponse = post.trim()
                    .replaceAll("[\u0000-\u001f\u007f-\u009f]", "") // 清理控制字符
                    .replaceAll("\\p{Cntrl}", "") // 清理所有控制字符
                    .replaceAll("[\\x00-\\x1f\\x7f]", ""); // 清理ASCII控制字符

                System.out.println("微信获取手机号原始响应: " + post);
                System.out.println("微信获取手机号清理后响应: " + cleanResponse);

                // 验证清理后的响应是否为有效JSON
                if (!cleanResponse.startsWith("{") || !cleanResponse.endsWith("}")) {
                    System.err.println("响应不是有效的JSON格式: " + cleanResponse);
                    throw new BusinessException("微信接口响应格式异常");
                }

                JSONObject json;
                try {
                    json = JSON.parseObject(cleanResponse);
                } catch (Exception parseException) {
                    System.err.println("JSON解析失败: " + parseException.getMessage());
                    System.err.println("原始响应: " + post);
                    System.err.println("清理后响应: " + cleanResponse);
                    throw new BusinessException("微信接口响应解析失败");
                }

                // 检查微信接口是否返回错误
                Object errcode = json.get("errcode");
                if (errcode != null && errcode.equals(0)) {
                    JSONObject phoneInfo = JSON.parseObject(json.getString("phone_info"));
                    // 微信用户的手机号码
                    final String phoneNumber = phoneInfo.getString("phoneNumber");
                    return phoneNumber;
                } else {
                    String errorMsg = json.getString("errmsg");
                    Integer errCodeInt = null;
                    if (errcode instanceof Integer) {
                        errCodeInt = (Integer) errcode;
                    } else if (errcode instanceof Long) {
                        errCodeInt = ((Long) errcode).intValue();
                    } else if (errcode instanceof String) {
                        try {
                            errCodeInt = Integer.parseInt((String) errcode);
                        } catch (NumberFormatException ignored) {}
                    }

                    System.err.println("微信获取手机号失败: errcode=" + errcode + ", errmsg=" + errorMsg);

                    // 判断是否是 token 相关错误，如果是则清理缓存
                    if (isTokenRelatedError(errCodeInt, errorMsg)) {
                        String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId != null ? tenantId.toString() : getTenantId().toString());
                        redisTemplate.delete(key);
                        System.err.println("已清理access_token缓存，key=" + key);
                    }

                    throw new BusinessException("获取手机号失败：" + errorMsg);
                }
            } catch (BusinessException be) {
                // 重新抛出业务异常
                throw be;
            } catch (Exception e) {
                System.err.println("获取微信手机号异常: " + e.getMessage());
                e.printStackTrace();
                throw new BusinessException("获取手机号失败，请重试: " + e.getMessage());
            }
        }

        throw new BusinessException("获取手机号失败，请检查参数");
    }

    /**
     * 判断是否是 token 相关的错误码，需要清理缓存
     */
    private boolean isTokenRelatedError(Integer errCode, String errMsg) {
        if (errCode == null) {
            return false;
        }
        // token 相关错误码
        return errCode == 40001    // AppSecret错误
            || errCode == 40013    // appid无效
            || errCode == 40125    // appsecret无效
            || errCode == 42001    // access_token超时
            || errCode == 42002    // refresh_token超时
            || errCode == 42003    // code超时
            || errCode == 41002    // appid不正确
            || errCode == 41008    // 缺少access_token参数
            || errCode == 40014;   // 不合法的access_token
    }

    /**
     * 生成随机账号
     *
     * @return username
     */
    private String createUsername(String type) {
        return type.concat(RandomUtil.randomString(12));
    }

    /**
     * 获取接口调用凭据AccessToken
     * <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">...</a>
     */
    private String getAccessToken() {
        return getAccessToken(getTenantId());
    }

    /**
     * 获取接口调用凭据AccessToken（支持指定租户ID）
     * <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">...</a>
     *
     * @param tenantId 租户ID
     * @return access_token
     */

    private String getAccessToken(Integer tenantId) {
        if (tenantId == null) {
            tenantId = getTenantId();
        }
        String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString());
        // 获取微信小程序配置信息（优先 app_config，回退 sys_setting）
        JSONObject setting = getMpWxSetting(tenantId);
        if (setting == null) {
            throw new BusinessException("请先配置小程序");
        }
        // 从缓存获取access_token
        String value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            try {
                // 尝试解析为JSON格式
                JSONObject response = JSON.parseObject(value);
                String accessToken = response.getString("access_token");
                if (StrUtil.isNotBlank(accessToken)) {
                    System.out.println("从缓存获取access_token(JSON格式): " + accessToken);
                    return accessToken;
                }
            } catch (Exception e) {
                // 如果JSON解析失败，可能是旧的字符串格式，直接使用
                if (StrUtil.isNotBlank(value) && !value.startsWith("{")) {
                    System.out.println("从缓存获取access_token(字符串格式): " + value);
                    System.out.println("检测到旧格式的access_token，将在下次更新时统一为JSON格式");
                    return value;
                }
                System.err.println("解析缓存的access_token失败: " + e.getMessage());
                // 缓存数据异常，删除缓存，重新获取
                redisTemplate.delete(key);
            }
        }
        // 微信获取凭证接口
        String apiUrl = "https://api.weixin.qq.com/cgi-bin/token";
        // 组装url参数
        String url = apiUrl.concat("?grant_type=client_credential").concat("&appid=").concat(setting.getString("appId")).concat("&secret=").concat(setting.getString("appSecret"));

        System.out.println("请求微信access_token接口: " + url);

        // 执行get请求
        String result = HttpUtil.get(url);
        System.out.println("微信access_token原始响应: " + result);

        try {
            // 验证响应内容
            if (StrUtil.isBlank(result)) {
                throw new BusinessException("微信接口响应为空");
            }

            // 更全面地清理响应中的特殊字符
            String cleanResult = result.trim()
                .replaceAll("[\u0000-\u001f\u007f-\u009f]", "") // 清理控制字符
                .replaceAll("\\p{Cntrl}", "") // 清理所有控制字符
                .replaceAll("[\\x00-\\x1f\\x7f]", ""); // 清理ASCII控制字符

            System.out.println("微信access_token清理后响应: " + cleanResult);

            // 验证清理后的响应是否为有效JSON
            if (!cleanResult.startsWith("{") || !cleanResult.endsWith("}")) {
                System.err.println("响应不是有效的JSON格式: " + cleanResult);
                throw new BusinessException("微信接口响应格式异常");
            }

            // 解析access_token
            JSONObject response;
            try {
                response = JSON.parseObject(cleanResult);
            } catch (Exception parseException) {
                System.err.println("JSON解析失败: " + parseException.getMessage());
                System.err.println("原始响应: " + result);
                System.err.println("清理后响应: " + cleanResult);
                throw new BusinessException("微信接口响应解析失败");
            }

            // 检查是否有错误码
            Object errcode = response.get("errcode");
            if (errcode != null && !errcode.equals(0)) {
                String errorMsg = response.getString("errmsg");
                System.err.println("获取access_token失败: errcode=" + errcode + ", errmsg=" + errorMsg);
                throw new BusinessException("获取access_token失败：" + errorMsg);
            }

            String accessToken = response.getString("access_token");
            if (StrUtil.isNotBlank(accessToken)) {
                // 构造标准的JSON格式存储对象
                JSONObject tokenData = new JSONObject();
                tokenData.put("access_token", accessToken);
                tokenData.put("expires_in", response.get("expires_in"));

                // 存入缓存，设置过期时间为7000秒（约2小时，微信access_token有效期为2小时）
                // 统一使用JSON格式存储，确保格式一致性
                redisTemplate.opsForValue().set(key, tokenData.toJSONString(), 7000L, TimeUnit.SECONDS);
                System.out.println("获取新的access_token成功: " + accessToken);
                System.out.println("已统一存储为JSON格式: " + tokenData.toJSONString());
                return accessToken;
            } else {
                System.err.println("响应中没有access_token字段: " + cleanResult);
                throw new BusinessException("获取access_token失败：响应格式异常");
            }
        } catch (BusinessException be) {
            // 重新抛出业务异常
            throw be;
        } catch (Exception e) {
            System.err.println("解析access_token异常: " + e.getMessage());
            e.printStackTrace();
            throw new BusinessException("小程序配置不正确或网络异常: " + e.getMessage());
        }
    }

    @Operation(summary = "获取微信openId并更新")
    @PostMapping("/getWxOpenId")
    public ApiResult<?> getWxOpenId(@RequestBody UserParam userParam) {
        final User loginUser = getLoginUser();
        if (loginUser == null) {
            return fail("请先登录");
        }
        // 已存在直接返回
        if (StrUtil.isNotBlank(loginUser.getOpenid())) {
            return success(loginUser);
        }
        // 请求微信接口获取openid
        String apiUrl = "https://api.weixin.qq.com/sns/jscode2session";
        final HashMap<String, Object> map = new HashMap<>();
        final JSONObject setting = getMpWxSetting(null);
        final String appId = setting.getString("appId");
        final String appSecret = setting.getString("appSecret");
        map.put("appid", appId);
        map.put("secret", appSecret);
        map.put("js_code", userParam.getCode());
        map.put("grant_type", "authorization_code");
        final String response = HttpUtil.get(apiUrl, map);
        final JSONObject jsonObject = JSONObject.parseObject(response);
        String openid = jsonObject.getString("openid");
        String sessionKey = jsonObject.getString("session_key");
        String unionid = jsonObject.getString("unionid");
        // 保存openID
        if (loginUser.getOpenid() == null || StrUtil.isBlank(loginUser.getOpenid())) {
            loginUser.setOpenid(openid);
            loginUser.setUnionid(unionid);
            userService.updateById(loginUser);
        }
        return success("获取成功", jsonObject);
    }

    @Operation(summary = "仅获取微信openId")
    @PostMapping("/getWxOpenIdOnly")
    public ApiResult<?> getWxOpenIdOnly(@RequestBody UserParam userParam) {

        String apiUrl = "https://api.weixin.qq.com/sns/jscode2session";
        final HashMap<String, Object> map = new HashMap<>();
        final JSONObject setting = getMpWxSetting(null);
        final String appId = setting.getString("appId");
        final String appSecret = setting.getString("appSecret");
        map.put("appid", appId);
        map.put("secret", appSecret);
        map.put("js_code", userParam.getCode());
        map.put("grant_type", "authorization_code");
        final String response = HttpUtil.get(apiUrl, map);
        final JSONObject jsonObject = JSONObject.parseObject(response);
        return success("获取成功", jsonObject);
    }

    @Operation(summary = "获取微信小程序码-用户ID")
    @GetMapping("/getUserQRCode")
    public ApiResult<?> getQRCode() {
        String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken();
        final HashMap<String, Object> map = new HashMap<>();
        map.put("path", "/package/user/qrcode?user_id=" + getLoginUserId());
//    map.put("env_version","trial");
        // 获取图片 Buffer
        byte[] qrCode = HttpRequest.post(apiUrl)
                .body(JSON.toJSONString(map))
                .execute().bodyBytes();

        // 保存的文件名称
        final String fileName = CommonUtil.randomUUID8().concat(".png");
        // 保存路径
        String filePath = getUploadDir().concat("qrcode/") + fileName;
        File file = FileUtil.writeBytes(qrCode, filePath);
        if (file != null) {
            return success(config.getFileServer().concat("/qrcode/").concat(fileName));
        }
        return fail("获取失败", null);
    }

    @Operation(summary = "获取微信小程序码-订单核销码")
    @GetMapping("/getOrderQRCode/{orderNo}")
    public ApiResult<?> getOrderQRCode(@PathVariable("orderNo") String orderNo) {
        String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken();
        final HashMap<String, Object> map = new HashMap<>();
        map.put("path", "/package/admin/order-scan?orderNo=".concat(orderNo));
        map.put("env_version", "trial");
        // 获取图片 Buffer
        byte[] qrCode = HttpRequest.post(apiUrl)
                .body(JSON.toJSONString(map))
                .execute().bodyBytes();

        // 保存的文件名称
        final String fileName = CommonUtil.randomUUID8().concat(".png");
        // 保存路径
        String filePath = getUploadDir().concat("qrcode/") + fileName;
        File file = FileUtil.writeBytes(qrCode, filePath);
        if (file != null) {
            return success(config.getFileServer().concat("/qrcode/").concat(fileName));
        }
        return fail("获取失败", null);
    }

    @Operation(summary = "获取微信小程序码-订单核销码-数量极多的业务场景")
    @GetMapping("/getOrderQRCodeUnlimited/{orderNo}")
    public ApiResult<?> getOrderQRCodeUnlimited(@PathVariable("orderNo") String orderNo) {
      final User loginUser = getLoginUser();
      if(loginUser == null){
        return fail("请先登录");
      }
      String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(loginUser.getTenantId());
        final HashMap<String, Object> map = new HashMap<>();
        map.put("scene", "orderNo=".concat(orderNo));
        map.put("page", "package/admin/order-scan");
        map.put("env_version", "trial");
        // 获取图片 Buffer
        byte[] qrCode = HttpRequest.post(apiUrl)
                .body(JSON.toJSONString(map))
                .execute().bodyBytes();
        System.out.println("qrCode = " + qrCode);

        // 保存的文件名称
        final String fileName = CommonUtil.randomUUID8().concat(".png");
        // 保存路径
        String filePath = getUploadDir().concat("qrcode/") + fileName;
        File file = FileUtil.writeBytes(qrCode, filePath);
        if (file != null) {
            return success(config.getFileServer().concat("/qrcode/").concat(fileName));
        }
        return fail("获取失败", null);
    }


    @Operation(summary = "openid无感登录")
    @PostMapping("/loginByOpenId")
    public ApiResult<?> loginByOpenId(@RequestBody Mp mp, HttpServletRequest request) {
        // 优先读 app_config（category=wechat），回退 sys_setting.mp-weixin
        final JSONObject setting = getMpWxSetting(mp.getTenantId());
        if (setting == null) {
            return fail("小程序未配置");
        }
        final String AppId = setting.getString("appId");
        final String AppSecret = setting.getString("appSecret");
        if (StrUtil.isBlank(AppId) || StrUtil.isBlank(AppSecret)) {
            return fail("小程序配置不完整，请检查 appId 和 appSecret");
        }

        // 请求微信接口获取openid
        try {
            String apiUrl = "https://api.weixin.qq.com/sns/jscode2session";
            final HashMap<String, Object> map = new HashMap<>();
            map.put("appid", AppId);
            map.put("secret", AppSecret);
            map.put("js_code", mp.getCode());
            map.put("grant_type", "authorization_code");
            final String response = HttpUtil.get(apiUrl, map);

            // 验证响应内容
            if (StrUtil.isBlank(response)) {
                return fail("微信接口响应为空");
            }

            // 清理响应中的控制字符
            String cleanResponse = response.trim().replaceAll("[\u0000-\u001f]", "");
            System.out.println("获取openId响应: " + cleanResponse);

            final JSONObject jsonObject = JSONObject.parseObject(cleanResponse);

            // 检查微信接口是否返回错误
            if (jsonObject.containsKey("errcode") && !jsonObject.getInteger("errcode").equals(0)) {
                String errmsg = jsonObject.getString("errmsg");
                System.err.println("微信接口返回错误: errcode=" + jsonObject.get("errcode") + ", errmsg=" + errmsg);
                return fail("微信授权失败：" + errmsg);
            }

            String openid = jsonObject.getString("openid");
            String sessionKey = jsonObject.getString("session_key");
            String unionid = jsonObject.getString("unionid");

            if (StrUtil.isNotBlank(openid)) {
                User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getOpenid, openid).eq(User::getDeleted,0).last("limit 1"));

                // 检查用户是否存在
                if (user == null) {
                    return fail("用户未注册", openid);
                }

                final User userInfo = userService.getByIdRel(user.getUserId());
                if (ObjectUtil.isNotEmpty(userInfo)) {
                      // 签发token
                      String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
                              configProperties.getTokenExpireTime(), configProperties.getTokenKey());
                      loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_REGISTER, null, user.getTenantId(), request);
                      return success("登录成功", new LoginResult(access_token, userInfo));
                  } else {
                      return fail("用户信息获取失败", openid);
                  }
            } else {
                return fail("openId获取失败");
            }
        } catch (Exception e) {
            System.err.println("openId登录异常: " + e.getMessage());
            e.printStackTrace();
            return fail("登录失败，请重试");
        }
    }

    /**
     * 文件上传位置(服务器)
     */
    private String getUploadDir() {
        return config.getUploadPath() + "/";
    }


}
