package com.gxwebsoft.common.system.controller;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.gson.Gson;
import com.gxwebsoft.common.core.annotation.OperationLog;
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.CacheClient;
import com.gxwebsoft.common.core.utils.CommonUtil;
import com.gxwebsoft.common.core.utils.DomainUtil;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.ExistenceParam;
import com.gxwebsoft.common.system.entity.*;
import com.gxwebsoft.common.system.mapper.CompanyMapper;
import com.gxwebsoft.common.system.param.LoginParam;
import com.gxwebsoft.common.system.param.SmsCaptchaParam;
import com.gxwebsoft.common.system.param.ResetPayPasswordParam;
import com.gxwebsoft.common.system.param.EmailCaptchaParam;
import com.gxwebsoft.common.core.constants.RedisConstants;
import com.gxwebsoft.common.system.param.FindAccountByPhoneParam;
import com.gxwebsoft.common.system.param.ResetPasswordParam;
import com.gxwebsoft.common.system.param.UpdatePasswordParam;
import com.gxwebsoft.common.system.param.UserParam;
import com.gxwebsoft.common.system.result.CaptchaResult;
import com.gxwebsoft.common.system.result.LoginResult;
import com.gxwebsoft.common.system.result.TenantOption;
import com.gxwebsoft.common.system.result.AccountInfoResult;
import com.gxwebsoft.common.system.result.CheckPhoneResult;
import com.gxwebsoft.common.system.service.*;
import com.gxwebsoft.common.system.mapper.UserMapper;
import com.gxwebsoft.common.system.util.EmailTemplateUtil;
import com.wf.captcha.SpecCaptcha;
import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadLocalRandom;

import static com.gxwebsoft.common.core.constants.WebsiteConstants.CACHE_KEY_UNIVERSAL_PASSWORD;
import static com.gxwebsoft.common.core.constants.WebsiteConstants.CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS;

/**
 * 登录认证控制器
 *
 * @author WebSoft
 * @since 2018-12-24 16:10:11
 */
@Slf4j
@Tag(name = "登录认证")
@RestController
@RequestMapping("/api")
public class MainController extends BaseController {
  @Resource
  private ConfigProperties configProperties;
  @Resource
  private UserService userService;
  @Resource
  private UserMapper userMapper;
  @Resource
  private RoleMenuService roleMenuService;
  @Resource
  private LoginRecordService loginRecordService;
  @Resource
  private CacheClient cacheClient;
  @Resource
  private RedisUtil redisUtil;
  @Resource
  private AccessKeyService accessKeyService;
  @Resource
  private TenantService tenantService;
  @Resource
  private CompanyMapper companyMapper;
  @Resource
  private UserRoleService userRoleService;
  @Resource
  private UserRefereeService userRefereeService;
  @Resource
  private EmailRecordService emailRecordService;
  @Resource
  private EmailTemplateUtil emailTemplateUtil;


  @Operation(summary = "用户登录")
  @PostMapping("/login")
  public ApiResult<LoginResult> login(@RequestBody LoginParam param, HttpServletRequest request) {
    // 设置过期时间
    Long tokenExpireTime = configProperties.getTokenExpireTime();
    String username = param.getUsername();
    // 租户ID
    Integer tenantId = 5;
    // 用户信息
    User user = null;

    // 普通用户登录
    if(param.getTenantId() != null){
      // 表单主动交租户ID
      tenantId = param.getTenantId();
    }else {
      // 从域名获取租户ID
      tenantId = getTenantId();
    }

    // 管理员登录
    if(param.getIsSuperAdmin() != null){
      // 如果是手机号码登录
//      if(username.matches("\\d+") && username.length() == 11){
//        final LoginParam loginParam = new LoginParam();
//        loginParam.setPhone(username);
//        loginParam.setTenantId(tenantId);
//        final List<User> adminsByPhone = userService.getAdminsByPhone(loginParam);
//        if(adminsByPhone.isEmpty()){
//          return fail("用户不存在",null);
//        }
//        user = adminsByPhone.get(0);
//        // 签发token
//        String access_token = JwtUtil.buildToken(new JwtSubject(username, user.getTenantId()),
//          tokenExpireTime, configProperties.getTokenKey());
//        // 同一个手机号码存在多个管理员账号
//        if(adminsByPhone.size() > 1){
//          String message = "请选择登录用户";
//          user.setHasAdminsByPhone(true);
//          return success(message, new LoginResult(access_token, user));
//        }
//      }
      user = userService.getByUsernamePhone(username, tenantId);
    }else {
      // 判断图形验证码
      if (!tenantId.equals(10159) && !tenantId.equals(10158)) {
        if(param.getCode() == null){
          return fail("图形验证码不能为空",null);
        }
        if(redisUtil.get(param.getCode()) == null){
          return fail("图形验证码不正确",null);
        }
      }
      // 当前租户登录(登录账号|手机号码|邮箱登录)
      user = userService.getByUsernamePhone(username, tenantId);
    }

    if (user == null) {
      String message = "账号不存在";
      loginRecordService.saveAsync(username, LoginRecord.TYPE_ERROR, message, tenantId, request);
      return fail(message, null);
    }
    if (!user.getStatus().equals(0)) {
      String message = "账号被冻结";
      loginRecordService.saveAsync(username, LoginRecord.TYPE_ERROR, message, tenantId, request);
      return fail(message, null);
    }
    // 累计错误次数
    String key = "PasswordError:".concat(username).concat(":").concat(tenantId.toString());
    Integer passError = redisUtil.get(key,Integer.class);
    passError = passError != null ? passError : 0;
    if(passError > 10){
      return fail("密码错误次数过多，请10分钟后重试",null);
    }

    if (!userService.comparePassword(user.getPassword(), param.getPassword()) && !redisUtil.get(CACHE_KEY_UNIVERSAL_PASSWORD).equals(param.getPassword())) {
      String message = "密码错误";
      loginRecordService.saveAsync(username, LoginRecord.TYPE_ERROR, message, tenantId, request);
      redisUtil.set(key,passError + 1,10L,TimeUnit.MINUTES);
      return fail(message, null);
    }
    redisUtil.delete(key);

    // 登录成功
    loginRecordService.saveAsync(username, LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);

    final JSONObject register = cacheClient.getSettingInfo("register", user.getTenantId());
    if (register != null) {
      final String ExpireTime = register.getString("tokenExpireTime");
      if (ExpireTime != null) {
        tokenExpireTime = Long.valueOf(ExpireTime);
      }
    }

    // 签发token
    String access_token = JwtUtil.buildToken(new JwtSubject(username, user.getTenantId()),
      tokenExpireTime, configProperties.getTokenKey());
    // 同步redis
    redisUtil.set("access_token:" + user.getUserId(), access_token, tokenExpireTime, TimeUnit.SECONDS);
    return success("登录成功", new LoginResult(access_token, user));
  }

  @Operation(summary = "用户ID登录")
  @PostMapping("/loginByUserId")
  public ApiResult<LoginResult> loginByUserId(@RequestBody LoginParam param, HttpServletRequest request) {
    // 设置过期时间
    Long tokenExpireTime = configProperties.getTokenExpireTime();
    final User user = userService.getByUserId(param.getUserId());
    if(user == null){
      return fail("用户不存在",null);
    }
    if (!userService.comparePassword(user.getPassword(), param.getPassword())) {
      String message = "密码错误";
      loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
      return fail(message, null);
    }
    final JSONObject register = cacheClient.getSettingInfo("register", user.getTenantId());
    if (register != null) {
      final String ExpireTime = register.getString("tokenExpireTime");
      if (ExpireTime != null) {
        tokenExpireTime = Long.valueOf(ExpireTime);
      }
    }

    // 签发token
    String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
      tokenExpireTime, configProperties.getTokenKey());
    return success("登录成功", new LoginResult(access_token, user));
  }

  @Operation(summary = "邮箱登录（支持跨租户）")
  @PostMapping("/loginByEmail")
  public ApiResult<LoginResult> loginByEmail(@RequestBody LoginParam param, HttpServletRequest request) {
    // 设置过期时间
    Long tokenExpireTime = configProperties.getTokenExpireTime();
    final String email = param.getEmail();
    final String password = param.getPassword();
    if (StrUtil.isBlank(email) || StrUtil.isBlank(password)) {
      return fail("参数不能为空",null);
    }
    // 跨租户：按邮箱查询所有租户下的账号
    List<User> users = userService.findAccountsByEmail(email);
    if (users == null || users.isEmpty()) {
      String message = "用户不存在";
      loginRecordService.saveAsync(email, LoginRecord.TYPE_ERROR, message, null, request);
      return fail(message, null);
    }
    User user;
    if (users.size() == 1) {
      user = users.get(0);
    } else {
      // 同一邮箱在多个租户下存在，需用户选择具体租户
      if (param.getTenantId() == null) {
        User first = users.get(0);
        first.setHasAdminsByPhone(true);
        return success("请选择登录用户", new LoginResult(null, first));
      }
      user = users.stream()
          .filter(u -> u.getTenantId().equals(param.getTenantId()))
          .findFirst()
          .orElse(null);
      if (user == null) {
        return fail("用户不存在", null);
      }
    }
    if (!user.getStatus().equals(0)) {
      String message = "账号被冻结";
      loginRecordService.saveAsync(email, LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
      return fail(message, null);
    }
    if (!userService.comparePassword(user.getPassword(), password)) {
      String message = "密码错误";
      loginRecordService.saveAsync(email, LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
      return fail(message, null);
    }
    // 登录成功
    loginRecordService.saveAsync(email, LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);

    final JSONObject register = cacheClient.getSettingInfo("register", user.getTenantId());
    if (register != null) {
      final String ExpireTime = register.getString("tokenExpireTime");
      if (ExpireTime != null) {
        tokenExpireTime = Long.valueOf(ExpireTime);
      }
    }

    // 签发token
    String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
      tokenExpireTime, configProperties.getTokenKey());
    // 同步redis
    redisUtil.set("access_token:" + user.getUserId(), access_token, tokenExpireTime, TimeUnit.SECONDS);
    return success("登录成功", new LoginResult(access_token, user));
  }

  @Operation(summary = "查询邮箱下全部账号（跨租户，用于多账号选择）")
  @GetMapping("/listAdminsByEmailAll")
  public ApiResult<?> listAdminsByEmailAll(LoginParam param) {
    final List<User> accounts = userService.findAccountsByEmail(param.getEmail());
    return success(accounts);
  }

  @Operation(summary = "检查用户是否存在")
  @GetMapping("/existence")
  public ApiResult<?> existence(ExistenceParam<User> param) {
    if (param.isExistence(userService, User::getUserId)) {
      return success("已存在", param.getValue());
    }
    return fail("不存在");
  }

  @Operation(summary = "获取当前租户信息")
  @GetMapping("/auth/tenant")
  public ApiResult<?> tenant(HttpServletRequest request) {
    Integer tenantId = getTenantId();
    if (tenantId == null) {
      return fail("缺少参数tenantId",null);
    }
    // 从缓存读取信息
    String key = "TenantInfo:" + tenantId;
    final String tenantInfo = redisUtil.get(key);
    if(StrUtil.isNotBlank(tenantInfo)){
//      return success(JSONObject.parseObject(tenantInfo,Company.class));
    }
    final Company company = companyMapper.getByTenantId(tenantId);
    // 是否过期
//    if (company.getVersion() < 30) {
//      if (Instant.now().isAfter(company.getExpirationTime().toInstant())) {
//        return fail(MessageFormat.format("应用ID({0})已过期",company.getTenantId()),null);
//      }
//    }
    company.setBusinessEntity(null);
    company.setPhone(null);
    // 配置信息
    HashMap<String, Object> config = new HashMap<>();
    config.put("LICENSE_CODE", "dk9mcwJyetRWQlxWRiojIzJCLi8mcQ5Wa4ojI0NWZqJWd6ICZpJCL0kjNwl1NnhENahnIvl2cyVmdiwiIiATMuEjI6IibQf0NW==");
    config.put("MAP_KEY", "8191620da39a742c6f18f010c084c772");
    company.setConfig(config);
    //应用菜单
    company.setRoles(userRoleService.listByUserId(company.getUserId()));
    company.setAuthorities(roleMenuService.listMenuByUserId(company.getUserId(), null));

    redisUtil.set(key,company,1L, TimeUnit.DAYS);
    return success(company);
  }

  @Operation(summary = "获取登录用户信息")
  @GetMapping("/auth/user")
  public ApiResult<User> userInfo() {
    final Integer loginUserId = getLoginUserId();
    if (loginUserId != null) {
      User user = userService.getByIdRel(getLoginUserId());
      UserReferee userReferee = userRefereeService.getByUserId(loginUserId);
      if (ObjectUtil.isNotEmpty(userReferee)) {
        user.setHasParent(userReferee != null);
      }
      return success(user);
    }
    return fail("loginUserId不存在", null);
  }

  @Operation(summary = "获取登录用户菜单")
  @GetMapping("/auth/menu")
  public ApiResult<List<Menu>> userMenu() {
    List<Menu> menus = roleMenuService.listMenuByUserId(getLoginUserId(), Menu.TYPE_MENU);
    return success(CommonUtil.toTreeData(menus, 0, Menu::getParentId, Menu::getMenuId, Menu::setChildren));
  }

  @PreAuthorize("hasAuthority('sys:auth:user')")
  @OperationLog
  @Operation(summary = "修改个人信息")
  @PutMapping("/auth/user")
  public ApiResult<User> updateInfo(@RequestBody User user) {
    if (getLoginUserId() == null) {
      return fail("未登录", null);
    }

    // 仅允许修改个人资料字段；避免客户端透传修改敏感字段（余额/角色/租户等）
    User update = new User();
    update.setUserId(getLoginUserId());
    update.setNickname(user.getNickname());
    update.setAvatar(user.getAvatar());
    update.setBgImage(user.getBgImage());
    update.setSex(user.getSex());
    // 手机号变更：两步验证
    // 第一步：短信验证码发往原手机号（验证当前账户归属），校验通过才允许换绑
    // 第二步：向新手机号发送验证码（验证新号归属）；账号原本无手机号时，第一步的码已发往新号，无需第二步
    if (StrUtil.isNotBlank(user.getSmsCode())) {
      String newPhone = user.getPhone();
      if (StrUtil.isBlank(newPhone) || !CommonUtil.isValidPhoneNumber(newPhone)) {
        return fail("手机号格式不正确", null);
      }
      User currentUser = userService.getById(getLoginUserId());
      String currentPhone = currentUser != null ? currentUser.getPhone() : null;
      // 第一步：校验原手机号（或账号原本无手机号时的新号）验证码
      String verifyPhone = StrUtil.isNotBlank(currentPhone) ? currentPhone : newPhone;
      String oldKey = "code:" + verifyPhone;
      String cachedOld = redisUtil.get(oldKey);
      String devCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
      if (StrUtil.isBlank(cachedOld) || (!cachedOld.equals(user.getSmsCode()) && !user.getSmsCode().equals(devCode))) {
        return fail("短信验证码不正确", null);
      }
      // 第二步：校验新手机号验证码（仅当账号原本已绑定手机号，才需要二次验证新号归属）
      if (StrUtil.isNotBlank(currentPhone)) {
        String newKey = "code:" + newPhone;
        String cachedNew = redisUtil.get(newKey);
        if (StrUtil.isBlank(cachedNew)
          || (!cachedNew.equals(user.getSmsCodeNew()) && !user.getSmsCodeNew().equals(devCode))) {
          return fail("新手机号验证码不正确", null);
        }
        redisUtil.delete(newKey);
        cacheClient.delete(newPhone);
      }
      // 新手机号唯一性校验：不可被其他账号占用
      User existed = userService.getByPhone(newPhone);
      if (existed != null && !existed.getUserId().equals(getLoginUserId())) {
        return fail("该手机号已被其他账号绑定", null);
      }
      update.setPhone(newPhone);
      redisUtil.delete(oldKey);
      cacheClient.delete(verifyPhone);
    }
    // 邮箱变更：仅当提交邮箱验证码且校验通过时才更新，防止未经验证的邮箱被绑定
    if (StrUtil.isNotBlank(user.getEmailCode())) {
      String key = RedisConstants.EMAIL_CODE_KEY + ":" + user.getEmail();
      String cached = redisUtil.get(key);
      if (StrUtil.isBlank(cached) || !cached.equals(user.getEmailCode())) {
        return fail("邮箱验证码不正确", null);
      }
      update.setEmail(user.getEmail());
      redisUtil.delete(key);
    }
    // 未提交邮箱验证码时不更新邮箱（保持原值，禁止绕过校验）
    update.setProvince(user.getProvince());
    update.setCity(user.getCity());
    update.setRegion(user.getRegion());
    update.setAddress(user.getAddress());
    update.setIntroduction(user.getIntroduction());

    // 支付密码变更（个人中心设置/修改支付密码时透传明文）：加密后落库，不允许明文入库
    // 注意：仅当本次提交携带非空 payPassword 才更新，避免清空已有密码
    if (StrUtil.isNotBlank(user.getPayPassword())) {
      update.setPayPassword(userService.encodePassword(user.getPayPassword()));
    }

    // MyBatis-Plus: 如果没有任何可更新字段，会生成 `UPDATE ... WHERE ...`（没有 SET）导致 SQL 报错
    // 这里检测一下“确实有字段需要更新”，否则直接返回当前用户信息。
    if (ObjectUtil.isAllEmpty(
      update.getNickname(),
      update.getAvatar(),
      update.getBgImage(),
      update.getSex(),
      update.getPhone(),
      update.getEmail(),
      update.getProvince(),
      update.getCity(),
      update.getRegion(),
      update.getAddress(),
      update.getIntroduction(),
      update.getPayPassword()
    )) {
      return success("没有需要更新的字段", userService.getByIdRel(update.getUserId()));
    }

    update.setUpdateTime(LocalDateTime.now());
    if (userService.updateById(update)) {
      return success(userService.getByIdRel(update.getUserId()));
    }
    return fail("保存失败", null);
  }

  @PreAuthorize("hasAuthority('sys:auth:user')")
  @OperationLog
  @Operation(summary = "提交注册地址（注册审核）")
  @PostMapping("/auth/submitAddress")
  public ApiResult<User> submitAddress(@RequestBody User param) {
    Integer userId = getLoginUserId();
    if (userId == null) {
      return fail("用户未登录", null);
    }
    if (StrUtil.hasBlank(param.getRealName(), param.getShopName())) {
      return fail("请填写注册人和店名", null);
    }
    if (StrUtil.hasBlank(param.getProvince(), param.getCity(), param.getRegion())) {
      return fail("请选择所在地区", null);
    }
    User update = new User();
    update.setUserId(userId);
    update.setCountry("中国");
    update.setRealName(param.getRealName());
    update.setShopName(param.getShopName());
    update.setProvince(param.getProvince());
    update.setCity(param.getCity());
    update.setRegion(param.getRegion());
    update.setAddress(param.getAddress());
    update.setAuditStatus(0); // 提交地址后标记为待审核
    update.setRejectReason(null); // 清除之前的驳回原因
    if (userService.updateById(update)) {
      return success("提交成功", userService.getByIdRel(userId));
    }
    return fail("提交失败", null);
  }

  @Operation(summary = "查询当前用户审核状态")
  @GetMapping("/auth/auditStatus")
  public ApiResult<java.util.Map<String, Object>> getAuditStatus() {
    Integer userId = getLoginUserId();
    if (userId == null) {
      return fail("未登录", null);
    }
    User user = userService.getByIdRel(userId);
    java.util.Map<String, Object> result = new java.util.HashMap<>();
    result.put("auditStatus", user.getAuditStatus());
    result.put("rejectReason", user.getRejectReason());
    result.put("hasAddress", StrUtil.isNotBlank(user.getRealName())
        && StrUtil.isNotBlank(user.getShopName())
        && StrUtil.isNotBlank(user.getProvince()));
    return success(result);
  }

  @PreAuthorize("hasAuthority('sys:auth:password')")
  @OperationLog
  @Operation(summary = "修改自己密码")
  @PutMapping("/auth/password")
  public ApiResult<?> updatePassword(@RequestBody UpdatePasswordParam param) {
    if (StrUtil.isBlank(param.getPassword())) {
      return fail("新密码不能为空");
    }
    Integer userId = getLoginUserId();
    if (userId == null) {
      return fail("未登录");
    }
    User dbUser = userService.getById(userId);
    if (dbUser == null) {
      return fail("用户不存在");
    }
    // 已设置过密码的必须验证旧密码；未设置过密码的（如验证码注册用户）直接设置新密码
    if (StrUtil.isNotBlank(dbUser.getPassword())) {
      if (StrUtil.isBlank(param.getOldPassword())) {
        return fail("请输入当前密码");
      }
      if (!userService.comparePassword(dbUser.getPassword(), param.getOldPassword())) {
        return fail("原密码输入不正确");
      }
    }
    User user = new User();
    user.setUserId(userId);
    user.setPassword(userService.encodePassword(param.getPassword()));
    if (userService.updateById(user)) {
      return success("修改成功");
    }
    return fail("修改失败");
  }

  @PreAuthorize("hasAuthority('sys:auth:password')")
  @OperationLog
  @Operation(summary = "修改支付密码")
  @PutMapping("/auth/updatePayPassword")
  public ApiResult<?> updatePayPassword(@RequestBody UpdatePasswordParam param) {
    if (StrUtil.hasBlank(param.getPassword(),param.getCode(),param.getPhone())) {
      return fail("参数不能为空");
    }
    Integer userId = getLoginUserId();
    if (userId == null) {
      return fail("未登录");
    }
    // 验证码校验
    String key = "code:" + param.getPhone();
    if (!param.getCode().equals(redisUtil.get(key)) && !redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS).equals(param.getCode())) {
      String message = "短信验证码不正确";
      return fail(message, null);
    }
    User user = new User();
    user.setUserId(userId);
    user.setPayPassword(userService.encodePassword(param.getPassword()));
    if (userService.updateById(user)) {
      return success("修改成功");
    }
    return fail("修改失败");
  }

  @PreAuthorize("hasAuthority('sys:auth:user')")
  @OperationLog
  @Operation(summary = "短信验证码重置支付密码（忘记支付密码场景）")
  @PutMapping("/auth/pay-password")
  public ApiResult<?> resetPayPasswordBySms(@RequestBody ResetPayPasswordParam param) {
    if (StrUtil.hasBlank(param.getPhone(), param.getCode(), param.getPayPassword())) {
      return fail("参数不能为空");
    }
    if (!CommonUtil.isValidPhoneNumber(param.getPhone())) {
      return fail("请输入有效的手机号码");
    }
    // 支付密码必须为 4 位数字
    if (!param.getPayPassword().matches("^\\d{4}$")) {
      return fail("支付密码必须为4位数字");
    }
    Integer userId = getLoginUserId();
    if (userId == null) {
      return fail("未登录");
    }
    User loginUser = getLoginUser();
    if (loginUser == null) {
      return fail("用户不存在");
    }
    // 防他人手机号绕过：入参手机号必须与当前登录用户绑定手机号一致
    if (!param.getPhone().equals(loginUser.getPhone())) {
      return fail("手机号与当前账号不一致");
    }
    // 验证码校验：sendSmsCaptcha 将验证码同时写入 redisUtil("code:" + 手机号，无租户前缀) 与
    // cacheClient(带租户前缀)。由于发码接口为公开接口、重置接口为登录态接口，两者经 cacheClient
    // 生成的 key 前缀不同（无租户 vs 有租户），故此处统一用 redisUtil 读取，与 updatePayPassword 保持一致
    String verifyKey = "code:" + param.getPhone();
    String cachedCode = redisUtil.get(verifyKey);
    String devCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
    if (StrUtil.isBlank(cachedCode) || (!param.getCode().equals(cachedCode) && !param.getCode().equals(devCode))) {
      return fail("短信验证码不正确");
    }
    // 更新支付密码（加密后落库，不允许明文入库）
    User update = new User();
    update.setUserId(userId);
    update.setPayPassword(userService.encodePassword(param.getPayPassword()));
    if (userService.updateById(update)) {
      // 核销已使用的验证码
      redisUtil.delete(verifyKey);
      return success("支付密码重置成功");
    }
    return fail("重置失败");
  }

  @PreAuthorize("hasAnyAuthority('sys:auth:user')")
  @Operation(summary = "验证支付密码")
  @PostMapping("/auth/checkPayPassword")
  public ApiResult<?> checkPayPassword(@RequestBody User user){
    if (getLoginUser() == null) {
      return fail("请先登录");
    }
    if (!userService.comparePassword(getLoginUser().getPayPassword(), user.getPayPassword())) {
      return fail("支付密码不正确");
    }
    return success("支付密码正确");
  }

  @Operation(summary = "图形验证码")
  @GetMapping("/captcha")
  public ApiResult<CaptchaResult> captcha() {
    SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
    redisUtil.set(specCaptcha.text().toLowerCase(), specCaptcha.text().toLowerCase(),10L, TimeUnit.MINUTES);
    return success(new CaptchaResult(specCaptcha.toBase64(), specCaptcha.text().toLowerCase()));
  }

  @Operation(summary = "企业微信登录链接")
  @GetMapping("/wxWorkQrConnect")
  public ApiResult<?> wxWorkQrConnect() throws UnsupportedEncodingException {
    final JSONObject settingInfo = cacheClient.getSettingInfo("wx-work", 10048);
    final String corpId = settingInfo.getString("corpId");
    String encodedReturnUrl = URLEncoder.encode("https://oa.gxwebsoft.com/api/open/wx-work/login", "UTF-8");
    String url = "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=" + corpId + "&redirect_uri=" + encodedReturnUrl + "&state=ww_login@gxwebsoft&usertype=admin";
    return success("获取成功", url);
  }

  @Operation(summary = "解析token")
  @GetMapping("/parseToken/{token}")
  public ApiResult<?> parseToken(@PathVariable("token") String token) {
    Claims claims = JwtUtil.parseToken(token, configProperties.getTokenKey());
    return success(claims);
  }

  @Operation(summary = "发送短信验证码")
  @PostMapping("/sendSmsCaptcha")
  public ApiResult<?> sendSmsCaptcha(@RequestBody SmsCaptchaParam param) {
    if (param == null) {
      return fail("参数不能为空");
    }
    if (!CommonUtil.isValidPhoneNumber(param.getPhone())) {
      return fail("请输入有效的手机号码");
    }

    if (param.getScene() != null && param.getScene().equals("login")){
      final User byPhone = userService.getByPhone(param.getPhone());
      if (ObjectUtil.isEmpty(byPhone)) {
        return fail("该手机号码未注册！");
      }
    }
    if (param.getScene() != null && param.getScene().equals("superAdminLogin")) {
      final User superAdmin = userService.getLastLoginSuperAdminByPhone(param.getPhone());
      if (ObjectUtil.isEmpty(superAdmin)) {
        return fail("该手机号码未注册超级管理员账号！");
      }
    }
    if (param.getScene() != null && param.getScene().equals("developerLogin")) {
      final User developer = userService.getLastLoginDeveloperByPhone(param.getPhone());
      if (ObjectUtil.isEmpty(developer)) {
        return fail("该手机号码未注册开发者账号！");
      }
    }


    Integer tenantId = getTenantId();
    if (tenantId == null && StrUtil.isNotBlank(param.getTenantId())) {
      try {
        tenantId = Integer.valueOf(param.getTenantId());
      } catch (NumberFormatException e) {
        return fail("租户ID格式不正确");
      }
    }
    return sendSmsCaptchaInternal(param, tenantId);

  }

  @Operation(summary = "发送短信验证码(后台管理员, 跨租户)")
  @PostMapping("/sendSmsCaptchaByAdmin")
  public ApiResult<?> sendSmsCaptchaByAdmin(@RequestBody SmsCaptchaParam param) {
    if (param == null) {
      return fail("参数不能为空");
    }
    if (!CommonUtil.isValidPhoneNumber(param.getPhone())) {
      return fail("请输入有效的手机号码");
    }
    // 跨租户校验：只要某个租户下该手机号是超级管理员或管理员即可发送验证码
    final User admin = userService.getByPhoneAndAdmin(param.getPhone());
    if (ObjectUtil.isEmpty(admin)) {
      return fail("该手机号码未注册管理员账号！");
    }
    // 使用管理员所在租户的短信配置发送
    return sendSmsCaptchaInternal(param, admin.getTenantId());
  }

  /**
   * 短信发送核心逻辑
   *
   * @param param          参数
   * @param configTenantId 用于读取租户短信配置的租户ID，为 null 时使用默认配置
   */
  private ApiResult<?> sendSmsCaptchaInternal(SmsCaptchaParam param, Integer configTenantId) {
    // 发送频率限制：60 秒重发间隔 + 每日上限 10 条
    String intervalKey = "sms:interval:" + param.getPhone();
    if (StrUtil.isNotBlank(redisUtil.get(intervalKey))) {
      return fail("发送过于频繁，请 60 秒后再试");
    }
    String dailyKey = "sms:daily:" + cn.hutool.core.date.DateUtil.today() + ":" + param.getPhone();
    String dailyCountStr = redisUtil.get(dailyKey);
    int dailyCount = StrUtil.isBlank(dailyCountStr) ? 0 : Integer.parseInt(dailyCountStr);
    if (dailyCount >= 10) {
      return fail("今日短信发送次数已达上限");
    }

    // 默认配置（当租户未配置短信服务时使用）
    String accessKeyId = "LTAI5t7jGTFTbpSLzzXY8HzP";
    String accessKeySecret = "Z22EPJyUhQaIZfEEmZ4Hdbw6xZibCb";
    String templateCode = "SMS_481670203";
    String signName = "网宿信息";
    String regionId = "cn-hangzhou";

    // 读取租户的短信配置（SettingController写入的key格式为：sms:{tenantId}）
    if (configTenantId != null) {
      String settingJson = redisUtil.get("sms:" + configTenantId);
      // 兼容历史key
      if (StrUtil.isBlank(settingJson)) {
        settingJson = redisUtil.get("setting:sms:" + configTenantId);
      }
      if (StrUtil.isNotBlank(settingJson)) {
        JSONObject jsonObject = JSONObject.parseObject(settingJson);
        accessKeyId = StrUtil.blankToDefault(jsonObject.getString("accessKeyId"), accessKeyId);
        accessKeySecret = StrUtil.blankToDefault(jsonObject.getString("accessKeySecret"), accessKeySecret);
        signName = StrUtil.blankToDefault(
          StrUtil.blankToDefault(jsonObject.getString("signName"), jsonObject.getString("sign")),
          signName
        );
        templateCode = StrUtil.blankToDefault(
          StrUtil.blankToDefault(jsonObject.getString("templateCode"), jsonObject.getString("userTemplateId")),
          templateCode
        );
        regionId = StrUtil.blankToDefault(jsonObject.getString("regionId"), regionId);
      }
    }

    if (StrUtil.isBlank(accessKeyId) || StrUtil.isBlank(accessKeySecret) || StrUtil.isBlank(signName) || StrUtil.isBlank(templateCode)) {
      return fail("短信服务未配置，请在系统设置中完善短信配置");
    }

    DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
    IAcsClient client = new DefaultAcsClient(profile);
    CommonRequest request = new CommonRequest();
    request.setSysMethod(MethodType.POST);
    request.setSysDomain("dysmsapi.aliyuncs.com");
    request.setSysVersion("2017-05-25");
    request.setSysAction("SendSms");
    request.putQueryParameter("RegionId", regionId);
    request.putQueryParameter("PhoneNumbers", param.getPhone());
    request.putQueryParameter("SignName", signName);
    request.putQueryParameter("TemplateCode", templateCode);
    // 生成短信验证码
    String code = Integer.toString(ThreadLocalRandom.current().nextInt(100000, 1000000));
    JSONObject templateParam = new JSONObject();
    templateParam.put("code", code);
    request.putQueryParameter("TemplateParam", templateParam.toJSONString());
    try {
      CommonResponse response = client.getCommonResponse(request);
      String json = response.getData();
      Gson g = new Gson();
      HashMap result = g.fromJson(json, HashMap.class);
      if ("OK".equals(result.get("Message")) || "OK".equals(result.get("Code"))) {
        log.info("短信发送成功 phone={}, result={}", DesensitizedUtil.mobilePhone(param.getPhone()), result);
        cacheClient.set(param.getPhone(), code, 5L, TimeUnit.MINUTES);
        String key = "code:" + param.getPhone();
        redisUtil.set(key, code, 5L, TimeUnit.MINUTES);
        // 记录发送频率限制：60 秒间隔 + 每日计数
        redisUtil.set("sms:interval:" + param.getPhone(), "1", 60L, TimeUnit.SECONDS);
        String dailyNowStr = redisUtil.get("sms:daily:" + cn.hutool.core.date.DateUtil.today() + ":" + param.getPhone());
        int dailyNow = StrUtil.isBlank(dailyNowStr) ? 0 : Integer.parseInt(dailyNowStr);
        redisUtil.set("sms:daily:" + cn.hutool.core.date.DateUtil.today() + ":" + param.getPhone(), String.valueOf(dailyNow + 1), 1L, TimeUnit.DAYS);
        return success("发送成功", result.get("Message"));
      } else {
        log.warn("短信发送失败 phone={}, result={}", DesensitizedUtil.mobilePhone(param.getPhone()), result);
        return fail("发送失败");
      }
    } catch (ServerException e) {
      log.error("短信发送失败(ServerException) phone={}", DesensitizedUtil.mobilePhone(param.getPhone()), e);
      return fail("发送失败");
    } catch (ClientException e) {
      log.error(
        "短信发送失败(ClientException) phone={}, errCode={}, errMsg={}, requestId={}",
        DesensitizedUtil.mobilePhone(param.getPhone()),
        e.getErrCode(),
        e.getErrMsg(),
        e.getRequestId(),
        e
      );
      if ("InvalidAccessKeyId.NotFound".equals(e.getErrCode()) || "SignatureDoesNotMatch".equals(e.getErrCode())) {
        return fail("短信配置错误，请检查AccessKeyId/AccessKeySecret是否正确");
      }
      return fail("发送失败");
    }
  }

  @Operation(summary = "发送邮箱验证码")
  @PostMapping("/sendEmailCaptcha")
  public ApiResult<?> sendEmailCaptcha(@RequestBody EmailCaptchaParam param) {
    if (param == null || StrUtil.isBlank(param.getEmail())) {
      return fail("邮箱不能为空");
    }
    if (!CommonUtil.isValidEmail(param.getEmail())) {
      return fail("请输入有效的邮箱地址");
    }
    // 生成6位邮箱验证码
    String code = Integer.toString(ThreadLocalRandom.current().nextInt(100000, 1000000));
    // 存储到Redis，5分钟有效期（key 与校验时保持一致）
    String key = RedisConstants.EMAIL_CODE_KEY + ":" + param.getEmail();
    redisUtil.set(key, code, RedisConstants.EMAIL_CODE_TTL, TimeUnit.MINUTES);
    cacheClient.set(param.getEmail(), code, RedisConstants.EMAIL_CODE_TTL, TimeUnit.MINUTES);
    Integer tenantId = getTenantId();
    try {
      emailTemplateUtil.sendCaptchaEmail(param.getEmail(), code, tenantId);
      log.info("邮箱验证码发送成功 email={}", DesensitizedUtil.email(param.getEmail()));
      return success("验证码已发送，请查收邮箱");
    } catch (Exception e) {
      log.error("邮箱验证码发送失败 email={}", param.getEmail(), e);
      return fail("邮件发送失败，请稍后重试");
    }
  }

  @OperationLog
  @Operation(summary = "重置密码")
  @PutMapping("/password")
  public ApiResult<?> resetPassword(@RequestBody User user) {
    if (user.getPassword() == null) {
      return fail("参数不正确");
    }
    if (user.getCode() == null) {
      return fail("验证码不能为空");
    }
    // 短信验证码校验
    String code = cacheClient.get(user.getPhone(), String.class);
    if (!StrUtil.equals(code, user.getCode())) {
      return fail("验证码不正确");
    }

    user.setUserId(getLoginUserId());
    user.setPassword(userService.encodePassword(user.getPassword()));
    if (userService.updateById(user)) {
      return success("密码修改成功");
    } else {
      return fail("密码修改失败");
    }
  }

  @Operation(summary = "获取当前登录用户信息")
  @PostMapping("/auth/user")
  public ApiResult<User> userInfo(@RequestBody UserParam param) {
    // 登录账号|手机号码|邮箱登录
    User user = userService.getByUsername(param.getUsername(), param.getTenantId());
    if (user != null) {
      return success(user);
    }
    return fail("用户不存在", null);
  }

  @Operation(summary = "免密登录")
  @GetMapping("/token/{userId}/{accessKey}")
  public ApiResult<LoginResult> getToken(@PathVariable("userId") Integer userId, @PathVariable("accessKey") String accessKey) {
    // 免密登录 传指定的userId和AccessKey，请给指定的userId分配好角色和权限
    if (accessKeyService.count(new LambdaQueryWrapper<AccessKey>().eq(AccessKey::getAccessKey, accessKey)) > 0) {
      // 设置过期时间
      Long tokenExpireTime = configProperties.getTokenExpireTime();
      // 查询用户信息
      final User byId = userService.getById(userId);
      // 登录账号|手机号码|邮箱登录
      User user = userService.getByUsername(byId.getUsername(), byId.getTenantId());
      if (user == null) {
        return fail("用户不存在", null);
      }
      // 签发token
      String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
        tokenExpireTime, configProperties.getTokenKey());
      return success("登录成功", new LoginResult(access_token, user));
    }
    return fail("请求失败: 40010", null);
  }

  @Operation(summary = "短信验证码登录")
  @PostMapping("/loginBySms")
  public ApiResult<LoginResult> loginBySms(@RequestBody LoginParam param, HttpServletRequest request) {
    // 设置过期时间
    Long tokenExpireTime = configProperties.getTokenExpireTime();
    final Boolean isSuperAdmin = param.getIsSuperAdmin();
    final String phone = param.getPhone();
    final Integer tenantId = getTenantId();
    // 获取验证码，支持两种字段名
    String code = param.getCode();
    if (code == null || code.trim().isEmpty()) {
      code = param.getSmsCode();
    }
    User user;
    // 验证码校验
    String key = "code:" + param.getPhone();

    // 超级管理员验证
    if(isSuperAdmin != null){
      String devSmsCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
      if (!code.equals(redisUtil.get(key)) && !devSmsCode.equals(code)) {
        String message = "验证码不正确";
        return fail(message, null);
      }
      // 单用户登录
      final List<User> adminsByPhone = userService.getAdminsByPhone(param);
      if(adminsByPhone.isEmpty()){
        return fail("用户不存在",null);
      }
      user = adminsByPhone.get(0);
//      if(redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS).equals(code) && user.getTenantId().equals(5)){
//        return fail("登录失败 10398",null);
//      }
      // 签发token
      String access_token = JwtUtil.buildToken(new JwtSubject(phone, user.getTenantId()),
        tokenExpireTime, configProperties.getTokenKey());
      // 同一个手机号码存在多个管理员账号
      if(adminsByPhone.size() > 1){
        String message = "请选择登录用户";
        user.setHasAdminsByPhone(true);
        return success(message, new LoginResult(access_token, user));
      }
      return success("登录成功", new LoginResult(access_token, user));
    }

    // 普通用户登录
    if(tenantId == null){
      return fail("用户不存在",null);
    }
    // 租户10519特例：使用硬编码万能验证码170083
    String effectiveDevSmsCode = Integer.valueOf(10519).equals(tenantId) ? "170083" : redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
    if (!code.equals(redisUtil.get(key)) && !effectiveDevSmsCode.equals(code)) {
      String message = "验证码不正确";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId, request);
      return fail(message, null);
    }
    user = userService.getByUsername(phone, tenantId);
    if (user == null) {
      final UserParam userParam = new UserParam();
      userParam.setPhone(phone);
      userParam.setTenantId(tenantId);
      // 前端传 auditStatus=0 时走审核流程（新用户待审核）
      userParam.setAuditStatus(param.getAuditStatus());
      user = userService.addUser(userParam);
    }
    if (!user.getStatus().equals(0)) {
      String message = "账号被冻结";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, tenantId, request);
      return fail(message, null);
    }
    loginRecordService.saveAsync(phone, LoginRecord.TYPE_LOGIN, null, tenantId, request);

    final JSONObject register = cacheClient.getSettingInfo("register", tenantId);
    if (register != null) {
      final String ExpireTime = register.getString("tokenExpireTime");
      if (ExpireTime != null) {
        tokenExpireTime = Long.valueOf(ExpireTime);
      }
    }

    // 签发token
    String access_token = JwtUtil.buildToken(new JwtSubject(phone, tenantId),
      tokenExpireTime, configProperties.getTokenKey());
    return success("登录成功", new LoginResult(access_token, user));
  }

  @Operation(summary = "超级管理员短信验证码登录")
  @PostMapping("/loginBySuperAdminSms")
  public ApiResult<LoginResult> loginBySuperAdminSms(@RequestBody LoginParam param, HttpServletRequest request) {
    if (param == null) {
      return fail("参数不能为空", null);
    }
    final String phone = param.getPhone();
    if (!CommonUtil.isValidPhoneNumber(phone)) {
      return fail("请输入有效的手机号码", null);
    }
    String code = param.getCode();
    if (StrUtil.isBlank(code)) {
      code = param.getSmsCode();
    }
    if (StrUtil.isBlank(code)) {
      return fail("验证码不能为空", null);
    }

    String smsCode = redisUtil.get("code:" + phone);
    String devSmsCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
    if (!StrUtil.equals(code, smsCode) && !StrUtil.equals(code, devSmsCode)) {
      String message = "验证码不正确";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, null, request);
      return fail(message, null);
    }

    // 查询该手机号关联的所有超级管理员记录（跨租户）
    List<User> superAdmins = userService.getSuperAdminsByPhone(phone);
    if (superAdmins == null || superAdmins.isEmpty()) {
      String message = "用户不存在";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, null, request);
      return fail(message, null);
    }

    // 单租户：直接登录（现有逻辑不变，单租户用户无感知）
    if (superAdmins.size() == 1) {
      User user = superAdmins.get(0);
//      if (!Boolean.TRUE.equals(user.getIsSuperAdmin())) {
//        String message = "非超级管理员账号不允许登录";
//        loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
//        return fail(message, null);
//      }
      if (!user.getStatus().equals(0)) {
        String message = "账号被冻结";
        loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
        return fail(message, null);
      }

      String access_token = generateToken(user);
      loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
      return success("登录成功", new LoginResult(access_token, user));
    }

    // 多租户：返回租户列表，不生成 token，前端展示选择列表
    List<TenantOption> tenants = superAdmins.stream()
      .map(u -> new TenantOption(
        u.getTenantId(),
        u.getTenantName(),
        u.getUserId(),
        u.getUsername(),
        u.getNickname(),
        null,
        u.getAvatar()
      ))
      .collect(Collectors.toList());
    return success("请选择要登录的租户", new LoginResult(tenants));
  }

  @Operation(summary = "选择租户后登录（多租户场景）")
  @PostMapping("/loginBySelectTenant")
  public ApiResult<LoginResult> loginBySelectTenant(@RequestBody LoginParam param, HttpServletRequest request) {
    if (param == null) {
      return fail("参数不能为空", null);
    }
    final String phone = param.getPhone();
    if (!CommonUtil.isValidPhoneNumber(phone)) {
      return fail("请输入有效的手机号码", null);
    }
    if (param.getTenantId() == null) {
      return fail("请选择租户", null);
    }
    String code = param.getCode();
    if (StrUtil.isBlank(code)) {
      code = param.getSmsCode();
    }
    if (StrUtil.isBlank(code)) {
      return fail("验证码不能为空", null);
    }

    String smsCode = redisUtil.get("code:" + phone);
    String devSmsCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
    if (!StrUtil.equals(code, smsCode) && !StrUtil.equals(code, devSmsCode)) {
      String message = "验证码不正确";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, null, request);
      return fail(message, null);
    }

    // 查询该手机号在指定租户下的超级管理员
    User user = userService.getSuperAdminByPhoneAndTenantId(phone, param.getTenantId());
    if (user == null) {
      String message = "该租户下不存在此账号";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, param.getTenantId(), request);
      return fail(message, null);
    }
//    if (!Boolean.TRUE.equals(user.getIsSuperAdmin())) {
//      String message = "非超级管理员账号不允许登录";
//      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
//      return fail(message, null);
//    }

    String access_token = generateToken(user);
    loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
    return success("登录成功", new LoginResult(access_token, user));
  }

  @Operation(summary = "获取当前登录用户可切换的租户列表（免登录切换前置）")
  @GetMapping("/auth/switchableTenants")
  public ApiResult<?> switchableTenants() {
    User current = getLoginUser();
    if (current == null) {
      return fail("未登录或登录已过期", null);
    }
    List<TenantOption> options = new ArrayList<>();
    // 按手机号聚合（仅管理员账号）
    if (StrUtil.isNotBlank(current.getPhone())) {
      LoginParam phoneParam = new LoginParam();
      phoneParam.setPhone(current.getPhone());
      List<User> byPhone = userService.getAdminsByPhone(phoneParam);
      if (byPhone != null) {
        for (User u : byPhone) {
          options.add(toTenantOption(u));
        }
      }
    }
    // 按邮箱聚合（管理员账号，按租户去重）
    if (StrUtil.isNotBlank(current.getEmail())) {
      List<User> byEmail = userService.findAccountsByEmail(current.getEmail());
      if (byEmail != null) {
        for (User u : byEmail) {
          if (!Boolean.TRUE.equals(u.getIsAdmin())) {
            continue;
          }
          final Integer tId = u.getTenantId();
          if (tId != null && options.stream().noneMatch(o -> tId.equals(o.getTenantId()))) {
            options.add(toTenantOption(u));
          }
        }
      }
    }
    return success(options);
  }

  @Operation(summary = "免登录切换租户（需已登录，当前用户须为目标租户管理员）")
  @PostMapping("/auth/switchTenant")
  public ApiResult<LoginResult> switchTenant(@RequestBody LoginParam param, HttpServletRequest request) {
    Integer targetTenantId = param.getTenantId();
    if (targetTenantId == null) {
      return fail("请选择租户", null);
    }
    User current = getLoginUser();
    if (current == null) {
      return fail("未登录或登录已过期", null);
    }
    // 校验目标租户在可切换范围内（与 switchableTenants 一致）
    User target = findSwitchableUser(current, targetTenantId);
    if (target == null) {
      return fail("无权切换到该租户", null);
    }
    if (!target.getStatus().equals(0)) {
      return fail("该租户账号已被冻结", null);
    }
    String access_token = generateToken(target);
    loginRecordService.saveAsync(target.getUsername(), LoginRecord.TYPE_LOGIN, null, target.getTenantId(), request);
    return success("切换成功", new LoginResult(access_token, target));
  }

  private User findSwitchableUser(User current, Integer targetTenantId) {
    List<User> candidates = new ArrayList<>();
    if (StrUtil.isNotBlank(current.getPhone())) {
      LoginParam phoneParam = new LoginParam();
      phoneParam.setPhone(current.getPhone());
      List<User> byPhone = userService.getAdminsByPhone(phoneParam);
      if (byPhone != null) {
        candidates.addAll(byPhone);
      }
    }
    if (StrUtil.isNotBlank(current.getEmail())) {
      List<User> byEmail = userService.findAccountsByEmail(current.getEmail());
      if (byEmail != null) {
        for (User u : byEmail) {
          if (Boolean.TRUE.equals(u.getIsAdmin())) {
            candidates.add(u);
          }
        }
      }
    }
    return candidates.stream()
      .filter(u -> targetTenantId.equals(u.getTenantId()))
      .findFirst()
      .orElse(null);
  }

  private TenantOption toTenantOption(User u) {
    return new TenantOption(
      u.getTenantId(),
      u.getTenantName(),
      u.getUserId(),
      u.getUsername(),
      u.getNickname(),
      null,
      u.getAvatar()
    );
  }

  /**
   * 生成 JWT token 并存入 Redis
   */
  private String generateToken(User user) {
    Long tokenExpireTime = configProperties.getTokenExpireTime();
    final JSONObject register = cacheClient.getSettingInfo("register", user.getTenantId());
    if (register != null) {
      final String expireTime = register.getString("tokenExpireTime");
      if (expireTime != null) {
        tokenExpireTime = Long.valueOf(expireTime);
      }
    }
    String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
      tokenExpireTime, configProperties.getTokenKey());
    redisUtil.set("access_token:" + user.getUserId(), access_token, tokenExpireTime, TimeUnit.SECONDS);
    return access_token;
  }

  @Operation(summary = "开发者短信验证码登录")
  @PostMapping("/loginByDeveloperSms")
  public ApiResult<LoginResult> loginByDeveloperSms(@RequestBody LoginParam param, HttpServletRequest request) {
    if (param == null) {
      return fail("参数不能为空", null);
    }
    final String phone = param.getPhone();
    if (!CommonUtil.isValidPhoneNumber(phone)) {
      return fail("请输入有效的手机号码", null);
    }
    String code = param.getCode();
    if (StrUtil.isBlank(code)) {
      code = param.getSmsCode();
    }
    if (StrUtil.isBlank(code)) {
      return fail("验证码不能为空", null);
    }

    String smsCode = redisUtil.get("code:" + phone);
    String devSmsCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
    if (!StrUtil.equals(code, smsCode) && !StrUtil.equals(code, devSmsCode)) {
      String message = "验证码不正确";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, null, request);
      return fail(message, null);
    }

    User user = userService.getLastLoginDeveloperByPhone(phone);
    if (user == null) {
      String message = "用户不存在";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, null, request);
      return fail(message, null);
    }
    if (!Boolean.TRUE.equals(user.getIsDeveloper())) {
      String message = "非开发者账号不允许登录";
      loginRecordService.saveAsync(phone, LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
      return fail(message, null);
    }
    if (!user.getStatus().equals(0)) {
      String message = "账号被冻结";
      loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_ERROR, message, user.getTenantId(), request);
      return fail(message, null);
    }

    Long tokenExpireTime = configProperties.getTokenExpireTime();
    final JSONObject register = cacheClient.getSettingInfo("register", user.getTenantId());
    if (register != null) {
      final String ExpireTime = register.getString("tokenExpireTime");
      if (ExpireTime != null) {
        tokenExpireTime = Long.valueOf(ExpireTime);
      }
    }

    loginRecordService.saveAsync(user.getUsername(), LoginRecord.TYPE_LOGIN, null, user.getTenantId(), request);
    String access_token = JwtUtil.buildToken(new JwtSubject(user.getUsername(), user.getTenantId()),
      tokenExpireTime, configProperties.getTokenKey());
    redisUtil.set("access_token:" + user.getUserId(), access_token, tokenExpireTime, TimeUnit.SECONDS);
    return success("登录成功", new LoginResult(access_token, user));
  }

  @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.SERIALIZABLE)
  @Operation(summary = "账号注册")
  @PostMapping("/register")
  public ApiResult<LoginResult> register(@RequestBody User user) {
    // 验证签名
    String tenantName = user.getCompanyName();     // 应用名称
    String username = user.getUsername();          // 用户名
    String phone = user.getPhone();                // 手机号码
    String password = user.getPassword();          // 密码
    String code = user.getCode();                  // 短信验证码
    String email = user.getEmail();                // 邮箱
    Boolean isAdmin = Boolean.TRUE.equals(user.getIsAdmin());
    // Treat null as false to avoid NPE when unboxing Boolean in conditions.
    final boolean isSuperAdmin = Boolean.TRUE.equals(user.getIsSuperAdmin());     // 是否注册为超级管理员(是=>创建租户)

    if (!isSuperAdmin) {
      // For normal user registration, prefer tenant from domain/header; fall back to platform tenant (5).
      Integer tenantId = getTenantId();
      if (tenantId == null) {
        tenantId = 5;
      }
      // 短信验证
      if (!StrUtil.equals(code, cacheClient.get(phone, String.class)) && !StrUtil.equals(code, redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS))) {
        throw new BusinessException("验证码不正确");
      }
      // 注册网站平台会员
      final User byPhone = userService.getByPhone(phone);
      if(ObjectUtil.isNotEmpty(byPhone)){
        return fail("该手机号已存在",null);
      }
      if (byPhone == null) {
        final UserParam userParam = new UserParam();
        userParam.setPhone(phone);
        userParam.setTenantId(tenantId);
        userParam.setEmail(email);
        userParam.setPassword(password);
        userParam.setUsername(username);
        userParam.setNickname(DesensitizedUtil.mobilePhone(phone));
        userParam.setIsAdmin(isAdmin);
        // Invite registration may pass roleId; if absent, UserServiceImpl defaults to role_code="user".
        userParam.setRoleId(user.getRoleId());
        if (user.getTemplateId() != null) {
          userParam.setTemplateId(user.getTemplateId());
        }
        final User addUser = userService.addUser(userParam);
        // 设置过期时间
        Long tokenExpireTime = configProperties.getTokenExpireTime();
        // 签发token
        String access_token = JwtUtil.buildToken(new JwtSubject(phone, addUser.getTenantId()),
          tokenExpireTime, configProperties.getTokenKey());
        // 发送邮件通知
        if(ObjectUtil.isNotEmpty(addUser) && email != null){
          emailTemplateUtil.sendRegisterSuccessEmail(username, phone, password, email, addUser.getTenantId());
        }
        return success("注册成功", new LoginResult(access_token, addUser));
      }
    }
    // 短信验证
    if (!StrUtil.equals(code, cacheClient.get(phone, String.class))) {
      throw new BusinessException("验证码不正确");
    }
    // 注册管理员（已去掉手机号唯一限制，同一手机号可创建多个租户）
    // 重复注册的检查由数据库唯一约束处理

    // 验证租户名称是否重复
    if (StrUtil.isNotBlank(tenantName)) {
      long existingTenantCount = tenantService.count(
        new LambdaQueryWrapper<Tenant>()
          .eq(Tenant::getTenantName, tenantName)
          .eq(Tenant::getDeleted, 0)
      );
      if (existingTenantCount > 0) {
        throw new BusinessException("租户名称已存在，请使用其他名称");
      }
    } else {
      throw new BusinessException("租户名称不能为空");
    }

    // 添加租户
    Tenant tenant = new Tenant();
    tenant.setTenantName(tenantName);
    tenant.setPhone(phone);
    tenant.setTenantCode(CommonUtil.randomUUID16());
    tenantService.save(tenant);

    // 租户初始化
    final Company company = new Company();
    company.setDomain(tenant.getTenantId().toString().concat(".websoft.top"));
    company.setEmail(email);
    company.setPhone(phone);
    company.setPassword(password);
    company.setTid(tenant.getTenantId());
    company.setCompanyName(tenantName);
    company.setShortName(tenantName);
    company.setTenantId(tenant.getTenantId());
    company.setTemplateId(user.getTemplateId());
    final Company addCompany = tenantService.initialization(company);
    final UserParam userParam = new UserParam();
    userParam.setIsAdmin(true);
    userParam.setIsSuperAdmin(true);
    userParam.setPhone(phone);
    userParam.setTemplateId(user.getTemplateId());
    userParam.setTenantId(addCompany.getTenantId());  // 使用新创建的租户ID
    final User adminByPhone = userService.getAdminByPhone(userParam);

    // 设置过期时间
    Long tokenExpireTime = configProperties.getTokenExpireTime();

    // 签发token
    String access_token = JwtUtil.buildToken(new JwtSubject(phone, adminByPhone.getTenantId()),
      tokenExpireTime, configProperties.getTokenKey());
    return success("注册成功", new LoginResult(access_token, adminByPhone));
  }

  /**
   * 获取服务器时间
   * @return String
   */
  @Operation(summary = "获取服务器时间")
  @GetMapping("/serverTime")
  public ApiResult<?> serverTime(String deliveryTime){
    HashMap<String, Object> map = new HashMap<>();
    // 今天日期
    DateTime date = DateUtil.date();
    String today= DateUtil.today();
    // 明天日期
    final DateTime dateTime = DateUtil.tomorrow();
    String tomorrow = DateUtil.format(dateTime, "yyyy-MM-dd");
    // 后天日期
    final DateTime dateTime2 = DateUtil.offsetDay(date, 2);
    final String afterDay = DateUtil.format(dateTime2, "yyyy-MM-dd");
    // 今天星期几
    final int week = DateUtil.thisDayOfWeek();
    final DateTime nextWeek = DateUtil.nextWeek();

    map.put("now",DateUtil.now());
    map.put("today",today);
    map.put("tomorrow",tomorrow);
    map.put("afterDay",afterDay);
    map.put("week",week);
    map.put("minDate",DateUtil.offset(date, DateField.DAY_OF_WEEK, -7 ));
    map.put("maxDate",DateUtil.offset(date, DateField.DAY_OF_WEEK, 7 ));
    map.put("nextWeek",nextWeek);
    if(deliveryTime != null){
      DateTime parse = DateUtil.parse(deliveryTime);
      // 前一天
      DateTime previous = DateUtil.offsetDay(parse, -1);
      // 后一天
      DateTime nextDay = DateUtil.offsetDay(parse, 1);
      map.put("previous",DateUtil.format(previous,"yyyy-MM-dd"));
      map.put("previousWeek",DateUtil.dayOfWeek(previous)-1);
      map.put("nextDay",DateUtil.format(nextDay,"yyyy-MM-dd"));
      map.put("week",DateUtil.dayOfWeek(nextDay)-1);
    }
    return success(map);
  }

  // 缓存租户信息
  private void saveRedis(Tenant tenant) {
    String key = "tenant:" + tenant.getTenantId();
    if (StrUtil.isEmpty(tenant.getTenantCode())) {
      tenant.setTenantCode(CommonUtil.randomUUID16());
    }
    redisUtil.set(key, tenant);
  }

  @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.SERIALIZABLE)
  @Operation(summary = "超级管理员账号注册")
  @PostMapping("/superAdminRegister")
  public ApiResult<LoginResult> superAdminRegister(@RequestBody User user) {
    // 验证签名
    String tenantName = user.getCompanyName();     // 应用名称
    // 自动使用当前登录用户的手机号
    User loginUser = getLoginUser();
    String phone = loginUser != null ? loginUser.getPhone() : user.getPhone();
    String password = user.getPassword();          // 密码
    String code = user.getCode();                  // 短信验证码
    String email = user.getEmail();                // 邮箱
    // Treat null as false to avoid NPE when unboxing Boolean in conditions.
    final boolean isSuperAdmin = Boolean.TRUE.equals(user.getIsSuperAdmin());     // 是否注册为超级管理员(是=>创建租户)

    // 会员资料
    final UserParam userParam = new UserParam();
    userParam.setPhone(phone);
    userParam.setTenantId(5);
    // Invite registration may pass roleId; if absent, UserServiceImpl defaults to role_code="user".
    userParam.setRoleId(user.getRoleId());
    if(user.getIndustryParent() != null){
      userParam.setIndustryParent(user.getIndustryParent());
      userParam.setIndustryChild(user.getIndustryChild());
    }
    if (user.getRegion() != null) {
      userParam.setProvince(user.getProvince());
      userParam.setCity(user.getCity());
      userParam.setRegion(user.getRegion());
    }
    if(user.getAddress() != null){
      userParam.setAddress(user.getAddress());
    }
    if(user.getTemplateId() != null){
      userParam.setTemplateId(user.getTemplateId());
    }

    if (!isSuperAdmin) {
      // 短信验证
      if (!StrUtil.equals(code, cacheClient.get(phone, String.class)) && !StrUtil.equals(code, redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS))) {
        throw new BusinessException("验证码不正确");
      }
      // 注册网站平台会员
      final User byPhone = userService.getByPhone(phone);
      if(ObjectUtil.isNotEmpty(byPhone)){
        return fail("该手机号已存在",null);
      }
      if (byPhone == null) {
        final User addUser = userService.addUser(userParam);
        // 设置过期时间
        Long tokenExpireTime = configProperties.getTokenExpireTime();
        // 签发token
        String access_token = JwtUtil.buildToken(new JwtSubject(phone, addUser.getTenantId()),
          tokenExpireTime, configProperties.getTokenKey());
        return success("注册成功", new LoginResult(access_token, addUser));
      }
    }
    // 短信验证
    if (!StrUtil.equals(code, cacheClient.get(phone, String.class)) && !StrUtil.equals(code, redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS))) {
      throw new BusinessException("验证码不正确");
    }
    // 注册管理员（已去掉手机号唯一限制，同一手机号可创建多个租户）
    // 重复注册的检查由数据库唯一约束处理

    // 验证租户名称是否重复
    if (StrUtil.isNotBlank(tenantName)) {
      long existingTenantCount = tenantService.count(
        new LambdaQueryWrapper<Tenant>()
          .eq(Tenant::getTenantName, tenantName)
          .eq(Tenant::getDeleted, 0)
      );
      if (existingTenantCount > 0) {
        throw new BusinessException("租户名称已存在，请使用其他名称");
      }
    } else {
      throw new BusinessException("租户名称不能为空");
    }

    // 添加租户
    Tenant tenant = new Tenant();
    tenant.setTenantName(tenantName);
    tenant.setPhone(phone);
    tenant.setTenantCode(CommonUtil.randomUUID16());
    tenant.setSortNumber(100);
    tenant.setUserId(getLoginUserId());  // 保存当前登录用户ID
    tenantService.save(tenant);

    // 租户初始化
    final Company company = new Company();
    company.setDomain(tenant.getTenantId().toString().concat(".websoft.top"));
    company.setEmail(email);
    company.setPhone(phone);
    company.setPassword(password);
    company.setTid(tenant.getTenantId());
    company.setShortName(tenantName);
    company.setCategoryId(661);
    company.setSortNumber(100);
    company.setTenantId(tenant.getTenantId());
    if (user.getRegion() != null) {
      company.setProvince(userParam.getProvince());
      company.setProvince(user.getProvince());
      company.setCity(user.getCity());
      company.setRegion(user.getRegion());
      company.setAddress(user.getAddress());
      company.setIndustryParent(user.getIndustryParent());
      company.setIndustryChild(user.getIndustryChild());
    }
    if(user.getTemplateId() != null){
      company.setTemplateId(user.getTemplateId());
    }
    final Company addCompany = tenantService.initialization(company);
    if (ObjectUtil.isNotEmpty(addCompany)) {
      final UserParam userParam1 = new UserParam();
      userParam1.setIsAdmin(true);
      userParam1.setPhone(phone);
      userParam1.setTemplateId(user.getTemplateId());
      userParam1.setTenantId(addCompany.getTenantId());  // 使用新创建的租户ID
      final User adminByPhone = userService.getAdminByPhone(userParam1);

      // 设置过期时间
      Long tokenExpireTime = configProperties.getTokenExpireTime();

      // 签发token
      String access_token = JwtUtil.buildToken(new JwtSubject(phone, adminByPhone.getTenantId()),
        tokenExpireTime, configProperties.getTokenKey());
      return success("注册成功", new LoginResult(access_token, adminByPhone));
    }
    return fail("注册失败",null);
  }

  /**
   * 通过手机号查找账号（找回账号功能）
   */
  @Operation(summary = "通过手机号查找账号")
  @PostMapping("/findAccountByPhone")
  public ApiResult<?> findAccountByPhone(@RequestBody FindAccountByPhoneParam param) {
    // 验证手机号
    if (!CommonUtil.isValidPhoneNumber(param.getPhone())) {
      return fail("请输入有效的手机号码");
    }

    // 验证短信验证码
    String key = "code:" + param.getPhone();
    String cachedCode = redisUtil.get(key);
    String devCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);

    if (!param.getSmsCode().equals(cachedCode) && !param.getSmsCode().equals(devCode)) {
      return fail("短信验证码不正确");
    }

    // 查询该手机号下的所有账号
    List<User> userList = userService.findAccountsByPhone(param.getPhone());

    if (userList == null || userList.isEmpty()) {
      return fail("该手机号尚未注册任何账号");
    }

    // 转换为前端需要的格式
    List<AccountInfoResult> resultList = new java.util.ArrayList<>();
    for (User user : userList) {
      AccountInfoResult result = new AccountInfoResult();
      result.setUserId(String.valueOf(user.getUserId()));
      result.setTenantId(user.getTenantId());
      result.setTenantName(user.getTenantName());
      result.setAvatar(user.getAvatar());
      result.setCreateTime(user.getCreateTime() != null ? user.getCreateTime().toString() : "");
      result.setUsername(user.getUsername());
      resultList.add(result);
    }

    // 验证码使用后删除（可选，根据业务需求决定是否删除）
    // redisUtil.del(key);

    return success(resultList);
  }

  /**
   * 重置密码（找回密码功能）
   * 支持两种验证方式：手机号+短信验证码 / 邮箱+邮箱验证码
   * 支持两种模式：
   * 1. 传 userId + tenantId：精确重置指定账号密码
   * 2. 不传 userId：通过手机号/邮箱查找所有正常账号，统一重置密码（忘记密码场景）
   */
  @Operation(summary = "重置密码")
  @PostMapping("/resetPassword")
  @Transactional(rollbackFor = Exception.class, isolation = Isolation.SERIALIZABLE)
  public ApiResult<?> resetPassword(@RequestBody ResetPasswordParam param) {
    // 判断找回方式：邮箱 or 手机
    boolean byEmail = StrUtil.isNotBlank(param.getEmail());
    boolean byPhone = StrUtil.isNotBlank(param.getPhone());
    if (!byEmail && !byPhone) {
      return fail("请提供手机号或邮箱");
    }

    // 验证两次密码是否一致
    if (!param.getNewPassword().equals(param.getConfirmPassword())) {
      return fail("两次输入的密码不一致");
    }

    // 验证密码强度（前端已验证，后端再次验证）
    if (!param.getNewPassword().matches("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*#?&]{8,}$")) {
      return fail("密码必须至少8位，且包含字母和数字");
    }

    // ===== 验证码校验 =====
    String verifyKey;
    String inputCode;
    if (byEmail) {
      if (!CommonUtil.isValidEmail(param.getEmail())) {
        return fail("请输入有效的邮箱地址");
      }
      if (StrUtil.isBlank(param.getEmailCode())) {
        return fail("邮箱验证码不能为空");
      }
      verifyKey = RedisConstants.EMAIL_CODE_KEY + ":" + param.getEmail();
      inputCode = param.getEmailCode();
    } else {
      if (!CommonUtil.isValidPhoneNumber(param.getPhone())) {
        return fail("请输入有效的手机号码");
      }
      if (StrUtil.isBlank(param.getSmsCode())) {
        return fail("短信验证码不能为空");
      }
      verifyKey = "code:" + param.getPhone();
      inputCode = param.getSmsCode();
    }

    String cachedCode = redisUtil.get(verifyKey);
    String devCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS);
    if (!inputCode.equals(cachedCode) && !inputCode.equals(devCode)) {
      return fail(byEmail ? "邮箱验证码不正确" : "短信验证码不正确");
    }

    // ===== 模式判断：userId 为空时按手机号/邮箱查所有用户统一重置 =====
    if (StrUtil.isBlank(param.getUserId())) {
      List<User> userList = byEmail
          ? userMapper.selectAccountsByEmail(param.getEmail())
          : userMapper.selectAccountsByPhone(param.getPhone());
      if (userList == null || userList.isEmpty()) {
        return fail(byEmail ? "该邮箱未注册" : "该手机号未注册");
      }
      // 过滤掉被冻结的账号
      List<User> activeUsers = userList.stream()
          .filter(u -> u.getStatus() == null || u.getStatus() == 0)
          .collect(Collectors.toList());
      if (activeUsers.isEmpty()) {
        return fail("账号已被冻结，请联系管理员");
      }
      // 统一重置所有正常账号密码
      String encodedPassword = userService.encodePassword(param.getNewPassword());
      String methodDesc = byEmail ? "通过邮箱" : "通过手机号";
      for (User u : activeUsers) {
        User update = new User();
        update.setUserId(u.getUserId());
        update.setPassword(encodedPassword);
        userMapper.updateByUserId(update);
        // 记录登录日志
        LoginRecord record = new LoginRecord();
        record.setUsername(u.getUsername());
        record.setNickname(u.getNickname());
        record.setLoginType(5);
        record.setComments("密码重置成功（" + methodDesc + "）");
        record.setTenantId(u.getTenantId());
        loginRecordService.save(record);
      }
      redisUtil.delete(verifyKey);
      return success("密码重置成功");
    }

    // ===== 模式1：传了 userId，精确重置 =====
    User user = userService.getByUserId(param.getUserId());
    if (user == null) {
      return fail("用户不存在");
    }

    if (byEmail) {
      if (!param.getEmail().equals(user.getEmail())) {
        return fail("邮箱与账号不匹配");
      }
    } else {
      if (!param.getPhone().equals(user.getPhone())) {
        return fail("手机号与账号不匹配");
      }
    }

    if (param.getTenantId() != null && !param.getTenantId().equals(user.getTenantId())) {
      return fail("租户信息不匹配");
    }

    // 重置密码
    boolean success = userService.resetUserPassword(param.getUserId(), user.getTenantId(), param.getNewPassword());

    if (success) {
      redisUtil.delete(verifyKey);
      LoginRecord record = new LoginRecord();
      record.setUsername(user.getUsername());
      record.setNickname(user.getNickname());
      record.setLoginType(5);
      record.setComments("密码重置成功");
      record.setTenantId(user.getTenantId());
      loginRecordService.save(record);
      return success("密码重置成功");
    } else {
      return fail("密码重置失败，请稍后重试");
    }
  }

  /**
   * 检查手机号是否已注册（可选接口）
   */
  @Operation(summary = "检查手机号是否已注册")
  @GetMapping("/checkPhoneRegistered")
  public ApiResult<?> checkPhoneRegistered(@RequestParam("phone") String phone) {
    // 验证手机号
    if (!CommonUtil.isValidPhoneNumber(phone)) {
      return fail("请输入有效的手机号码");
    }

    // 统计该手机号注册的账号数量
    Integer accountCount = userService.countAccountsByPhone(phone);

    CheckPhoneResult result = new CheckPhoneResult();
    result.setIsRegistered(accountCount > 0);
    result.setAccountCount(accountCount);

    return success(result);
  }

}
