package com.fs.app.controller;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fs.app.annotation.Login;
import com.fs.app.param.*;
import com.fs.app.utils.UniVerifyUtil;
import com.fs.app.utils.WxUtil;
import com.fs.common.VerifyCodeUtil;
import com.fs.common.annotation.RepeatSubmit;
import com.fs.common.core.domain.R;
import com.fs.common.core.redis.RedisCache;
import com.fs.common.exception.CustomException;
import com.fs.common.exception.ServiceException;
import com.fs.common.service.ISmsService;
import com.fs.common.utils.ServletUtils;
import com.fs.common.utils.ip.IpUtils;
import com.fs.common.utils.sign.Md5Utils;
import com.fs.company.domain.CompanyUser;
import com.fs.company.service.ICompanyUserService;
import com.fs.core.config.WxOpenProperties;
import com.fs.course.domain.FsCoursePlaySourceConfig;
import com.fs.course.service.IFsCoursePlaySourceConfigService;
import com.fs.course.utils.FsCoursePlaySourceConfigQuery;
import com.fs.his.domain.FsUser;
import com.fs.his.mapper.FsUserMapper;
import com.fs.his.service.IFsUserCouponService;
import com.fs.his.service.IFsUserNewTaskService;
import com.fs.his.service.IFsUserService;
import com.fs.his.vo.FsUserRegisterParam;
import com.fs.im.service.OpenIMService;
import com.fs.lotus.utils.SignatureUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import static com.fs.his.utils.PhoneUtil.*;

@Api("app登录接口")
@RestController
@RequestMapping(value="/app/app")
@Slf4j
public class AppLoginController extends AppBaseController{
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 原硬编码万能验证码已废弃（存在任意用户登录风险）。
     * 如需内部联调，请走正常短信验证或专用开发环境配置，禁止在生产使用固定验证码。
     */
    private static final String INTERNAL_LOGIN_CODE = "";
    private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$");
    @Autowired
    private IFsUserService userService;
    @Autowired
    private FsUserMapper userMapper;
    @Autowired
    private IFsUserNewTaskService userNewTaskService;
    @Autowired
    private WxOpenProperties openProperties;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private IFsUserCouponService fsUserCouponService;

    @Autowired
    private ISmsService smsService;
    @Autowired
    private IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
    @Autowired
    private OpenIMService openIMService;

    @Autowired
    private UniVerifyUtil uniVerifyUtil;

    @Autowired
    private ICompanyUserService companyUserService;

    @ApiOperation("注册app用户")
    @PostMapping("/register")
    @RepeatSubmit
    public R registerDoctor(@Validated @RequestBody FsUserRegisterParam param){
        FsUser fsUser = findUserByPhone(param.getPhone());

//        if (fsUser == null) {
//            // 尝试使用加密后的手机号查询
//            fsUser = userService.selectFsUserByPhone(encryptPhone(param.getPhone()));
//        }

        if (fsUser != null && StringUtils.isNotEmpty(fsUser.getPassword())) {
            return R.error("此账号已经注册");
        }

        FsUser user = new FsUser();
        if (fsUser != null) {
            // 更新已有用户的密码
            user.setUserId(fsUser.getUserId());
            user.setPassword(Md5Utils.hash(param.getPassword()));
            user.setUpdateTime(new DateTime());
            userService.updateFsUser(user);
            // 注册也视为首次使用 app：补写首次登录时间并发放奖励（如符合条件）
            userNewTaskService.performFirstLoginApp(fsUser.getUserId());
            bindCompanyUserInfoByPhone(fsUser);
            return R.ok("注册成功");
        } else {
            // 创建新用户
            user.setPhone(param.getPhone());
            user.setNickName("app用户" + param.getPhone().substring(param.getPhone().length() - 4));
            user.setStatus(1);
            user.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
            user.setPassword(Md5Utils.hash(param.getPassword()));
            user.setCreateTime(new Date());

            if (userService.insertFsUser(user) > 0) {
                // 注册也视为首次使用 app：补写首次登录时间并发放奖励（如符合条件）
                userNewTaskService.performFirstLoginApp(user.getUserId());
                bindCompanyUserInfoByPhone(user);
                return R.ok("注册成功");
            } else {
                return R.error("注册失败");
            }
        }

    }

    @ApiOperation("登录")
    @PostMapping("/login")
    @Transactional
    public R login(@Validated @RequestBody FsUserLoginParam param) {
        int loginType = param.getLoginType();
        switch (loginType) {
            case 1:
                return handleLoginType1(param);
//            case 3:
//                return handleLoginType3(param);
            case 4:
                return handleLoginType4(param);
            default:
                return R.error("请选择正确的登陆类型！");
        }
    }

    @PostMapping("/registerSendCode")
    public R registerSendCode(@RequestBody Map<String, String> body){
        String phone = body.get("phone");
        String encryptPhone = encryptPhone(phone);
        List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
        if(CollectionUtil.isEmpty(user)){
            user = userService.selectFsUserListByPhone(encryptPhoneOldKey(phone));
        }
        if (!CollectionUtil.isEmpty(user)){
            return R.error("此电话号码已注册");
        }

        // 验证码 key（存验证码，3分钟有效）
        String smsCodeKey = "sms:code:" + phone;
        // 发送冷却 key（限制60秒内不能再次发送）
        String smsCooldownKey = "sms:cooldown:" + phone;

        // 判断是否在60秒冷却时间内
        if (redisCache.getCacheObject(smsCooldownKey) != null) {
            return R.error("验证码已发送，请稍后再试");
        }

        // 生成新的验证码
        String smsCode = VerifyCodeUtil.generateCode();

        // 发送短信
        smsService.sendCaptcha(phone, smsCode, "验证码");

        // 缓存验证码（3分钟有效）
        redisCache.setCacheObject(smsCodeKey, smsCode, 180, TimeUnit.SECONDS);
        // 设置冷却时间（60秒内不能再发）
        redisCache.setCacheObject(smsCooldownKey, "1", 60, TimeUnit.SECONDS);

        return R.ok("验证码已发送");
    }


    @PostMapping("/registerByPhone")
    public R registerByPhone(@RequestBody Map<String,String> map){
        String phone = map.get("phone");
        String code = map.get("code");
        String password = map.get("password");
        String encryptPhone = encryptPhone(phone);
        List<FsUser> users = userService.selectFsUserListByPhone(encryptPhone);
        if (users == null || CollectionUtil.isEmpty(users)){
            String s = encryptPhoneOldKey(phone);
            users = userService.selectFsUserListByPhone(s);
        }
        if (!CollectionUtil.isEmpty(users)){
            return R.error("此账号已经注册");
        }
        String smsCodeKey = "sms:code:" + phone;
        String redisCode = redisCache.getCacheObject(smsCodeKey);
        if (StringUtils.isEmpty(redisCode)){
            return R.error("验证码已过期，请重新发送");
        }
        if (!redisCode.equals(code)) {
            return R.error("验证码错误");
        }
        // 验证成功后立即删除验证码，防止重复使用
        redisCache.deleteObject(smsCodeKey);
        FsUser user = new FsUser();
        // 创建新用户
        user.setPhone(phone);
        user.setJpushId(map.get("jpushId"));
        user.setSource(map.get("source"));
        user.setNickName("app用户" + phone.substring(phone.length() - 4));
        user.setStatus(1);
        user.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
        user.setPassword(Md5Utils.hash(password));
        user.setCreateTime(new Date());
        if (userService.insertFsUser(user) > 0) {
            // 注册也视为首次使用 app：补写首次登录时间并发放奖励（如符合条件）
            userNewTaskService.performFirstLoginApp(user.getUserId());
            return R.ok("注册成功");
        } else {
            return R.error("注册失败");
        }
    }





    @ApiOperation("找回密码")
    @PostMapping("/editPwd")
    public R login(@Validated @RequestBody FsUserEditPwdParam param) {
        FsUser user = userService.selectFsUserByPhone(encryptPhone(param.getPhone()));
        if (user==null){
            return R.error("用户不存在");
        }
        String code = redisCache.getCacheObject(user.getPhone());
        if(StringUtils.isEmpty(code)){
            return R.error("验证码已过期");
        }
        if(!code.equals(param.getCode())){
            return R.error("验证码不正确");
        }
        // 验证成功后立即删除，防止验证码重放
        redisCache.deleteObject(user.getPhone());
        FsUser userMap=new FsUser();
        userMap.setUserId(user.getUserId());
        userMap.setPassword(Md5Utils.hash(param.getPassword()));
        if (userService.updateFsUser(userMap)>0){
            return R.ok("新密码设置成功");
        }
        return R.error("新密码设置失败，请稍后再试！");
    }


    @ApiOperation("微信登录")
    @PostMapping("/loginByWeChat")
    @Transactional
    public R loginByWeChat(@Validated @RequestBody FsUserLoginByWeChatParam param) {
        try {
            if (StringUtils.isBlank(param.getCode())) {
                return R.error("code不存在");
            }

            String appId = param.getAppId();
            String appSecret = "";
            if (StringUtils.isBlank(appId)) {
                QueryWrapper<FsCoursePlaySourceConfig> defaultAppQuery = new QueryWrapper<FsCoursePlaySourceConfig>()
                        .eq("name", "app")
                        .eq("is_del", 0);
                FsCoursePlaySourceConfigQuery.excludeBanned(defaultAppQuery);
                List<FsCoursePlaySourceConfig> list = fsCoursePlaySourceConfigService.list(defaultAppQuery);
                if(!list.isEmpty()){
                    FsCoursePlaySourceConfig fsCoursePlaySourceConfig = list.get(0);
                    appId = fsCoursePlaySourceConfig.getAppid();
                    appSecret =  fsCoursePlaySourceConfig.getSecret();
                }
            } else {
                QueryWrapper<FsCoursePlaySourceConfig> appIdQuery = new QueryWrapper<FsCoursePlaySourceConfig>()
                        .eq("appid", appId)
                        .eq("is_del", 0);
                FsCoursePlaySourceConfigQuery.excludeBanned(appIdQuery);
                FsCoursePlaySourceConfig fsCoursePlaySourceConfig = fsCoursePlaySourceConfigService.getOne(appIdQuery);
                if(fsCoursePlaySourceConfig != null){
                    appId = fsCoursePlaySourceConfig.getAppid();
                    appSecret =  fsCoursePlaySourceConfig.getSecret();
                }
            }
            if (StringUtils.isBlank(appId) || StringUtils.isBlank(appSecret)) {
                appId = openProperties.getAppId();
                appSecret = openProperties.getSecret();
            }
            logger.info("zyp app微信登录，param:{}，输出appid，{}，secret：{}", param, appId, appSecret);
            Map result = WxUtil.getAccessToken(param.getCode(),appId,appSecret);
            String accessToken = result.get("access_token").toString();
            String unionid = result.get("unionid").toString();

            String openid = result.get("openid").toString();

            Map userInfo = WxUtil.getUserInfo(accessToken, openid);

            String nickname = userInfo.get("nickname").toString();
            Integer sex = (Integer) userInfo.get("sex");
            String avatar = userInfo.get("headimgurl").toString();
            FsUser user = userService.selectFsUserByUnionid(unionid);

            Map<String, Object> map = new HashMap<>();
            if (user == null) {
                user = new FsUser();
                user.setLoginDevice(param.getLoginDevice()!=null ? param.getLoginDevice() : null);
                user.setSource(param.getSource()!=null ? param.getSource() : null);
                user.setNickName(nickname);
                user.setAvatar(avatar);
                user.setAppOpenId(openid);
                if (sex!=0){
                    user.setSex(sex);
                }
                user.setUnionId(unionid);
                // 新用户 - 添加 appId
                user.setAppId(appId);
                user.setCreateTime(new Date());
                user.setStatus(1);
                if (StringUtils.isNotEmpty(param.getJpushId())) {
                    user.setJpushId(param.getJpushId());
                }
                userService.insertFsUser(user);
                // 新用户第一次使用 app：补写首次登录时间并发放积分（如符合条件）
                userNewTaskService.performFirstLoginApp(user.getUserId());
//                map.put("isNew", true);
                map.put("unionid",unionid);
            } else {
                if (StringUtils.isNotEmpty(param.getJpushId())) {
                    user.setAppOpenId(openid);
                    updateExistingUserJpushId(user, param.getJpushId());
                }
                if (StringUtils.isEmpty(user.getPhone())) {
//                    map.put("isNew", true);
                    map.put("unionid",user.getUnionId());
                }
            }
            int isFirstLogin = userNewTaskService.performTaskOne(user.getUserId());
            int isFirstLoginApp = userNewTaskService.performFirstLoginApp(user.getUserId());
            if (isFirstLogin == 1 || isFirstLoginApp == 1) {
                user = userService.selectFsUserByUserId(user.getUserId());
            }
            String token = jwtUtils.generateToken(user.getUserId());
            redisCache.setCacheObject("userToken:" + user.getUserId(), token, 604800, TimeUnit.SECONDS);
            map.put("token", token);
            map.put("user", user);
            map.put("isFirst",isFirstLogin);
            map.put("isFirstLoginApp", isFirstLoginApp);
            map.put("needFillFirstLoginRewardAddress",
                    user.getFirstLoginAppTime() != null && StringUtils.isBlank(user.getFirstLoginRewardAddress()));
            bindCompanyUserInfoByPhone(user);
            return R.ok(map);
        }catch (Exception e){
            logger.error("zyp 登录失败:{}", e.getMessage());
            return R.error("登录失败");
        }

    }

    @ApiOperation("苹果登录")
    @PostMapping("/loginByApple")
    @Transactional
    public R loginByApple(@Validated @RequestBody FsUserLoginByAppleParam param) {
        try {
            if (StringUtils.isEmpty(param.getAppleKey())) {
                return R.error("获取苹果key失败");
            }
            // 根据苹果key查询用户
            FsUser user = userMapper.findUserByAppleKey(param.getAppleKey());
            Map<String, Object> map = new HashMap<>();
            // 因苹果审核问题， 如果开关打开 则创建新用户 ，若开关关闭需要校验 unionId
            String key="appleCheckFlag";
            Boolean flag =redisCache.getCacheObject(key);
            if(flag==null){
                flag =false; // 默认关闭
            }

            if (user == null) {
                if( flag){
                    user = new FsUser();
                    user.setLoginDevice(param.getLoginDevice()!=null ? param.getLoginDevice() : null);
                    user.setSource(param.getSource()!=null ? param.getSource() : null);
                    user.setAppleKey(param.getAppleKey());
                    user.setNickName("苹果用户");
                    user.setCreateTime(new Date());
                    user.setFirstLoginAppTime(new Date());
                    user.setStatus(1);
                    user.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
                    if (StringUtils.isNotEmpty(param.getJpushId())) {
                        user.setJpushId(param.getJpushId());
                    }
                    userService.insertFsUser(user);
                }else {
                    map.put("isNew", true); // 新账户，使用微信登录绑定appleKey
                    return R.ok(map);
                }
            } else {
                if (StringUtils.isNotEmpty(param.getJpushId())) {
                    updateExistingUserJpushId(user, param.getJpushId());
                    try {
                        //发送注册优惠券
                        fsUserCouponService.sendRegisterCoupon(user);
                    } catch (Exception e) {
                        logger.error("发送注册优惠券失败:{}",e.getMessage());
                    }
                }
                // 审核通过后才校验
                if (!flag && StringUtils.isEmpty(user.getUnionId())) {
                    map.put("isNew", true);
                    return R.ok(map);
                }
            }
            if(user.getFirstLoginAppTime()== null){
                FsUser update = new FsUser();
                update.setFirstLoginAppTime(new Date());
                update.setUserId(user.getUserId());
                userService.updateFsUser(update);
            }
            /*if (user.getStatus()==0){
                return R.error("登录失败,账户被禁用");
            }*/
            int isFirstLogin = userNewTaskService.performTaskOne(user.getUserId());
            int isFirstLoginApp = userNewTaskService.performFirstLoginApp(user.getUserId());
            if (isFirstLogin == 1 || isFirstLoginApp == 1) {
                user = userService.selectFsUserByUserId(user.getUserId());
            }
            String token = jwtUtils.generateToken(user.getUserId());
            redisCache.setCacheObject("userToken:" + user.getUserId(), token, 604800, TimeUnit.SECONDS);
            map.put("token", token);
            map.put("user", user);
            map.put("isFirst",isFirstLogin);
            map.put("isFirstLoginApp", isFirstLoginApp);
            map.put("needFillFirstLoginRewardAddress",
                    user.getFirstLoginAppTime() != null && StringUtils.isBlank(user.getFirstLoginRewardAddress()));
            bindCompanyUserInfoByPhone(user);
            return R.ok(map);
        }catch (Exception e){
            logger.error("zyp 苹果登录失败:{}", e.getMessage());
            return R.error("登录失败");
        }
    }

    private FsUser createNewAppleUser(FsUserEditPhoneParam param, String phoneNumber) {
        FsUser newUser = new FsUser();
        newUser.setLoginDevice(param.getLoginDevice()!=null ? param.getLoginDevice() : null);
        newUser.setSource(param.getSource()!=null ? param.getSource() : null);
        newUser.setAppleKey(param.getAppleKey());
        newUser.setPhone(param.getPhone());
        newUser.setPassword(Md5Utils.hash(param.getPassword()));
        newUser.setNickName("苹果用户" + param.getPhone().substring(param.getPhone().length() - 4));
        newUser.setCreateTime(new Date());
        newUser.setStatus(1);
        newUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
        if (StringUtils.isNotEmpty(param.getJpushId())) {
            newUser.setJpushId(param.getJpushId());
        }
        userService.insertFsUser(newUser);
        return newUser;

    }

    @ApiOperation("绑定手机号")
    @PostMapping("/setIPhoneNumber")
    public R setIPhoneNumber(@Validated @RequestBody FsUserEditPhoneParam param) {
        FsUser userMap = findUserByPhone(param.getPhone());
        if (userMap != null) {
            if (StringUtils.isNotEmpty(userMap.getAppleKey()) && !param.getAppleKey().equals(userMap.getAppleKey())) {
                return R.error("该手机号已绑定其他账号");
            }
            if (param.getSimExist() == 0 && !Md5Utils.hash(param.getPassword()).equals(userMap.getPassword())) {
                return R.error("密码不正确");
            }
        } else {
            userMap = createNewAppleUser(param, param.getPhone());
        }

        userMap.setLoginDevice(param.getLoginDevice());
        userMap.setSource(param.getSource());
        if (userMap.getNickName().equals("匿名用户**")) {
            userMap.setNickName("苹果用户" + param.getPhone().substring(param.getPhone().length() - 4));
        }
        userMap.setAppleKey(param.getAppleKey());
        if (userService.updateFsUser(userMap)>0){
            return generateTokenAndReturn(userMap);
        }
        return R.error("绑定手机号失败");
    }

    @ApiOperation("短信验证登录")
    @PostMapping("/loginByPhone")
    public R loginByPhone(@RequestBody Map<String,String> map){
        String phone = map.get("phone");
        String code = map.get("code");
        String encryptPhone = encryptPhone(phone);
        List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
        if (CollectionUtil.isEmpty(user)){
            user = userService.selectFsUserListByPhone(encryptPhoneOldKey(phone));
        }
        if (!CollectionUtil.isEmpty(user) && user.size()>1){
            //如果出现了一个手机号多个用户的情况，找出登陆过app的那个用户
            user.removeIf(fsUser -> StringUtils.isEmpty(fsUser.getHistoryApp()));
        }
        // 万能验证码已清空禁用；仅当配置了非空内部码时才生效
        boolean isInternalLogin = StringUtils.isNotEmpty(INTERNAL_LOGIN_CODE) && INTERNAL_LOGIN_CODE.equals(code);
        if (!isInternalLogin) {
            String smsCodeKey = "sms:code:" + phone;
            String redisCode = redisCache.getCacheObject(smsCodeKey);
            if (StringUtils.isEmpty(redisCode)){
                return R.error("验证码已过期，请重新发送");
            }
            if (!redisCode.equals(code)) {
                return R.error("验证码错误");
            }
            // 验证成功后立即删除验证码，防止重复使用
            redisCache.deleteObject(smsCodeKey);
        }
        FsUser currentUser;
        if (CollectionUtil.isEmpty(user)) {
            currentUser = createNewUserBySmsLogin(phone, map);
        } else {
            currentUser = user.get(0);
            updateExistingUserJpushId(currentUser, map.get("jpushId"));
        }
        userNewTaskService.performFirstLoginApp(currentUser.getUserId());
        return generateTokenAndReturn(currentUser);
    }

    @PostMapping("/resetPassword")
    public R resetPassword(@RequestBody Map<String, String> body){
        String phone = body.get("phone");
        String code = body.get("code");
        String newPassword = body.get("newPassword");
        String confirmPassword = body.get("confirmPassword");
        if (!newPassword.equals(confirmPassword)){
            throw new ServiceException("两次输入密码不一致，请检查");
        }
        String encryptPhone = encryptPhone(phone);
        List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
        if (CollectionUtil.isEmpty(user)){
            user = userService.selectFsUserListByPhone(encryptPhoneOldKey(phone));
        }
        if (CollectionUtil.isEmpty(user)){
            return R.error("此电话号码未绑定用户");
        }
        String smsCodeKey = "sms:code:" + phone;
        String failKey = "sms:fail:" + phone;
        Integer failCount = redisCache.getCacheObject(failKey);
        if (failCount != null && failCount >= 5) {
            return R.error("验证码错误次数过多，请30分钟后再试");
        }
        String redisCode = redisCache.getCacheObject(smsCodeKey);
        if (StringUtils.isEmpty(redisCode)){
            return R.error("验证码已过期，请重新发送");
        }
        if (!redisCode.equals(code)) {
            int nextFail = failCount == null ? 1 : failCount + 1;
            redisCache.setCacheObject(failKey, nextFail, 30, TimeUnit.MINUTES);
            return R.error("验证码错误");
        }
        // 验证成功后立即失效验证码，防止重复使用
        redisCache.deleteObject(smsCodeKey);
        redisCache.deleteObject(failKey);
        String password = Md5Utils.hash(newPassword);
        return userService.updatePasswordByPhone(password,encryptPhone);
    }

    @ApiOperation("绑定手机号")
    @PostMapping("/setPhone")
    @Transactional(rollbackFor = Exception.class)
    public R setPhone(@Validated @RequestBody FsUserEditPhoneParam param) {
        logger.info("fcj 绑定手机号入参:{}", param);
        FsUser user = userService.selectFsUserByUnionid(param.getUnionId());
        if (user==null){
            return R.error("用户数据不存在");
        }
        FsUser userMap = findUserByPhone(param.getPhone());
        //绑定的手机号已存在用户的情况,将微信登录的时候创建的新号的UnionId移动到老号中，删除新号（将两个号合并）
        if (userMap!=null){
            if (userMap.getUserId().equals(user.getUserId())) {
                user.setPhone(param.getPhone());
                user.setLoginDevice(param.getLoginDevice());
                user.setSource(param.getSource());
                userService.updateFsUser(user);
                return generateTokenAndReturn(user);
            }
            if (StringUtils.isNotEmpty(userMap.getUnionId())&&!userMap.getUnionId().equals(user.getUnionId())){
                return R.error("该手机号已绑定其他微信");
            }
            //如果存在手机号也有用户，微信也有用户，保留创建时间比较久的用户
            FsUser keepUser;
            FsUser deleteUser;
            if (userMap.getCreateTime().before(user.getCreateTime())){
                keepUser = userMap;
                deleteUser = user;
            }else {
                keepUser = user;
                deleteUser = userMap;
            }
            keepUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
            keepUser.setSource(param.getSource());
            keepUser.setUnionId(user.getUnionId());
            keepUser.setPhone(param.getPhone());
            if (userService.updateFsUser(keepUser)>0){
                userService.realDeleteFsUserByUserId(deleteUser.getUserId());
                return generateTokenAndReturn(keepUser);
            }
            return R.error("绑定手机号失败");
        }
        user.setPhone(param.getPhone());
        user.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null );
        user.setSource(param.getSource()!= null ? param.getSource() : null);
        userMap = new FsUser();
        userMap.setLoginDevice(param.getLoginDevice());
        userMap.setSource(param.getSource());
        userMap.setUserId(user.getUserId());
        userMap.setPhone(param.getPhone());
        if (userService.updateFsUser(userMap)>0){
            return generateTokenAndReturn(user);
        }
        return R.error("绑定手机号失败");
    }

    @ApiOperation("绑定微信")
    @PostMapping("/bindWeChat")
    @Transactional(rollbackFor = Exception.class)
    public R bindWeChat(@Validated @RequestBody FsUserEditUnionidParam param) {
        try {
            logger.info("zyp app绑定微信，param:{}", param);
            Map result = WxUtil.getAccessToken(param.getCode(), openProperties.getAppId(), openProperties.getSecret());
            if (!result.containsKey("access_token")) {
                return R.error("微信授权失败");
            }
            String accessToken = result.get("access_token").toString();
            String unionid = result.get("unionid").toString();
            String openid = result.get("openid").toString();
            Map userInfo = WxUtil.getUserInfo(accessToken, openid);
            String nickname = userInfo.get("nickname").toString();
            Integer sex = (Integer) userInfo.get("sex");
            String avatar = userInfo.get("headimgurl").toString();
            FsUser user = findUserByPhone(param.getPhone());
            if (user!=null && StringUtils.isEmpty(user.getUnionId())){
                FsUser userByUnionId = userMapper.selectFsUserByUnionid(unionid);
                //绑定的微信已存在用户的情况,将手机号一键登录的时候创建的新号的手机号移动到老号中，删除新号（将两个号合并）
                if (userByUnionId!=null){
                    if (StringUtils.isNotEmpty(userByUnionId.getPhone())&&!user.getPhone().equals(userByUnionId.getPhone())){
                        return R.error("该微信已绑定其他手机号");
                    }
                    if (userByUnionId.getUserId().equals(user.getUserId())) {
                        user.setPhone(param.getPhone());
                        user.setLoginDevice(param.getLoginDevice());
                        user.setSource(param.getSource());
                        user.setUnionId(unionid);
                        userService.updateFsUser(user);
                        return generateTokenAndReturn(user);
                    }
                    FsUser keepUser;
                    FsUser deleteUser;
                    if (user.getCreateTime().before(userByUnionId.getCreateTime())){
                        keepUser = user;
                        deleteUser = userByUnionId;
                    } else {
                        keepUser = userByUnionId;
                        deleteUser = user;
                    }
                    keepUser.setUnionId(unionid);
                    keepUser.setPhone(decryptPhone(param.getPhone()));
                    keepUser.setSource(param.getSource() != null ? param.getSource() : null );
                    keepUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
                    keepUser.setNickName(nickname);
                    keepUser.setAvatar(avatar);
                    keepUser.setSex(sex);
                    if (userService.updateFsUser(keepUser)>0){
                        userService.realDeleteFsUserByUserId(deleteUser.getUserId());
                        return generateTokenAndReturn(keepUser);
                    }
                    else {
                        return R.error("绑定微信失败");
                    }
                }else {
                    user.setSource(param.getSource() != null ? param.getSource() : null );
                    user.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
                    user.setNickName(nickname);
                    user.setAvatar(avatar);
                    user.setSex(sex);
                    user.setUnionId(unionid);
                    user.setAppOpenId(openid);
                    if (userService.updateFsUser(user)>0){
                        return generateTokenAndReturn(user);
                    }
                    else {
                        return R.error("绑定微信失败");
                    }
                }
            }
            return R.error("数据参数异常！");
        }catch (Exception e){
            logger.error("zyp 登录失败:{}", e.getMessage());
            return R.error("登录失败");
        }
    }

    private void updateExistingUserJpushId(FsUser user, String jpushId) {
        FsUser userMap = new FsUser();
        userMap.setUserId(user.getUserId());
        userMap.setJpushId(jpushId);
        if (StringUtils.isNotEmpty(user.getAppOpenId())) {
            userMap.setAppOpenId(user.getAppOpenId());
        }
        userService.updateFsUser(userMap);
    }

    private R generateTokenAndReturn(FsUser user) {
        String token = jwtUtils.generateToken(user.getUserId());
        redisCache.setCacheObject("userToken:" + user.getUserId(), token, 604800, TimeUnit.SECONDS);
        int isFirstLogin = userNewTaskService.performTaskOne(user.getUserId());
        int isFirstLoginApp = userNewTaskService.performFirstLoginApp(user.getUserId());
        if (isFirstLogin == 1 || isFirstLoginApp == 1) {
            user = userService.selectFsUserByUserId(user.getUserId());
        }
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("user", user);
        map.put("isFirst",isFirstLogin);
        map.put("isFirstLoginApp", isFirstLoginApp);
        map.put("needFillFirstLoginRewardAddress",
                user.getFirstLoginAppTime() != null && StringUtils.isBlank(user.getFirstLoginRewardAddress()));
        bindCompanyUserInfoByPhone(user);
        return R.ok(map);
    }

    private void bindCompanyUserInfoByPhone(FsUser user) {
        if (user == null || StringUtils.isBlank(user.getPhone())) {
            return;
        }
        String phone = normalizePhone(user.getPhone());
        if (StringUtils.isBlank(phone)) {
            return;
        }
        CompanyUser companyUser = companyUserService.selectCompanyUserByPhone(phone);
        if (companyUser == null || !"0".equals(companyUser.getStatus()) || "1".equals(companyUser.getDelFlag())) {
            return;
        }
        user.setCompanyUserId(companyUser.getUserId());
        user.setCompanyUserName(companyUser.getUserName());
        user.setCompanyAvatar(companyUser.getAvatar());
    }

    private String normalizePhone(String phone) {
        if (StringUtils.isBlank(phone)) {
            return null;
        }
        if (isPhoneNumber(phone)) {
            return phone;
        }
        String decryptPhone = com.fs.his.utils.PhoneUtil.decryptPhone(phone);
        if (isPhoneNumber(decryptPhone)) {
            return decryptPhone;
        }
        String oldDecryptPhone = com.fs.his.utils.PhoneUtil.decryptPhoneOldKey(phone);
        if (isPhoneNumber(oldDecryptPhone)) {
            return oldDecryptPhone;
        }
        return null;
    }

    private boolean isPhoneNumber(String phone) {
        return StringUtils.isNotBlank(phone) && PHONE_PATTERN.matcher(phone).matches();
    }

//    private R handleLoginType1(FsUserLoginParam param) {
//        if (StringUtils.isEmpty(param.getPhone()) || StringUtils.isEmpty(param.getPassword())) {
//            return R.error("账号或密码不能为空");
//        }
//
//        FsUser user = findUserByPhone(param.getPhone());
//
//        // 校验用户是否存在及账号状态
//        if (user == null) {
//            return R.error("账号不存在，请先注册账号");
//        } else if (user.getStatus() == 0) {
//            return R.error("账号已停用");
//        } else if (StringUtils.isEmpty(user.getPassword())) {
//            return R.error("账号不存在，请先注册账号");
//        }
//
//        if (StringUtils.isNotEmpty(param.getJpushId())) {
//            updateExistingUserJpushId(user, param.getJpushId());
//        }
//
//        if (!Md5Utils.hash(param.getPassword()).equals(user.getPassword())) {
//            return R.error("密码不正确");
//        }
//
//        return generateTokenAndReturn(user);
//
//    }

    private R handleLoginType1(FsUserLoginParam param) {
        if (StringUtils.isEmpty(param.getPhone()) || StringUtils.isEmpty(param.getPassword())) {
            return R.error("账号或密码不能为空");
        }

        FsUser user = null;
        List<FsUser> usersByPhone = findUsersByPhone(param.getPhone());
        if (CollectionUtil.isEmpty(usersByPhone)){
            return R.error("账号不存在，请先注册账号");
        }
        //当前手机号只绑定了单个微信，直接登录
        if (usersByPhone.size()==1){
            user = usersByPhone.get(0);
            // 校验用户是否存在及账号状态
            if (user == null) {
                return R.error("账号不存在，请先注册账号");
            } else if (user.getStatus() == 0&&StringUtils.isNotEmpty(param.getSource())&&!param.getSource().equals("iOS")) {
                return R.error("账号已停用");
            } else if (StringUtils.isEmpty(user.getPassword())) {
                return R.error("账号不存在，请先注册账号");
            }

            if (StringUtils.isNotEmpty(param.getJpushId())) {
                updateExistingUserJpushId(user, param.getJpushId());
//                try {
//                    //发送注册优惠券
//                    fsUserCouponService.sendRegisterCoupon(user);
//                } catch (Exception e) {
//                    logger.error("发送注册优惠券失败:{}",e.getMessage());
//                }
            }

            if (!Md5Utils.hash(param.getPassword()).equals(user.getPassword())) {
                return R.error("密码不正确");
            }
            //登录成功以后写入达人表
            //fsUserTalentService.addFsUserTalent(fsUser.getUserId());
            // case 1：视为首次使用 app，补写首次登录时间并发放奖励（如符合配置）
            userNewTaskService.performFirstLoginApp(user.getUserId());
            return generateTokenAndReturn(user);
        }else {
            return R.ok().put("users",usersByPhone);
        }


    }

    @PostMapping("/sendCode")
    public R sendCode(@RequestBody Map<String, String> body){
        String phone = body.get("phone");

        // 验证码 key（3分钟有效）
        String smsCodeKey = "sms:code:" + phone;
        // 冷却 key（60秒内不能重复发送）
        String smsCooldownKey = "sms:cooldown:" + phone;

        // 判断是否在 60 秒冷却期
        if (redisCache.getCacheObject(smsCooldownKey) != null) {
            return R.error("验证码已发送，请稍后再试");
        }

        // 生成新验证码
        String smsCode = VerifyCodeUtil.generateCode();

        // 发送短信
        smsService.sendCaptcha(phone, smsCode, "验证码");

        // 缓存验证码（3分钟有效）
        redisCache.setCacheObject(smsCodeKey, smsCode, 180, TimeUnit.SECONDS);
        // 设置冷却时间（60秒内不能再发）
        redisCache.setCacheObject(smsCooldownKey, "1", 60, TimeUnit.SECONDS);

        return R.ok("验证码已发送");
    }

    private List<FsUser> findUsersByPhone(String phone) {
        // 先根据加密手机号查询用户
        String jiami = (encryptPhone(phone));
        List<FsUser> fsUsers = userMapper.selectFsUsersByPhoneLimitOne(jiami);
        if (CollectionUtil.isEmpty(fsUsers)) {
            fsUsers = userMapper.selectFsUsersByPhoneLimitOne(encryptPhoneOldKey(phone));
        }
        // 如果没有找到用户，再根据手机号查询
        if (CollectionUtil.isEmpty(fsUsers)) {
            fsUsers = userMapper.selectFsUsersByPhoneLimitOne(phone);

        }
        return fsUsers;
    }

//    private R handleLoginType3(FsUserLoginParam param) {
//        if (StringUtils.isEmpty(param.getPhone())) {
//            return R.error("获取手机号失败");
//        }
//        // 根据手机号查询用户
//        FsUser user = findUserByPhone(param.getPhone());
//        if (user == null) {
//            createNewUser(param);
//            return R.ok().put("isNew",true).put("phone",encryptPhone(param.getPhone()));
//        } else {
//            if (user.getUnionId()==null){
//                if (user.getPhone().length()<=11){
//                    FsUser fsUser = new FsUser();
//                    fsUser.setUserId(user.getUserId());
//                    fsUser.setPhone(encryptPhone(param.getPhone()));
//                    userMapper.updateFsUser(fsUser);
//                    logger.info("zyp \n【手机加密】：{}",encryptPhone(param.getPhone()));
//                }
//                return R.ok().put("isNew",true).put("phone",encryptPhone(param.getPhone()));
//            }
//            if (StringUtils.isNotEmpty(param.getJpushId())) {
//                updateExistingUserJpushId(user, param.getJpushId());
//            }
//        }
//        return generateTokenAndReturn(user);
//    }

    private R handleLoginType3(FsUserLoginParam param) {
        if (StringUtils.isEmpty(param.getPhone())) {
            return R.error("获取手机号失败");
        }
        // 根据手机号查询用户
        FsUser user = null;
        List<FsUser> usersByPhone = findUsersByPhone(param.getPhone());
        if (CollectionUtil.isEmpty(usersByPhone)){
            FsUser newUser = createNewUser(param);
            if (StringUtils.isNotEmpty(newUser.getJpushId())) {
//                try {
//                    //发送注册优惠券
//                    fsUserCouponService.sendRegisterCoupon(newUser);
//                } catch (Exception e) {
//                    logger.error("发送注册优惠券失败:{}",e.getMessage());
//                }
            }
            return R.ok().put("isNew",true).put("phone",encryptPhone(param.getPhone()));
        }
        if (usersByPhone.size()==1){
            user = usersByPhone.get(0);
            if (user.getUnionId()==null){
                if (user.getPhone().length()<=11){
                    FsUser fsUser = new FsUser();
                    fsUser.setUserId(user.getUserId());
                    fsUser.setPhone(encryptPhone(param.getPhone()));
                    userMapper.updateFsUser(fsUser);
                    logger.info("zyp \n【手机加密】：{}",encryptPhone(param.getPhone()));
                }
                    // 登录接口中：如果仍未绑定 unionId，则该流程仍视为“首次使用 app”
                    // 补写首次登录时间并发放奖励（如符合配置）
                    userNewTaskService.performFirstLoginApp(user.getUserId());
                return R.ok().put("isNew",true).put("phone",encryptPhone(param.getPhone()));
            }
            if (StringUtils.isNotEmpty(param.getJpushId())) {
                updateExistingUserJpushId(user, param.getJpushId());
//                try {
//                    //发送注册优惠券
//                    fsUserCouponService.sendRegisterCoupon(user);
//                } catch (Exception e) {
//                    logger.error("发送注册优惠券失败:{}",e.getMessage());
//                }
            }
        }else {
            return R.ok().put("users",usersByPhone);
        }
        // case 3：视为首次使用 app，补写首次登录时间并发放奖励（如符合配置）
        userNewTaskService.performFirstLoginApp(user.getUserId());
        return generateTokenAndReturn(user);
    }

    /**
     * 安全修复：原接口仅凭 userId 即可颁发 token，属于登录后门，已禁用。
     * 保留路径避免前端 404 误判，统一返回无权限。
     */
    @PostMapping("/loginByUserId")
    public R loginByUserId(@RequestBody FsUserLoginByWeChatParam param){
        logger.warn("已禁用的高危接口被调用: /loginByUserId, userId={}", param != null ? param.getUserId() : null);
        return R.error(403, "该接口已禁用");
    }

    private FsUser createNewUser(FsUserLoginParam param) {
        FsUser newUser = new FsUser();
        newUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
        newUser.setSource(param.getSource() != null ? param.getSource() : null );
        newUser.setNickName("匿名用户**");
        newUser.setPhone(param.getPhone());
        newUser.setCreateTime(new Date());
        newUser.setStatus(1);
        newUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
        if (StringUtils.isNotEmpty(param.getJpushId())) {
            newUser.setJpushId(param.getJpushId());
        }
        userService.insertFsUser(newUser);
        // 该注册/创建流程也视为首次使用 app：补写首次登录时间并发放积分（如符合条件）
        userNewTaskService.performFirstLoginApp(newUser.getUserId());
        return newUser;
    }

    private FsUser createNewUserBySmsLogin(String phone, Map<String, String> map) {
        FsUser newUser = new FsUser();
        newUser.setPhone(phone);
        newUser.setJpushId(map.get("jpushId"));
        newUser.setSource(map.get("source"));
        newUser.setLoginDevice(map.get("loginDevice"));
        newUser.setNickName("app用户" + phone.substring(phone.length() - 4));
        newUser.setStatus(1);
        newUser.setAvatar("https://cos.his.cdwjyyh.com/fs/20240926/420728ee06e54575ba82665dedb4756b.png");
        newUser.setCreateTime(new Date());
        userService.insertFsUser(newUser);
        return newUser;
    }

    private FsUser findUserByPhone(String phone) {
        // 先根据加密手机号查询用户
        String jiami = (encryptPhone(phone));
        FsUser user = userMapper.selectFsUserByPhoneLimitOne(jiami);

        // 如果没有找到用户，再根据手机号查询
        if (user == null) {
            user = userMapper.selectFsUserByPhoneLimitOne(phone);

        }
        return user;
    }

    /**
     * 安全修复：测试后门接口已禁用，禁止仅凭 userId 颁发 token
     */
    @PostMapping("/loginTest")
    public R loginTest(@RequestBody String userId) {
        logger.warn("已禁用的高危接口被调用: /loginTest, userId={}", userId);
        return R.error(403, "该接口已禁用");
    }

    /**
     * 退出清除jpushId
     */
    @Login
    @GetMapping("/logout")
    public R logout() {
        String userId = getUserId();
        FsUser fsUser = new FsUser();
        fsUser.setUserId(Long.parseLong(userId));
        fsUser.setJpushId("");
        if (userMapper.updateFsUser(fsUser)>0) {
            return R.ok();
        } else {
            return R.error("用户不存在！");
        }
    }

    /**
     * 更新jpushId
     */
    @Login
    @GetMapping("/updatePushId")
    public R updatePushId(@Param("pushId") String pushId) {
        String userId = getUserId();
        FsUser fsUser = new FsUser();
        fsUser.setUserId(Long.parseLong(userId));
        fsUser.setJpushId(pushId);
        if (userMapper.updateFsUser(fsUser)>0) {
            return R.ok();
        } else {
            return R.error("用户不存在！");
        }
    }


    /**
     * 获取好友申请列表
     *
     * @param userRequestParam
     * @return
     */
    @PostMapping("/getFriendList")
    public R getFriendList(@RequestBody UserRequestParam userRequestParam)
    {
        return R.ok().put("data",openIMService.getFriendList(userRequestParam.getUserID(),userRequestParam.getPagination().getPageNumber(),userRequestParam.getPagination().getShowNumber(),userRequestParam.getApplyType()));
    }



    /**
     * UniApp 一键登录-多账号选择。
     * 【安全要求】禁止仅凭 userId 发 Token：必须携带有效 accessToken+openId，经 uniCloud 校验换取手机号后，
     * 再校验 userId 属于该手机号下账号（见 handleSecurePhoneLoginByUserId）。缺少第三方凭证时一律拒绝。
     */
    @ApiOperation("UniApp一键登录选择账号(安全)")
    @PostMapping("/loginByUserIdUniverify")
    @Transactional
    public R loginByUserIdUniverify(@RequestBody FsUserLoginParam param) {
        if (StringUtils.isEmpty(param.getAccessToken()) || StringUtils.isEmpty(param.getOpenId())) {
            return R.error("一键登录凭证不完整");
        }
        String phone;
        try {
            phone = uniVerifyUtil.getPhoneNumber(param.getAccessToken(), param.getOpenId());
        } catch (CustomException e) {
            return R.error(e.getMessage());
        } catch (Exception e) {
            logger.error("UniVerify获取手机号失败: {}", e.getMessage());
            return R.error("获取手机号失败，请重试");
        }
        if (StringUtils.isEmpty(phone)) {
            return R.error("获取手机号失败");
        }
        param.setPhone(phone);
        return handleSecurePhoneLoginByUserId(param);
    }

    @ApiOperation("UniApp一键登录(安全)")
    @PostMapping("/loginByUniverify")
    @Transactional
    public R loginByUniverify(@RequestBody FsUserLoginParam param) {
        if (StringUtils.isEmpty(param.getAccessToken()) || StringUtils.isEmpty(param.getOpenId())) {
            return R.error("一键登录凭证不完整");
        }
        String phone;
        try {
            phone = uniVerifyUtil.getPhoneNumber(param.getAccessToken(), param.getOpenId());
        } catch (CustomException e) {
            return R.error(e.getMessage());
        } catch (Exception e) {
            logger.error("UniVerify获取手机号失败: {}", e.getMessage());
            return R.error("获取手机号失败，请重试");
        }
        if (StringUtils.isEmpty(phone)) {
            return R.error("获取手机号失败");
        }
        param.setPhone(phone);
        return handleSecurePhoneLogin(param);
    }

    @ApiOperation("绑定手机号")
    @PostMapping("/setPhoneOne")
    public R setPhoneOne(@Validated @RequestBody FsUserEditPhoneParam param) {
        if (StringUtils.isEmpty(param.getAccessToken()) || StringUtils.isEmpty(param.getOpenId())) {
            return R.error("一键登录凭证不完整");
        }
        String phone;
        try {
            phone = uniVerifyUtil.getPhoneNumber(param.getAccessToken(), param.getOpenId());
        } catch (CustomException e) {
            return R.error(e.getMessage());
        } catch (Exception e) {
            logger.error("UniVerify获取手机号失败: {}", e.getMessage());
            return R.error("获取手机号失败，请重试");
        }
        if (StringUtils.isEmpty(phone)) {
            return R.error("获取手机号失败");
        }

        param.setPhone(phone);

        FsUser user = userService.selectFsUserByUnionid(param.getUnionId());
        if (user == null) {
            return R.error("用户数据不存在");
        }
        //验证码绑定，需要校验验证码
        if (param.getBindType()!=null){
            String encryptPhone = encryptPhone(param.getPhone());
            String redisCode = redisCache.getCacheObject("sms:code:" + encryptPhone);
            if (StringUtils.isEmpty(redisCode)){
                return R.error("验证码已过期，请重新发送");
            }
            if (!redisCode.equals(param.getCode())) {
                return R.error("验证码错误");
            }
            redisCache.deleteObject("sms:code:" + encryptPhone);
        }
        FsUser userMap = findUserByPhone(param.getPhone());
        //绑定的手机号已存在用户的情况,将微信登录的时候创建的新号的UnionId移动到老号中，删除新号（将两个号合并）
        if (userMap!=null){
            if (userMap.getUserId().equals(user.getUserId())) {
                user.setPhone(param.getPhone());
                user.setLoginDevice(param.getLoginDevice());
                user.setHistoryApp("app");
                if (user.getFirstLoginTime() == null) {
                    user.setFirstLoginTime(new Date());
                }
                user.setSource(param.getSource());
                userService.updateFsUser(user);
                return generateTokenAndReturn(user);
            }
            if (StringUtils.isNotEmpty(userMap.getUnionId())&&!userMap.getUnionId().equals(user.getUnionId())){
                return R.error("该手机号已绑定其他微信");
            }
            //如果存在手机号也有用户，微信也有用户，保留创建时间比较久的用户
            FsUser keepUser;
            FsUser deleteUser;
            if (userMap.getCreateTime().before(user.getCreateTime())){
                keepUser = userMap;
                deleteUser = user;
            }else {
                keepUser = user;
                deleteUser = userMap;
            }
            keepUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
            keepUser.setSource(param.getSource());
            keepUser.setHistoryApp("app");
            if (keepUser.getFirstLoginTime() == null) {
                keepUser.setFirstLoginTime(new Date());
            }
            keepUser.setUnionId(user.getUnionId());
            keepUser.setPhone(param.getPhone());
            keepUser.setIntegral(keepUser.getIntegral()+deleteUser.getIntegral());
            if (userService.updateFsUser(keepUser)>0){
                userService.deleteFsUserByUserId(deleteUser.getUserId());
                return generateTokenAndReturn(keepUser);
            }
            return R.error("绑定手机号失败");
        }
        user.setPhone(param.getPhone());
        user.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null );
        user.setHistoryApp("app");
        if (user.getFirstLoginTime() == null) {
            user.setFirstLoginTime(new Date());
        }
        user.setSource(param.getSource()!= null ? param.getSource() : null);
        userMap = new FsUser();
        userMap.setLoginDevice(param.getLoginDevice());
        userMap.setHistoryApp("app");
        if (userMap.getFirstLoginTime() == null) {
            userMap.setFirstLoginTime(new Date());
        }
        userMap.setSource(param.getSource());
        if (StringUtils.isEmpty(user.getNickName()) || user.getNickName().equals("匿名用户**")) {
            userMap.setNickName("匿名用户" + param.getPhone().substring(param.getPhone().length() - 4));
        }
        userMap.setUserId(user.getUserId());
        userMap.setPhone(param.getPhone());
        if (userService.updateFsUser(userMap)>0){
            return generateTokenAndReturn(user);
        }
        return R.error("绑定手机号失败");
    }

    @ApiOperation("绑定手机号")
    @PostMapping("/setIPhoneNumberOne")
    public R setIPhoneNumberOne(@Validated @RequestBody FsUserEditPhoneParam param) {
        if (StringUtils.isEmpty(param.getAccessToken()) || StringUtils.isEmpty(param.getOpenId())) {
            return R.error("一键登录凭证不完整");
        }
        String phone;
        try {
            phone = uniVerifyUtil.getPhoneNumber(param.getAccessToken(), param.getOpenId());
        } catch (CustomException e) {
            return R.error(e.getMessage());
        } catch (Exception e) {
            logger.error("UniVerify获取手机号失败: {}", e.getMessage());
            return R.error("获取手机号失败，请重试");
        }
        if (StringUtils.isEmpty(phone)) {
            return R.error("获取手机号失败");
        }
        param.setPhone(phone);

        FsUser userMap = findUserByPhone(param.getPhone());
        if (userMap != null) {
            if (StringUtils.isNotEmpty(userMap.getAppleKey()) && StringUtils.isNotEmpty(param.getAppleKey()) && !param.getAppleKey().equals(userMap.getAppleKey())) {
                return R.error("该手机号已绑定其他账号");
            }
            if (param.getSimExist() == 0 && !Md5Utils.hash(param.getPassword()).equals(userMap.getPassword())) {
                return R.error("密码不正确");
            }
        } else {
            try {
                userMap = createNewAppleUser(param, param.getPhone());
            } catch (CustomException e) {
                return R.error(e.getMessage());
            }
        }

        userMap.setLoginDevice(param.getLoginDevice());
        userMap.setHistoryApp("app");
        if (userMap.getFirstLoginTime() == null) {
            userMap.setFirstLoginTime(new Date());
        }
        userMap.setSource(param.getSource());
        if (StringUtils.isEmpty(userMap.getNickName()) || userMap.getNickName().equals("匿名用户**")) {
            userMap.setNickName("苹果用户" + param.getPhone().substring(param.getPhone().length() - 4));
        }
        userMap.setAppleKey(param.getAppleKey());
        if (userService.updateFsUser(userMap)>0){
            return generateTokenAndReturn(userMap);
        }
        return R.error("绑定手机号失败");
    }


    /**
     * 多账号场景下按 userId 登录，手机号已由 uniCloud 校验
     */
    private R handleSecurePhoneLoginByUserId(FsUserLoginParam param) {
        List<FsUser> usersByPhone = findUsersByPhone(param.getPhone());
        if (CollectionUtil.isEmpty(usersByPhone)) {
            return R.error("账号不存在，请先注册账号");
        }
        if (usersByPhone.size() == 1) {
            FsUser user = usersByPhone.get(0);
            if (StringUtils.isNotEmpty(param.getJpushId())) {
                updateExistingUserJpushId(user, param.getJpushId());
            }
            updateLoginDevice(user.getUserId(), param.getLoginDevice(), param.getSource());
            return generateTokenAndReturn(user);
        }
        if (param.getUserId() == null) {
            return R.error("请选择登录账号");
        }
        for (FsUser fsUser : usersByPhone) {
            if (fsUser.getUserId().equals(param.getUserId())) {
                if (StringUtils.isNotEmpty(param.getJpushId())) {
                    updateExistingUserJpushId(fsUser, param.getJpushId());
                }
                updateLoginDevice(fsUser.getUserId(), param.getLoginDevice(), param.getSource());
                return generateTokenAndReturn(fsUser);
            }
        }
        return R.error("账号不匹配，请检查账号");
    }

    /**
     * 更新用户登录设备信息
     * @param userId 用户ID
     * @param loginDevice 登录设备
     * @param source 来源
     */
    private void updateLoginDevice(Long userId, String loginDevice, String source) {
        FsUser updateUser = new FsUser();
        updateUser.setUserId(userId);
        if (StringUtils.isNotBlank(loginDevice)) {
            updateUser.setLoginDevice(loginDevice);
        }
        if (StringUtils.isNotBlank(source)) {
            updateUser.setSource(source);
        }
        updateUser.setLastIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        userService.updateFsUser(updateUser);
    }

    /**
     * UniApp 一键登录（安全）：手机号已由 uniCloud 校验，不信任客户端传入
     * <ul>
     *   <li>无用户 → 自动注册新用户</li>
     *   <li>单用户 → 直接登录</li>
     *   <li>多用户 → 返回 users 列表，前端选择后调 /loginByUserIdUniverify</li>
     * </ul>
     */
    private R handleSecurePhoneLogin(FsUserLoginParam param) {
        String phone = param.getPhone();
        List<FsUser> usersByPhone = findUsersByPhone(phone);
        if (CollectionUtil.isEmpty(usersByPhone)) {
            // 新用户自动注册
            String appid = openProperties.getAppId();
            param.setAppId(appid);
            FsUser newUser = createNewUser(param);
            if (StringUtils.isNotEmpty(param.getJpushId())) {
                updateExistingUserJpushId(newUser, param.getJpushId());
            }
            updateLoginDevice(newUser.getUserId(), param.getLoginDevice(), param.getSource());
            R r = generateTokenAndReturn(newUser);
            r.put("isNew", true);
            return r;
        }
        if (usersByPhone.size() == 1) {
            FsUser user = usersByPhone.get(0);
            // 历史用户首次一键登录：手机号还是明文，加密后落库
            if (user.getUnionId() == null) {
                if (user.getPhone() != null && user.getPhone().length() <= 11) {
                    FsUser fsUser = new FsUser();
                    fsUser.setUserId(user.getUserId());
                    fsUser.setPhone(encryptPhone(phone));
                    userMapper.updateFsUser(fsUser);
                }
                return R.ok().put("isNew", true).put("phone", encryptPhone(phone));
            }
            if (StringUtils.isNotEmpty(param.getJpushId())) {
                updateExistingUserJpushId(user, param.getJpushId());
            }
            updateLoginDevice(user.getUserId(), param.getLoginDevice(), param.getSource());
            return generateTokenAndReturn(user);
        }
        // 多账号场景，脱敏后返回让前端选择
        for (FsUser fsUser : usersByPhone) {
            fsUser.setPhone("***********");
        }
        return R.ok().put("users", usersByPhone);
    }

    private R handleLoginType4(FsUserLoginParam param) {
        if (param.getUserId() == null || StringUtils.isEmpty(param.getSign()) || param.getTimestamp() == null) {
            return R.error("登录参数不完整");
        }
        // 验签
        JSONObject reqJson = new JSONObject();
        reqJson.put("userId", param.getUserId());
        if (!SignatureUtil.verifySignature(param.getTimestamp(), reqJson.toString(), param.getSign())) {
            return R.error("签名验证失败");
        }
        // 签名一次性：同一 sign 5 分钟内只能换票一次，防止截包重放
        String usedKey = "lotus:login:sign:" + param.getSign();
        if (!redisCache.setIfAbsent(usedKey, "1", 300, TimeUnit.SECONDS)) {
            return R.error("登录凭证已失效，请重新授权");
        }
        Long userId = param.getUserId();
        FsUser fsUser = userMapper.selectFsUserByUserId(userId);
        if (fsUser == null) {
            return R.error("用户不存在");
        }
        String token = jwtUtils.generateToken(userId);
        redisCache.setCacheObject("userToken:" + userId, token, 604800, TimeUnit.SECONDS);
        JSONObject res = new JSONObject();
        res.put("token", token);
        res.put("user", fsUser);
        return R.ok().put("data", res);
    }

}
