import { Injectable, Inject, Logger } from '@nestjs/common';
import {
  UserAccountRepository,
  USER_ACCOUNT_REPOSITORY,
} from '@/domain/repositories/user-account.repository.interface';
import {
  MpcKeyShareRepository,
  MPC_KEY_SHARE_REPOSITORY,
} from '@/domain/repositories/mpc-key-share.repository.interface';
import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate';
import { WalletAddress } from '@/domain/entities/wallet-address.entity';
import {
  AccountSequenceGeneratorService,
  UserValidatorService,
} from '@/domain/services';
import {
  UserId,
  PhoneNumber,
  ReferralCode,
  AccountSequence,
  ChainType,
  KYCInfo,
} from '@/domain/value-objects';
import { TokenService } from './token.service';
import { RedisService } from '@/infrastructure/redis/redis.service';
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
import { SmsService } from '@/infrastructure/external/sms/sms.service';
import { EmailService } from '@/infrastructure/external/email/email.service';
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
import { BlockchainClientService } from '@/infrastructure/external/blockchain/blockchain-client.service';
import { MpcWalletService } from '@/infrastructure/external/mpc';
import { BlockchainWalletHandler } from '../event-handlers/blockchain-wallet.handler';
import { MpcKeygenCompletedHandler } from '../event-handlers/mpc-keygen-completed.handler';
import { ApplicationError } from '@/shared/exceptions/domain.exception';
import { generateIdentity } from '@/shared/utils';
import {
  MpcKeygenRequestedEvent,
  AccountRecoveredEvent,
  AccountRecoveryFailedEvent,
  MnemonicRevokedEvent,
  AccountUnfrozenEvent,
  KeyRotationRequestedEvent,
  UserProfileUpdatedEvent,
} from '@/domain/events';
import {
  AutoCreateAccountCommand,
  RegisterByPhoneCommand,
  RegisterWithoutSmsVerifyCommand,
  RecoverByMnemonicCommand,
  RecoverByPhoneCommand,
  AutoLoginCommand,
  RegisterCommand,
  LoginCommand,
  BindPhoneNumberCommand,
  UpdateProfileCommand,
  SubmitKYCCommand,
  ReviewKYCCommand,
  RemoveDeviceCommand,
  SendSmsCodeCommand,
  GetMyProfileQuery,
  GetMyDevicesQuery,
  GetUserByReferralCodeQuery,
  ValidateReferralCodeQuery,
  GetReferralStatsQuery,
  GenerateReferralLinkCommand,
  GetWalletStatusQuery,
  WalletStatusResult,
  MarkMnemonicBackedUpCommand,
  AutoCreateAccountResult,
  RecoverAccountResult,
  AutoLoginResult,
  RegisterResult,
  LoginResult,
  UserProfileDTO,
  DeviceDTO,
  UserBriefDTO,
  ReferralCodeValidationResult,
  ReferralLinkResult,
  ReferralStatsResult,
  MeResult,
} from '../commands';

@Injectable()
export class UserApplicationService {
  private readonly logger = new Logger(UserApplicationService.name);

  constructor(
    @Inject(USER_ACCOUNT_REPOSITORY)
    private readonly userRepository: UserAccountRepository,
    @Inject(MPC_KEY_SHARE_REPOSITORY)
    private readonly mpcKeyShareRepository: MpcKeyShareRepository,
    private readonly sequenceGenerator: AccountSequenceGeneratorService,
    private readonly validatorService: UserValidatorService,
    private readonly blockchainClient: BlockchainClientService,
    private readonly mpcWalletService: MpcWalletService,
    private readonly tokenService: TokenService,
    private readonly redisService: RedisService,
    private readonly prisma: PrismaService,
    private readonly smsService: SmsService,
    private readonly emailService: EmailService,
    private readonly eventPublisher: EventPublisherService,
    // 注入事件处理器以确保它们被 NestJS 实例化并执行 onModuleInit
    private readonly blockchainWalletHandler: BlockchainWalletHandler,
    private readonly mpcKeygenCompletedHandler: MpcKeygenCompletedHandler,
  ) {}

  /**
   * 自动创建账户 (首次打开APP)
   *
   * 简化版本:
   * - 生成随机用户名和头像
   * - 创建账户记录
   * - 生成推荐码
   * - 返回 token
   *
   * 注意: MPC钱包地址生成移到后台异步处理
   */
  async autoCreateAccount(
    command: AutoCreateAccountCommand,
  ): Promise<AutoCreateAccountResult> {
    this.logger.log(`Creating account for device: ${command.deviceId}`);

    // 1. 验证设备ID (检查设备是否已创建过账户)
    const deviceValidation =
      await this.validatorService.checkDeviceNotRegistered(command.deviceId);
    if (!deviceValidation.isValid)
      throw new ApplicationError(deviceValidation.errorMessage!);

    // 2. 验证邀请码
    let inviterSequence: AccountSequence | null = null;
    if (command.inviterReferralCode) {
      const referralCode = ReferralCode.create(command.inviterReferralCode);
      const referralValidation =
        await this.validatorService.validateReferralCode(referralCode);
      if (!referralValidation.isValid)
        throw new ApplicationError(referralValidation.errorMessage!);
      const inviter =
        await this.userRepository.findByReferralCode(referralCode);
      inviterSequence = inviter!.accountSequence;
    }

    // 3. 生成用户序列号
    const accountSequence =
      await this.sequenceGenerator.generateNextUserSequence();

    // 4. 生成用户名和头像
    const identity = generateIdentity(accountSequence.value);

    // 5. 构建设备名称字符串
    let deviceNameStr = '未命名设备';
    if (command.deviceName) {
      const parts: string[] = [];
      if (command.deviceName.model) parts.push(command.deviceName.model);
      if (command.deviceName.platform) parts.push(command.deviceName.platform);
      if (command.deviceName.osVersion)
        parts.push(command.deviceName.osVersion);
      if (parts.length > 0) deviceNameStr = parts.join(' ');
    }

    // 6. 创建用户账户 - deviceInfo 100% 保持前端传递的原样
    const account = UserAccount.createAutomatic({
      accountSequence,
      initialDeviceId: command.deviceId,
      deviceName: deviceNameStr,
      deviceInfo: command.deviceName, // 100% 保持原样存储
      inviterSequence,
      nickname: identity.username,
      avatarSvg: identity.avatarSvg,
    });

    // 7. 保存账户
    await this.userRepository.save(account);

    // 7.1 更新事件中的 userId（save 后才有真实 userId）
    account.updateEventsUserId();

    // 8. 生成 Token
    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: command.deviceId,
    });

    // 9. 发布领域事件 (包含 UserAccountAutoCreated)
    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();

    // 10. 发布 MPC Keygen 请求事件 (触发后台生成钱包)
    const sessionId = crypto.randomUUID();
    await this.eventPublisher.publish(
      new MpcKeygenRequestedEvent({
        sessionId,
        userId: account.userId.toString(),
        accountSequence: account.accountSequence.value, // 8位账户序列号，用于关联恢复助记词
        username: `user_${account.accountSequence.value}`, // 用于 mpc-system 标识
        threshold: 2,
        totalParties: 3,
        requireDelegate: true,
      }),
    );

    this.logger.log(
      `Account created: sequence=${accountSequence.value}, username=${identity.username}, MPC keygen requested`,
    );

    return {
      userSerialNum: account.accountSequence.value,
      referralCode: account.referralCode.value,
      username: account.nickname,
      avatarSvg: account.avatarUrl || identity.avatarSvg,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  /**
   * 手机号注册 (验证码+密码一步到位)
   *
   * - 验证短信验证码
   * - 创建账户并绑定手机号
   * - 设置密码
   * - 触发钱包生成
   * - 返回 token
   */
  async registerByPhone(
    command: RegisterByPhoneCommand,
  ): Promise<AutoCreateAccountResult> {
    const phoneNumber = PhoneNumber.create(command.phoneNumber);

    this.logger.log(
      `[REGISTER] Starting phone registration: phone=${phoneNumber.value}, deviceId=${command.deviceId}`,
    );

    // 1. 验证短信验证码
    const cachedCode = await this.redisService.get(
      `sms:register:${phoneNumber.value}`,
    );
    if (cachedCode !== command.smsCode) {
      this.logger.warn(
        `[REGISTER] SMS code mismatch: phone=${phoneNumber.value}`,
      );
      throw new ApplicationError('验证码错误或已过期');
    }
    this.logger.log(`[REGISTER] SMS code verified`);

    // 2. 检查手机号是否已注册
    const phoneValidation =
      await this.validatorService.validatePhoneNumber(phoneNumber);
    if (!phoneValidation.isValid) {
      this.logger.warn(
        `[REGISTER] Phone validation failed: ${phoneValidation.errorMessage}`,
      );
      throw new ApplicationError(phoneValidation.errorMessage!);
    }
    this.logger.log(`[REGISTER] Phone number validated`);

    // 3. 验证邀请码 (必填)
    if (!command.inviterReferralCode) {
      this.logger.warn(`[REGISTER] Missing required referral code`);
      throw new ApplicationError('推荐码不能为空');
    }

    const referralCode = ReferralCode.create(command.inviterReferralCode);
    const referralValidation =
      await this.validatorService.validateReferralCode(referralCode);
    if (!referralValidation.isValid) {
      this.logger.warn(
        `[REGISTER] Referral code invalid: ${command.inviterReferralCode}`,
      );
      throw new ApplicationError(referralValidation.errorMessage!);
    }
    const inviter = await this.userRepository.findByReferralCode(referralCode);
    if (!inviter) {
      this.logger.warn(
        `[REGISTER] Inviter not found for code: ${command.inviterReferralCode}`,
      );
      throw new ApplicationError('推荐码对应的用户不存在');
    }
    const inviterSequence = inviter.accountSequence;
    this.logger.log(`[REGISTER] Inviter validated: ${inviterSequence.value}`);

    // 4. 生成用户序列号
    const accountSequence =
      await this.sequenceGenerator.generateNextUserSequence();
    this.logger.log(
      `[REGISTER] Generated sequence: ${accountSequence.value}`,
    );

    // 5. 生成用户名和头像
    const identity = generateIdentity(accountSequence.value);

    // 6. 构建设备名称字符串
    let deviceNameStr = '未命名设备';
    if (command.deviceName) {
      const parts: string[] = [];
      if (command.deviceName.model) parts.push(command.deviceName.model);
      if (command.deviceName.platform) parts.push(command.deviceName.platform);
      if (command.deviceName.osVersion)
        parts.push(command.deviceName.osVersion);
      if (parts.length > 0) deviceNameStr = parts.join(' ');
    }

    // 7. 创建用户账户（带手机号）
    const account = UserAccount.create({
      accountSequence,
      phoneNumber,
      initialDeviceId: command.deviceId,
      deviceName: deviceNameStr,
      deviceInfo: command.deviceName,
      inviterSequence,
    });
    this.logger.log(
      `[REGISTER] Account aggregate created (not saved yet): sequence=${accountSequence.value}`,
    );

    // 8. 设置随机用户名和头像
    account.updateProfile({
      nickname: identity.username,
      avatarUrl: identity.avatarSvg,
    });

    // 9. 保存账户
    this.logger.log(`[REGISTER] Saving account to database...`);
    await this.userRepository.save(account);
    this.logger.log(
      `[REGISTER] Account saved: userId=${account.userId.value}, sequence=${account.accountSequence.value}`,
    );

    // 9.1 验证保存成功
    if (account.userId.value === BigInt(0)) {
      this.logger.error(
        `[REGISTER] CRITICAL: userId is still 0 after save! sequence=${accountSequence.value}`,
      );
      throw new ApplicationError('账户保存失败：未获取到用户ID');
    }

    // 10. 设置密码
    this.logger.log(`[REGISTER] Setting password hash...`);
    const bcrypt = await import('bcrypt');
    const passwordHash = await bcrypt.hash(command.password, 10);
    await this.prisma.userAccount.update({
      where: { userId: account.userId.value },
      data: { passwordHash },
    });
    this.logger.log(`[REGISTER] Password hash set`);

    // 11. 删除验证码
    await this.redisService.delete(`sms:register:${phoneNumber.value}`);

    // 12. 生成 Token
    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: command.deviceId,
    });
    this.logger.log(`[REGISTER] Tokens generated`);

    // 13. 更新事件中的 userId（save 后才有真实 userId）
    account.updateEventsUserId();

    // 14. 发布领域事件
    this.logger.log(
      `[REGISTER] Publishing ${account.domainEvents.length} domain events...`,
    );
    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();
    this.logger.log(`[REGISTER] Domain events published`);

    // 14. 发布 MPC Keygen 请求事件 (触发后台生成钱包)
    const sessionId = crypto.randomUUID();
    await this.eventPublisher.publish(
      new MpcKeygenRequestedEvent({
        sessionId,
        userId: account.userId.toString(),
        accountSequence: account.accountSequence.value,
        username: `user_${account.accountSequence.value}`,
        threshold: 2,
        totalParties: 3,
        requireDelegate: true,
      }),
    );

    this.logger.log(
      `[REGISTER] COMPLETE: sequence=${accountSequence.value}, phone=${phoneNumber.value}, userId=${account.userId.value}`,
    );

    return {
      userSerialNum: account.accountSequence.value,
      referralCode: account.referralCode.value,
      username: account.nickname,
      avatarSvg: account.avatarUrl || identity.avatarSvg,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  /**
   * 跳过短信验证注册
   *
   * 当用户收不到验证码时，允许跳过验证继续注册
   * - 不验证短信验证码
   * - 设置 phoneVerified = false
   * - 设置 kycStatus = 'NOT_STARTED'
   * - 其他流程与 registerByPhone 相同
   */
  async registerWithoutSmsVerify(
    command: RegisterWithoutSmsVerifyCommand,
  ): Promise<AutoCreateAccountResult> {
    const phoneNumber = PhoneNumber.create(command.phoneNumber);

    this.logger.log(
      `[REGISTER_NO_SMS] Starting registration without SMS verify: phone=${phoneNumber.value}, deviceId=${command.deviceId}`,
    );

    // 1. 检查手机号是否已注册
    const phoneValidation =
      await this.validatorService.validatePhoneNumber(phoneNumber);
    if (!phoneValidation.isValid) {
      this.logger.warn(
        `[REGISTER_NO_SMS] Phone validation failed: ${phoneValidation.errorMessage}`,
      );
      throw new ApplicationError(phoneValidation.errorMessage!);
    }
    this.logger.log(`[REGISTER_NO_SMS] Phone number validated`);

    // 2. 验证邀请码 (必填)
    if (!command.inviterReferralCode) {
      this.logger.warn(`[REGISTER_NO_SMS] Missing required referral code`);
      throw new ApplicationError('推荐码不能为空');
    }

    const referralCode = ReferralCode.create(command.inviterReferralCode);
    const referralValidation =
      await this.validatorService.validateReferralCode(referralCode);
    if (!referralValidation.isValid) {
      this.logger.warn(
        `[REGISTER_NO_SMS] Referral code invalid: ${command.inviterReferralCode}`,
      );
      throw new ApplicationError(referralValidation.errorMessage!);
    }
    const inviter = await this.userRepository.findByReferralCode(referralCode);
    if (!inviter) {
      this.logger.warn(
        `[REGISTER_NO_SMS] Inviter not found for code: ${command.inviterReferralCode}`,
      );
      throw new ApplicationError('推荐码对应的用户不存在');
    }
    const inviterSequence = inviter.accountSequence;
    this.logger.log(
      `[REGISTER_NO_SMS] Inviter validated: ${inviterSequence.value}`,
    );

    // 3. 生成用户序列号
    const accountSequence =
      await this.sequenceGenerator.generateNextUserSequence();
    this.logger.log(
      `[REGISTER_NO_SMS] Generated sequence: ${accountSequence.value}`,
    );

    // 4. 生成用户名和头像
    const identity = generateIdentity(accountSequence.value);

    // 5. 构建设备名称字符串
    let deviceNameStr = '未命名设备';
    if (command.deviceName) {
      const parts: string[] = [];
      if (command.deviceName.model) parts.push(command.deviceName.model);
      if (command.deviceName.platform) parts.push(command.deviceName.platform);
      if (command.deviceName.osVersion)
        parts.push(command.deviceName.osVersion);
      if (parts.length > 0) deviceNameStr = parts.join(' ');
    }

    // 6. 创建用户账户（带手机号，但未验证）
    const account = UserAccount.create({
      accountSequence,
      phoneNumber,
      initialDeviceId: command.deviceId,
      deviceName: deviceNameStr,
      deviceInfo: command.deviceName,
      inviterSequence,
    });
    this.logger.log(
      `[REGISTER_NO_SMS] Account aggregate created (not saved yet): sequence=${accountSequence.value}`,
    );

    // 7. 设置随机用户名和头像
    account.updateProfile({
      nickname: identity.username,
      avatarUrl: identity.avatarSvg,
    });

    // 8. 保存账户
    this.logger.log(`[REGISTER_NO_SMS] Saving account to database...`);
    await this.userRepository.save(account);
    this.logger.log(
      `[REGISTER_NO_SMS] Account saved: userId=${account.userId.value}, sequence=${account.accountSequence.value}`,
    );

    // 8.1 验证保存成功
    if (account.userId.value === BigInt(0)) {
      this.logger.error(
        `[REGISTER_NO_SMS] CRITICAL: userId is still 0 after save! sequence=${accountSequence.value}`,
      );
      throw new ApplicationError('账户保存失败：未获取到用户ID');
    }

    // 9. 设置密码，同时设置 phoneVerified = false 和 kycStatus = 'NOT_STARTED'
    this.logger.log(
      `[REGISTER_NO_SMS] Setting password hash and phoneVerified=false...`,
    );
    const bcrypt = await import('bcrypt');
    const passwordHash = await bcrypt.hash(command.password, 10);
    await this.prisma.userAccount.update({
      where: { userId: account.userId.value },
      data: {
        passwordHash,
        phoneVerified: false,
        kycStatus: 'NOT_STARTED',
      },
    });
    this.logger.log(`[REGISTER_NO_SMS] Password hash set, phoneVerified=false`);

    // 10. 生成 Token
    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: command.deviceId,
    });
    this.logger.log(`[REGISTER_NO_SMS] Tokens generated`);

    // 11. 更新事件中的 userId（save 后才有真实 userId）
    account.updateEventsUserId();

    // 12. 发布领域事件
    this.logger.log(
      `[REGISTER_NO_SMS] Publishing ${account.domainEvents.length} domain events...`,
    );
    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();
    this.logger.log(`[REGISTER_NO_SMS] Domain events published`);

    // 12. 发布 MPC Keygen 请求事件 (触发后台生成钱包)
    const sessionId = crypto.randomUUID();
    await this.eventPublisher.publish(
      new MpcKeygenRequestedEvent({
        sessionId,
        userId: account.userId.toString(),
        accountSequence: account.accountSequence.value,
        username: `user_${account.accountSequence.value}`,
        threshold: 2,
        totalParties: 3,
        requireDelegate: true,
      }),
    );

    this.logger.log(
      `[REGISTER_NO_SMS] COMPLETE: sequence=${accountSequence.value}, phone=${phoneNumber.value}, userId=${account.userId.value}, phoneVerified=false`,
    );

    return {
      userSerialNum: account.accountSequence.value,
      referralCode: account.referralCode.value,
      username: account.nickname,
      avatarSvg: account.avatarUrl || identity.avatarSvg,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  async recoverByMnemonic(
    command: RecoverByMnemonicCommand,
  ): Promise<RecoverAccountResult> {
    const accountSequence = AccountSequence.create(command.accountSequence);
    const account =
      await this.userRepository.findByAccountSequence(accountSequence);
    if (!account) {
      // 发布恢复失败审计事件
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'mnemonic',
          failureReason: '账户序列号不存在',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('账户序列号不存在');
    }
    if (!account.isActive) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'mnemonic',
          failureReason: '账户已冻结或注销',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('账户已冻结或注销');
    }

    // 检查验证失败次数限制 (防止暴力破解)
    const failKey = `mnemonic:fail:${command.accountSequence}`;
    const failCountStr = await this.redisService.get(failKey);
    const failCount = failCountStr ? parseInt(failCountStr, 10) : 0;
    if (failCount >= 5) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'mnemonic',
          failureReason: '验证次数过多',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('验证次数过多，请1小时后重试');
    }

    // 调用 blockchain-service 验证助记词 hash
    // 助记词作为身份凭证，通过 hash 匹配验证，不需要与钱包地址关联
    const verifyResult = await this.blockchainClient.verifyMnemonicByAccount({
      accountSequence: command.accountSequence,
      mnemonic: command.mnemonic,
    });

    if (!verifyResult.valid) {
      // 记录失败次数
      await this.redisService.incr(failKey);
      await this.redisService.expire(failKey, 3600); // 1小时过期
      // 发布恢复失败审计事件
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'mnemonic',
          failureReason: verifyResult.message || '助记词错误',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError(verifyResult.message || '助记词错误');
    }

    // 验证成功，清除失败计数
    await this.redisService.delete(failKey);

    account.addDevice(command.newDeviceId, command.deviceName);
    account.recordLogin();
    await this.userRepository.save(account);

    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: command.newDeviceId,
    });

    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();

    // 发布账户恢复成功审计事件
    await this.eventPublisher.publish(
      new AccountRecoveredEvent({
        userId: account.userId.toString(),
        accountSequence: account.accountSequence.value,
        recoveryMethod: 'mnemonic',
        deviceId: command.newDeviceId,
        deviceName: command.deviceName,
        recoveredAt: new Date(),
      }),
    );

    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      nickname: account.nickname,
      avatarUrl: account.avatarUrl,
      referralCode: account.referralCode.value,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  async recoverByPhone(
    command: RecoverByPhoneCommand,
  ): Promise<RecoverAccountResult> {
    const accountSequence = AccountSequence.create(command.accountSequence);
    const account =
      await this.userRepository.findByAccountSequence(accountSequence);
    if (!account) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'phone',
          failureReason: '账户序列号不存在',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('账户序列号不存在');
    }
    if (!account.isActive) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'phone',
          failureReason: '账户已冻结或注销',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('账户已冻结或注销');
    }
    if (!account.phoneNumber) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'phone',
          failureReason: '账户未绑定手机号',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('该账户未绑定手机号，请使用助记词恢复');
    }

    const phoneNumber = PhoneNumber.create(command.phoneNumber);
    if (!account.phoneNumber.equals(phoneNumber)) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'phone',
          failureReason: '手机号与账户不匹配',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('手机号与账户不匹配');
    }

    const cachedCode = await this.redisService.get(
      `sms:recover:${phoneNumber.value}`,
    );
    if (cachedCode !== command.smsCode) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence: command.accountSequence,
          recoveryMethod: 'phone',
          failureReason: '验证码错误或已过期',
          deviceId: command.newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('验证码错误或已过期');
    }

    account.addDevice(command.newDeviceId, command.deviceName);
    account.recordLogin();
    await this.userRepository.save(account);
    await this.redisService.delete(`sms:recover:${phoneNumber.value}`);

    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: command.newDeviceId,
    });

    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();

    // 发布账户恢复成功审计事件
    await this.eventPublisher.publish(
      new AccountRecoveredEvent({
        userId: account.userId.toString(),
        accountSequence: account.accountSequence.value,
        recoveryMethod: 'phone',
        deviceId: command.newDeviceId,
        deviceName: command.deviceName,
        recoveredAt: new Date(),
      }),
    );

    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      nickname: account.nickname,
      avatarUrl: account.avatarUrl,
      referralCode: account.referralCode.value,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  async autoLogin(command: AutoLoginCommand): Promise<AutoLoginResult> {
    const payload = await this.tokenService.verifyRefreshToken(
      command.refreshToken,
    );
    const account = await this.userRepository.findById(
      UserId.create(payload.userId),
    );
    if (!account || !account.isActive)
      throw new ApplicationError('账户不存在或已冻结');
    if (!account.isDeviceAuthorized(command.deviceId)) {
      throw new ApplicationError(
        '设备未授权，请重新登录',
        'DEVICE_UNAUTHORIZED',
      );
    }

    account.addDevice(command.deviceId);
    account.recordLogin();
    await this.userRepository.save(account);

    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: command.deviceId,
    });

    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  async sendSmsCode(command: SendSmsCodeCommand): Promise<void> {
    const phoneNumber = PhoneNumber.create(command.phoneNumber);
    const code = this.generateSmsCode();
    const cacheKey = `sms:${command.type.toLowerCase()}:${phoneNumber.value}`;

    // 先存储到 Redis，再发送短信
    // 避免短信发送过程中用户已收到验证码但 Redis 中还没有存储的竞态条件
    await this.redisService.set(cacheKey, code, 300);
    await this.smsService.sendVerificationCode(phoneNumber.value, code);
  }

  async register(command: RegisterCommand): Promise<RegisterResult> {
    const phoneNumber = PhoneNumber.create(command.phoneNumber);
    const cachedCode = await this.redisService.get(
      `sms:register:${phoneNumber.value}`,
    );
    if (cachedCode !== command.smsCode)
      throw new ApplicationError('验证码错误或已过期');

    const phoneValidation =
      await this.validatorService.validatePhoneNumber(phoneNumber);
    if (!phoneValidation.isValid)
      throw new ApplicationError(phoneValidation.errorMessage!);

    let inviterSequence: AccountSequence | null = null;
    if (command.inviterReferralCode) {
      const referralCode = ReferralCode.create(command.inviterReferralCode);
      const referralValidation =
        await this.validatorService.validateReferralCode(referralCode);
      if (!referralValidation.isValid)
        throw new ApplicationError(referralValidation.errorMessage!);
      const inviter =
        await this.userRepository.findByReferralCode(referralCode);
      inviterSequence = inviter!.accountSequence;
    }

    const accountSequence =
      await this.sequenceGenerator.generateNextUserSequence();

    const account = UserAccount.create({
      accountSequence,
      phoneNumber,
      initialDeviceId: command.deviceId,
      deviceName: command.deviceName,
      inviterSequence,
    });

    await this.userRepository.save(account);
    await this.redisService.delete(`sms:register:${phoneNumber.value}`);

    // 更新事件中的 userId（save 后才有真实 userId）
    account.updateEventsUserId();

    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();

    // 发布 MPC Keygen 请求事件 (触发后台生成钱包)
    const sessionId = crypto.randomUUID();
    await this.eventPublisher.publish(
      new MpcKeygenRequestedEvent({
        sessionId,
        userId: account.userId.toString(),
        accountSequence: account.accountSequence.value,
        username: `user_${account.accountSequence.value}`,
        threshold: 2,
        totalParties: 3,
        requireDelegate: true,
      }),
    );

    this.logger.log(
      `Account registered: sequence=${accountSequence.value}, phone=${phoneNumber.value}, MPC keygen requested`,
    );

    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: command.deviceId,
    });

    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      referralCode: account.referralCode.value,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  async login(command: LoginCommand): Promise<LoginResult> {
    const phoneNumber = PhoneNumber.create(command.phoneNumber);
    const cachedCode = await this.redisService.get(
      `sms:login:${phoneNumber.value}`,
    );
    if (cachedCode !== command.smsCode)
      throw new ApplicationError('验证码错误或已过期');

    const account = await this.userRepository.findByPhoneNumber(phoneNumber);
    if (!account) throw new ApplicationError('用户不存在');
    if (!account.isActive) throw new ApplicationError('账户已冻结或注销');

    account.addDevice(command.deviceId);
    account.recordLogin();
    await this.userRepository.save(account);
    await this.redisService.delete(`sms:login:${phoneNumber.value}`);

    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: command.deviceId,
    });

    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  /**
   * 手机号+密码登录
   *
   * 用于账号恢复功能，使用手机号和密码登录
   */
  async loginWithPassword(
    phoneNumber: string,
    password: string,
    deviceId: string,
  ): Promise<LoginResult> {
    this.logger.log(
      `[LOGIN_PASSWORD] Attempting login for phone: ${phoneNumber}`,
    );

    // 1. 验证手机号格式
    const phone = PhoneNumber.create(phoneNumber);

    // 2. 查找用户
    const account = await this.userRepository.findByPhoneNumber(phone);
    if (!account) {
      this.logger.warn(
        `[LOGIN_PASSWORD] User not found for phone: ${phoneNumber}`,
      );
      throw new ApplicationError('手机号或密码错误');
    }

    // 3. 检查账户状态
    if (!account.isActive) {
      this.logger.warn(
        `[LOGIN_PASSWORD] Account inactive for phone: ${phoneNumber}`,
      );
      throw new ApplicationError('账户已冻结或注销');
    }

    // 4. 验证密码
    const isPasswordValid = await this.verifyPassword(
      account.userId.toString(),
      password,
    );
    if (!isPasswordValid) {
      this.logger.warn(
        `[LOGIN_PASSWORD] Invalid password for phone: ${phoneNumber}`,
      );
      throw new ApplicationError('手机号或密码错误');
    }

    // 5. 添加设备并记录登录
    account.addDevice(deviceId);
    account.recordLogin();
    await this.userRepository.save(account);

    // 6. 生成token
    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId,
    });

    this.logger.log(
      `[LOGIN_PASSWORD] Login successful for phone: ${phoneNumber}, userId: ${account.userId.toString()}`,
    );

    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  async bindPhoneNumber(command: BindPhoneNumberCommand): Promise<void> {
    const account = await this.userRepository.findById(
      UserId.create(command.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');

    const phoneNumber = PhoneNumber.create(command.phoneNumber);
    const cachedCode = await this.redisService.get(
      `sms:bind:${phoneNumber.value}`,
    );
    if (cachedCode !== command.smsCode)
      throw new ApplicationError('验证码错误或已过期');

    const validation =
      await this.validatorService.validatePhoneNumber(phoneNumber);
    if (!validation.isValid)
      throw new ApplicationError(validation.errorMessage!);

    account.bindPhoneNumber(phoneNumber);
    await this.userRepository.save(account);
    await this.redisService.delete(`sms:bind:${phoneNumber.value}`);
    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();
  }

  // ============ 更换手机号相关方法 ============

  /**
   * 发送旧手机验证码（更换手机号第一步）
   */
  async sendOldPhoneCode(userId: string): Promise<void> {
    this.logger.log(`[CHANGE_PHONE] Sending code to old phone for user: ${userId}`);

    const account = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: { phoneNumber: true, phoneVerified: true },
    });

    if (!account) {
      throw new ApplicationError('用户不存在');
    }

    if (!account.phoneNumber) {
      throw new ApplicationError('您还未绑定手机号');
    }

    // 生成并发送验证码
    const code = this.generateSmsCode();
    const cacheKey = `sms:change_phone_old:${account.phoneNumber}`;

    await this.redisService.set(cacheKey, code, 300); // 5分钟有效
    await this.smsService.sendVerificationCode(account.phoneNumber, code);

    this.logger.log(`[CHANGE_PHONE] Old phone code sent to: ${this.maskPhoneNumber(account.phoneNumber)}`);
  }

  /**
   * 验证旧手机验证码（更换手机号第二步）
   * 返回临时令牌用于后续操作
   * 同时更新 phoneVerified 状态（首次验证时）
   */
  async verifyOldPhoneCode(userId: string, smsCode: string): Promise<{
    changePhoneToken: string;
    wasAlreadyVerified: boolean;
  }> {
    this.logger.log(`[CHANGE_PHONE] Verifying old phone code for user: ${userId}`);

    const account = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: { phoneNumber: true, phoneVerified: true },
    });

    if (!account?.phoneNumber) {
      throw new ApplicationError('用户未绑定手机号');
    }

    const cacheKey = `sms:change_phone_old:${account.phoneNumber}`;
    const cachedCode = await this.redisService.get(cacheKey);

    if (!cachedCode) {
      throw new ApplicationError('验证码已过期，请重新获取');
    }

    if (cachedCode !== smsCode) {
      throw new ApplicationError('验证码错误');
    }

    // 删除已使用的验证码
    await this.redisService.delete(cacheKey);

    // 记录之前是否已验证过
    const wasAlreadyVerified = account.phoneVerified;

    // 如果手机号之前未验证，更新为已验证状态
    if (!account.phoneVerified) {
      await this.prisma.userAccount.update({
        where: { userId: BigInt(userId) },
        data: {
          phoneVerified: true,
          phoneVerifiedAt: new Date(),
        },
      });
      this.logger.log(`[CHANGE_PHONE] Phone marked as verified for user: ${userId}`);
    }

    // 生成临时令牌（10分钟有效）
    const changePhoneToken = `CPT_${userId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
    await this.redisService.set(`change_phone_token:${userId}`, changePhoneToken, 600);

    this.logger.log(`[CHANGE_PHONE] Old phone verified for user: ${userId}`);

    return { changePhoneToken, wasAlreadyVerified };
  }

  /**
   * 发送新手机验证码（更换手机号第三步）
   */
  async sendNewPhoneCode(
    userId: string,
    newPhoneNumber: string,
    changePhoneToken: string,
  ): Promise<void> {
    this.logger.log(`[CHANGE_PHONE] Sending code to new phone for user: ${userId}`);

    // 验证临时令牌
    const cachedToken = await this.redisService.get(`change_phone_token:${userId}`);
    if (!cachedToken || cachedToken !== changePhoneToken) {
      throw new ApplicationError('操作已过期，请重新验证旧手机号');
    }

    // 验证新手机号格式
    const phoneNumber = PhoneNumber.create(newPhoneNumber);

    // 检查新手机号是否已被使用
    const existingUser = await this.prisma.userAccount.findFirst({
      where: {
        phoneNumber: phoneNumber.value,
        NOT: { userId: BigInt(userId) },
      },
    });
    if (existingUser) {
      throw new ApplicationError('该手机号已被其他账户绑定');
    }

    // 发送验证码
    const code = this.generateSmsCode();
    const cacheKey = `sms:change_phone_new:${phoneNumber.value}`;

    await this.redisService.set(cacheKey, code, 300); // 5分钟有效
    await this.smsService.sendVerificationCode(phoneNumber.value, code);

    this.logger.log(`[CHANGE_PHONE] New phone code sent to: ${this.maskPhoneNumber(phoneNumber.value)}`);
  }

  /**
   * 确认更换手机号（更换手机号第四步）
   */
  async confirmChangePhone(
    userId: string,
    newPhoneNumber: string,
    smsCode: string,
    changePhoneToken: string,
  ): Promise<void> {
    this.logger.log(`[CHANGE_PHONE] Confirming phone change for user: ${userId}`);

    // 验证临时令牌
    const cachedToken = await this.redisService.get(`change_phone_token:${userId}`);
    if (!cachedToken || cachedToken !== changePhoneToken) {
      throw new ApplicationError('操作已过期，请重新验证旧手机号');
    }

    const phoneNumber = PhoneNumber.create(newPhoneNumber);

    // 验证新手机验证码
    const cacheKey = `sms:change_phone_new:${phoneNumber.value}`;
    const cachedCode = await this.redisService.get(cacheKey);

    if (!cachedCode) {
      throw new ApplicationError('验证码已过期，请重新获取');
    }

    if (cachedCode !== smsCode) {
      throw new ApplicationError('验证码错误');
    }

    // 再次检查新手机号是否已被使用
    const existingUser = await this.prisma.userAccount.findFirst({
      where: {
        phoneNumber: phoneNumber.value,
        NOT: { userId: BigInt(userId) },
      },
    });
    if (existingUser) {
      throw new ApplicationError('该手机号已被其他账户绑定');
    }

    // 更新手机号
    await this.prisma.userAccount.update({
      where: { userId: BigInt(userId) },
      data: {
        phoneNumber: phoneNumber.value,
        phoneVerified: true,
        phoneVerifiedAt: new Date(),
      },
    });

    // 清理缓存
    await this.redisService.delete(cacheKey);
    await this.redisService.delete(`change_phone_token:${userId}`);

    this.logger.log(`[CHANGE_PHONE] Phone changed successfully for user: ${userId}`);
  }

  /**
   * 获取手机号状态
   */
  async getPhoneStatus(userId: string): Promise<{
    isBound: boolean;
    isVerified: boolean;
    phoneNumber: string | null;
    verifiedAt: Date | null;
  }> {
    const account = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: {
        phoneNumber: true,
        phoneVerified: true,
        phoneVerifiedAt: true,
      },
    });

    if (!account) {
      throw new ApplicationError('用户不存在');
    }

    return {
      isBound: !!account.phoneNumber,
      isVerified: account.phoneVerified,
      phoneNumber: account.phoneNumber ? this.maskPhoneNumber(account.phoneNumber) : null,
      verifiedAt: account.phoneVerifiedAt,
    };
  }

  async updateProfile(command: UpdateProfileCommand): Promise<void> {
    const account = await this.userRepository.findById(
      UserId.create(command.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');

    account.updateProfile({
      nickname: command.nickname,
      avatarUrl: command.avatarUrl,
    });

    await this.userRepository.save(account);

    // 发布用户资料更新事件
    const event = new UserProfileUpdatedEvent({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      nickname: account.nickname,
      avatarUrl: account.avatarUrl,
      updatedAt: new Date(),
    });
    await this.eventPublisher.publishAll([event]);
  }

  async submitKYC(command: SubmitKYCCommand): Promise<void> {
    const account = await this.userRepository.findById(
      UserId.create(command.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');

    const kycInfo = KYCInfo.create({
      realName: command.realName,
      idCardNumber: command.idCardNumber,
      idCardFrontUrl: command.idCardFrontUrl,
      idCardBackUrl: command.idCardBackUrl,
    });

    account.submitKYC(kycInfo);
    await this.userRepository.save(account);
    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();
  }

  async reviewKYC(command: ReviewKYCCommand): Promise<void> {
    const account = await this.userRepository.findById(
      UserId.create(command.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');

    if (command.approved) {
      account.approveKYC();
    } else {
      account.rejectKYC(command.reason || '审核未通过');
    }

    await this.userRepository.save(account);
    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();
  }

  async getMyDevices(query: GetMyDevicesQuery): Promise<DeviceDTO[]> {
    const account = await this.userRepository.findById(
      UserId.create(query.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');

    return account.getAllDevices().map((device) => ({
      deviceId: device.deviceId,
      deviceName: device.deviceName,
      addedAt: device.addedAt,
      lastActiveAt: device.lastActiveAt,
      isCurrent: device.deviceId === query.currentDeviceId,
    }));
  }

  async removeDevice(command: RemoveDeviceCommand): Promise<void> {
    const account = await this.userRepository.findById(
      UserId.create(command.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');
    if (command.deviceIdToRemove === command.currentDeviceId) {
      throw new ApplicationError('不能删除当前设备');
    }

    account.removeDevice(command.deviceIdToRemove);
    await this.userRepository.save(account);
    await this.tokenService.revokeDeviceTokens(
      account.userId.toString(),
      command.deviceIdToRemove,
    );
    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();
  }

  async getMyProfile(query: GetMyProfileQuery): Promise<UserProfileDTO> {
    const account = await this.userRepository.findById(
      UserId.create(query.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');
    return this.toUserProfileDTO(account);
  }

  async getUserByReferralCode(
    query: GetUserByReferralCodeQuery,
  ): Promise<UserBriefDTO | null> {
    const account = await this.userRepository.findByReferralCode(
      ReferralCode.create(query.referralCode),
    );
    if (!account) {
      throw new ApplicationError('推荐码不存在或无效');
    }

    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      nickname: account.nickname,
      avatarUrl: account.avatarUrl,
    };
  }

  private toUserProfileDTO(account: UserAccount): UserProfileDTO {
    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      phoneNumber: account.phoneNumber?.masked() || null,
      nickname: account.nickname,
      avatarUrl: account.avatarUrl,
      referralCode: account.referralCode.value,
      walletAddresses: account.getAllWalletAddresses().map((wa) => ({
        chainType: wa.chainType,
        address: wa.address,
      })),
      kycStatus: account.kycStatus,
      kycInfo: account.kycInfo
        ? {
            realName: account.kycInfo.realName,
            idCardNumber: account.kycInfo.maskedIdCardNumber(),
          }
        : null,
      status: account.status,
      registeredAt: account.registeredAt,
      lastLoginAt: account.lastLoginAt,
    };
  }

  private generateSmsCode(): string {
    return String(Math.floor(100000 + Math.random() * 900000));
  }

  // ============ 推荐/分享相关 API ============

  /**
   * 获取当前登录用户信息 (GET /api/me)
   */
  async getMe(userId: string): Promise<MeResult> {
    this.logger.debug(`getMe() - userId: ${userId}`);
    const account = await this.userRepository.findById(UserId.create(userId));
    if (!account) throw new ApplicationError('用户不存在');

    this.logger.debug(
      `getMe() - account found: seq=${account.accountSequence.value}, nickname=${account.nickname}`,
    );
    this.logger.debug(
      `getMe() - avatarUrl: ${account.avatarUrl ? `长度=${account.avatarUrl.length}` : 'null'}`,
    );
    if (account.avatarUrl) {
      this.logger.debug(
        `getMe() - avatarUrl前100字符: ${account.avatarUrl.substring(0, 100)}`,
      );
    }

    const baseUrl = 'https://app.rwadurian.com'; // TODO: 从配置读取
    const referralLink = `${baseUrl}/invite/${account.referralCode.value}`;

    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      phoneNumber: account.phoneNumber?.masked() || null,
      nickname: account.nickname,
      avatarUrl: account.avatarUrl,
      referralCode: account.referralCode.value,
      referralLink,
      inviterSequence: account.inviterSequence?.value || null,
      walletAddresses: account.getAllWalletAddresses().map((wa) => ({
        chainType: wa.chainType,
        address: wa.address,
      })),
      kycStatus: account.kycStatus,
      status: account.status,
      registeredAt: account.registeredAt,
    };
  }

  /**
   * 验证推荐码是否有效 (GET /api/referrals/validate)
   */
  async validateReferralCode(
    query: ValidateReferralCodeQuery,
  ): Promise<ReferralCodeValidationResult> {
    try {
      const referralCode = ReferralCode.create(query.referralCode);
      const account =
        await this.userRepository.findByReferralCode(referralCode);

      if (!account) {
        return { valid: false, message: '推荐码不存在' };
      }

      if (!account.isActive) {
        return { valid: false, message: '推荐人账户已冻结' };
      }

      return {
        valid: true,
        referralCode: account.referralCode.value,
        inviterInfo: {
          accountSequence: account.accountSequence.value,
          nickname: account.nickname,
          avatarUrl: account.avatarUrl,
        },
      };
    } catch (error) {
      return { valid: false, message: '推荐码格式无效' };
    }
  }

  /**
   * 生成推荐链接 (POST /api/referrals/links)
   */
  async generateReferralLink(
    command: GenerateReferralLinkCommand,
  ): Promise<ReferralLinkResult> {
    const account = await this.userRepository.findById(
      UserId.create(command.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');

    // 生成短链码 (6位随机字符)
    const shortCode = this.generateShortCode();
    const baseUrl = 'https://app.rwadurian.com'; // TODO: 从配置读取

    // 保存到数据库
    const link = await this.userRepository.createReferralLink({
      userId: account.userId.value,
      referralCode: account.referralCode.value,
      shortCode,
      channel: command.channel || null,
      campaignId: command.campaignId || null,
    });

    return {
      linkId: link.linkId.toString(),
      referralCode: account.referralCode.value,
      shortUrl: `${baseUrl}/r/${shortCode}`,
      fullUrl: `${baseUrl}/invite/${account.referralCode.value}?ch=${command.channel || 'default'}`,
      channel: command.channel || null,
      campaignId: command.campaignId || null,
      createdAt: link.createdAt,
    };
  }

  /**
   * 查询邀请统计 (GET /api/referrals/stats)
   */
  async getReferralStats(
    query: GetReferralStatsQuery,
  ): Promise<ReferralStatsResult> {
    const account = await this.userRepository.findById(
      UserId.create(query.userId),
    );
    if (!account) throw new ApplicationError('用户不存在');

    // 查询直接邀请的用户
    const directInvites = await this.userRepository.findByInviterSequence(
      account.accountSequence,
    );

    // 查询间接邀请 (二级)
    let indirectInvites: typeof directInvites = [];
    for (const invite of directInvites) {
      const secondLevel = await this.userRepository.findByInviterSequence(
        invite.accountSequence,
      );
      indirectInvites = indirectInvites.concat(secondLevel);
    }

    // 时间统计
    const now = new Date();
    const todayStart = new Date(
      now.getFullYear(),
      now.getMonth(),
      now.getDate(),
    );
    const weekStart = new Date(todayStart);
    weekStart.setDate(weekStart.getDate() - weekStart.getDay());
    const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);

    const todayInvites = directInvites.filter(
      (u) => u.registeredAt >= todayStart,
    ).length;
    const thisWeekInvites = directInvites.filter(
      (u) => u.registeredAt >= weekStart,
    ).length;
    const thisMonthInvites = directInvites.filter(
      (u) => u.registeredAt >= monthStart,
    ).length;

    // 合并并排序最近邀请
    interface InviteRecord {
      account: UserAccount;
      level: number;
    }
    const allInvites: InviteRecord[] = [
      ...directInvites.map((u) => ({ account: u, level: 1 })),
      ...indirectInvites.map((u) => ({ account: u, level: 2 })),
    ]
      .sort(
        (a, b) =>
          b.account.registeredAt.getTime() - a.account.registeredAt.getTime(),
      )
      .slice(0, 20);

    return {
      referralCode: account.referralCode.value,
      totalInvites: directInvites.length + indirectInvites.length,
      directInvites: directInvites.length,
      indirectInvites: indirectInvites.length,
      todayInvites,
      thisWeekInvites,
      thisMonthInvites,
      recentInvites: allInvites.map(({ account: u, level }) => ({
        accountSequence: u.accountSequence.value,
        nickname: u.nickname,
        avatarUrl: u.avatarUrl,
        registeredAt: u.registeredAt,
        level,
      })),
    };
  }

  /**
   * 生成短链码
   */
  private generateShortCode(): string {
    const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
    let result = '';
    for (let i = 0; i < 6; i++) {
      result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
  }

  // ============ 钱包状态查询 ============

  /**
   * 获取钱包状态 (GET /user/{userSerialNum}/wallet)
   *
   * 钱包通过 Kafka 事件异步生成，此接口用于轮询查询状态
   * 首次查询时会返回助记词（未备份状态），之后不再返回
   *
   * 增强功能：
   * - 检查 Redis 中的实际生成状态
   * - 如果状态是 failed 或超时，返回 failed 状态
   * - 同时自动触发重试（使用分布式锁防止重复触发）
   */
  async getWalletStatus(
    query: GetWalletStatusQuery,
  ): Promise<WalletStatusResult> {
    const accountSequence = AccountSequence.create(query.userSerialNum);
    const account =
      await this.userRepository.findByAccountSequence(accountSequence);

    if (!account) {
      throw new ApplicationError('用户不存在');
    }

    const userId = account.userId.value.toString();

    // 获取所有钱包地址
    const wallets = account.getAllWalletAddresses();

    // 检查是否已有三条链的钱包地址
    const kavaWallet = wallets.find((w) => w.chainType === ChainType.KAVA);
    const dstWallet = wallets.find((w) => w.chainType === ChainType.DST);
    const bscWallet = wallets.find((w) => w.chainType === ChainType.BSC);

    if (kavaWallet && dstWallet && bscWallet) {
      // 钱包已就绪，获取助记词
      const mnemonic = await this.getRecoveryMnemonic(
        BigInt(account.userId.value),
      );

      // 清理 Redis 状态（可选，节省内存）
      await this.redisService.delete(`keygen:status:${userId}`);
      await this.redisService.delete(`keygen:retry:${userId}`);

      return {
        status: 'ready',
        walletAddresses: {
          kava: kavaWallet.address,
          dst: dstWallet.address,
          bsc: bscWallet.address,
        },
        mnemonic: mnemonic || '',
      };
    }

    // 钱包不完整，检查 Redis 中的生成状态
    const redisKey = `keygen:status:${userId}`;
    const statusData = await this.redisService.get(redisKey);

    let shouldTriggerRetry = false;
    let isFailed = false;
    let errorMessage = '';

    if (statusData) {
      try {
        const parsed = JSON.parse(statusData);
        const status = parsed.status;
        const updatedAt = parsed.updatedAt;

        // 检查状态是否超时（超过60秒未更新）
        const TIMEOUT_MS = 60 * 1000;
        const isTimeout =
          updatedAt && Date.now() - new Date(updatedAt).getTime() > TIMEOUT_MS;

        if (status === 'failed') {
          // 明确失败状态
          isFailed = true;
          errorMessage = parsed.errorMessage || '钱包生成失败';
          shouldTriggerRetry = true;
          this.logger.log(
            `[WALLET-STATUS] User ${userId} status is 'failed', will trigger retry`,
          );
        } else if (
          (status === 'generating' || status === 'deriving') &&
          isTimeout
        ) {
          // 超时状态
          isFailed = true;
          errorMessage = '钱包生成超时';
          shouldTriggerRetry = true;
          this.logger.log(
            `[WALLET-STATUS] User ${userId} status '${status}' timed out, will trigger retry`,
          );
        } else if (status === 'completed') {
          // Redis 显示 completed 但数据库没有完整地址，状态不一致
          shouldTriggerRetry = true;
          this.logger.warn(
            `[WALLET-STATUS] User ${userId} Redis status is 'completed' but wallet incomplete, will trigger retry`,
          );
        }
        // pending/generating/deriving 且未超时 → 正常等待
      } catch (e) {
        // JSON 解析失败，触发重试
        shouldTriggerRetry = true;
        this.logger.warn(
          `[WALLET-STATUS] Failed to parse status data for user ${userId}, will trigger retry`,
        );
      }
    } else {
      // Redis 无状态但数据库钱包不完整 → 需要触发生成
      shouldTriggerRetry = true;
      this.logger.log(
        `[WALLET-STATUS] User ${userId} has no Redis status but wallet incomplete, will trigger retry`,
      );
    }

    // 如果需要触发重试，使用分布式锁防止并发
    if (shouldTriggerRetry) {
      const lockKey = `keygen:lock:${userId}`;
      const gotLock = await this.redisService.tryLock(lockKey, 60); // 60秒锁

      if (gotLock) {
        // 异步触发重试，不阻塞响应
        this.triggerWalletRetryAsync(userId, account).catch((err) => {
          this.logger.error(
            `[WALLET-STATUS] Async retry failed for user ${userId}`,
            err,
          );
        });
        this.logger.log(
          `[WALLET-STATUS] Acquired lock and triggered retry for user ${userId}`,
        );
      } else {
        this.logger.log(
          `[WALLET-STATUS] Lock not acquired for user ${userId}, retry already in progress`,
        );
      }
    }

    // 如果是失败状态，返回 failed；否则返回 generating
    if (isFailed) {
      return {
        status: 'failed',
        errorMessage,
      };
    }

    return {
      status: 'generating',
    };
  }

  /**
   * 异步触发钱包生成重试
   *
   * 内部方法，由 getWalletStatus 调用
   * 发布 MpcKeygenRequestedEvent 触发 MPC 生成流程
   */
  private async triggerWalletRetryAsync(
    userId: string,
    account: UserAccount,
  ): Promise<void> {
    try {
      // 发布 MpcKeygenRequestedEvent 触发钱包生成（与注册流程使用相同的事件类型）
      const sessionId = crypto.randomUUID();
      await this.eventPublisher.publish(
        new MpcKeygenRequestedEvent({
          sessionId,
          userId: account.userId.toString(),
          accountSequence: account.accountSequence.value,
          username: `user_${account.accountSequence.value}`,
          threshold: 2,
          totalParties: 3,
          requireDelegate: true,
        }),
      );

      // 更新 Redis 状态为 pending
      const statusData = {
        status: 'pending',
        userId,
        updatedAt: new Date().toISOString(),
      };

      await this.redisService.set(
        `keygen:status:${userId}`,
        JSON.stringify(statusData),
        60 * 60 * 24, // 24 小时
      );

      this.logger.log(
        `[WALLET-STATUS] Wallet generation retry triggered for user ${userId}, sessionId=${sessionId}`,
      );
    } catch (error) {
      this.logger.error(
        `[WALLET-STATUS] Failed to trigger retry for user ${userId}`,
        error,
      );
      throw error;
    }
  }

  /**
   * 手动重试钱包生成
   *
   * 用户可以通过此方法手动触发钱包生成重试
   * 幂等操作：重新发布 UserAccountCreatedEvent
   *
   * 保护机制：
   * - 钱包已完成时，直接返回不重试
   * - 钱包正在生成中（generating/deriving/pending），直接返回不重试
   * - 只有失败或无状态时才触发重试
   */
  async retryWalletGeneration(userId: string): Promise<void> {
    this.logger.log(
      `[WALLET-RETRY] Manual retry requested for user: ${userId}`,
    );

    try {
      // 1. 获取用户账号信息
      const userIdObj = UserId.create(BigInt(userId));
      const account = await this.userRepository.findById(userIdObj);

      if (!account) {
        throw new ApplicationError('用户不存在');
      }

      // 2. 检查钱包是否已经生成完成
      const wallets = account.getAllWalletAddresses();
      const kavaWallet = wallets.find((w) => w.chainType === ChainType.KAVA);
      const dstWallet = wallets.find((w) => w.chainType === ChainType.DST);
      const bscWallet = wallets.find((w) => w.chainType === ChainType.BSC);

      if (kavaWallet && dstWallet && bscWallet) {
        this.logger.log(
          `[WALLET-RETRY] Wallet already complete for user: ${userId}`,
        );
        return; // 钱包已完成，无需重试
      }

      // 3. 检查 Redis 中的 keygen 状态，防止重复触发
      const redisKey = `keygen:status:${userId}`;
      const currentStatusData = await this.redisService.get(redisKey);

      if (currentStatusData) {
        try {
          const parsed = JSON.parse(currentStatusData);
          const status = parsed.status;
          const updatedAt = parsed.updatedAt;

          // 检查状态是否超时（超过60秒未更新）
          const TIMEOUT_MS = 60 * 1000;
          const isTimeout = updatedAt && (Date.now() - new Date(updatedAt).getTime() > TIMEOUT_MS);

          // 如果状态是 pending/generating/deriving 且未超时，说明正在生成中，不触发重试
          if ((status === 'pending' || status === 'generating' || status === 'deriving') && !isTimeout) {
            this.logger.log(
              `[WALLET-RETRY] Wallet generation in progress (status=${status}) for user: ${userId}, skip retry`,
            );
            return; // 正在生成中，无需重试
          }

          // 如果超时，记录日志并继续重试
          if (isTimeout) {
            this.logger.log(
              `[WALLET-RETRY] Status ${status} timed out for user: ${userId}, forcing retry`,
            );
          }

          // 如果状态是 completed，但数据库没有完整地址，说明是过期状态，继续重试
          // 如果状态是 failed，允许重试
          this.logger.log(
            `[WALLET-RETRY] Current status: ${status}, will trigger retry`,
          );
        } catch (e) {
          // JSON 解析失败，继续执行重试
          this.logger.warn(
            `[WALLET-RETRY] Failed to parse status data for user: ${userId}`,
          );
        }
      }

      // 4. 重新触发钱包生成流程 - 使用 MpcKeygenRequestedEvent（MPC服务只监听此事件）
      const sessionId = crypto.randomUUID();
      await this.eventPublisher.publish(
        new MpcKeygenRequestedEvent({
          sessionId,
          userId: account.userId.toString(),
          accountSequence: account.accountSequence.value,
          username: `user_${account.accountSequence.value}`,
          threshold: 2,
          totalParties: 3,
          requireDelegate: true,
        }),
      );

      this.logger.log(
        `[WALLET-RETRY] Wallet generation retry triggered for user: ${userId}, sessionId=${sessionId}`,
      );

      // 5. 更新 Redis 状态为 pending（等待重新生成）
      const statusData = {
        status: 'pending',
        userId,
        updatedAt: new Date().toISOString(),
      };

      await this.redisService.set(
        redisKey,
        JSON.stringify(statusData),
        60 * 60 * 24, // 24 小时
      );
    } catch (error) {
      this.logger.error(
        `[WALLET-RETRY] Failed to retry wallet generation for user ${userId}`,
        error,
      );
      throw new ApplicationError('钱包生成重试失败，请稍后再试');
    }
  }

  /**
   * 获取用户的恢复助记词
   *
   * 只从 Redis 获取（首次生成时临时存储的明文）
   * DDD: 助记词数据存储在 blockchain-service，identity-service 不直接访问
   */
  private async getRecoveryMnemonic(userId: bigint): Promise<string | null> {
    // 从 Redis 获取首次生成的助记词
    const redisKey = `keygen:status:${userId}`;
    const statusData = await this.redisService.get(redisKey);

    if (statusData) {
      try {
        const parsed = JSON.parse(statusData);
        if (parsed.mnemonic) {
          this.logger.log(
            `[MNEMONIC] Found mnemonic in Redis for user: ${userId}`,
          );
          return parsed.mnemonic;
        }
      } catch {
        // 解析失败
      }
    }

    // Redis 中没有助记词，可能已经备份或过期
    // DDD: 不再直接从 identity-service 数据库获取，助记词数据在 blockchain-service
    this.logger.log(
      `[MNEMONIC] No mnemonic in Redis for user: ${userId} (may be backed up or expired)`,
    );
    return null;
  }

  // ============ 助记词备份相关 ============

  /**
   * 标记助记词已备份 (PUT /user/mnemonic/backup)
   *
   * 用户确认已备份助记词后调用此接口：
   * 1. 调用 blockchain-service 更新 isBackedUp = true (DDD: domain separation)
   * 2. 清除 Redis 中的明文助记词
   */
  async markMnemonicBackedUp(
    command: MarkMnemonicBackedUpCommand,
  ): Promise<void> {
    const userId = BigInt(command.userId);
    this.logger.log(
      `[BACKUP] Marking mnemonic as backed up for user: ${userId}`,
    );

    // 1. 获取用户的 accountSequence
    const account = await this.userRepository.findById(
      UserId.create(command.userId),
    );
    if (!account) {
      this.logger.warn(`[BACKUP] User not found: ${userId}`);
      return;
    }

    // 2. 调用 blockchain-service 标记助记词已备份 (DDD: domain separation)
    try {
      await this.blockchainClient.markMnemonicBackedUp(
        account.accountSequence.value,
      );
      this.logger.log(
        `[BACKUP] Mnemonic marked as backed up in blockchain-service for account: ${account.accountSequence.value}`,
      );
    } catch (error) {
      this.logger.error(
        `[BACKUP] Failed to mark mnemonic as backed up in blockchain-service`,
        error,
      );
      // 不阻塞，继续清除 Redis
    }

    // 3. 清除 Redis 中的明文助记词（更新状态，移除 mnemonic 字段）
    const redisKey = `keygen:status:${userId}`;
    const statusData = await this.redisService.get(redisKey);

    if (statusData) {
      try {
        const parsed = JSON.parse(statusData);
        if (parsed.mnemonic) {
          // 移除明文助记词，保留其他状态信息
          delete parsed.mnemonic;
          parsed.isBackedUp = true;
          await this.redisService.set(
            redisKey,
            JSON.stringify(parsed),
            60 * 60 * 24,
          ); // 24小时
          this.logger.log(
            `[BACKUP] Cleared mnemonic from Redis for user: ${userId}`,
          );
        }
      } catch {
        // 解析失败，忽略
      }
    }
  }

  // ============ 助记词挂失相关 ============

  /**
   * 挂失助记词 (POST /user/mnemonic/revoke)
   *
   * 用户主动挂失助记词，防止泄露后被滥用
   * 挂失后该助记词将无法用于账户恢复
   */
  async revokeMnemonic(
    userId: string,
    reason: string,
  ): Promise<{ success: boolean; message: string }> {
    this.logger.log(
      `[REVOKE] Revoking mnemonic for user: ${userId}, reason: ${reason}`,
    );

    // 1. 获取用户的 accountSequence
    const account = await this.userRepository.findById(UserId.create(userId));
    if (!account) {
      this.logger.warn(`[REVOKE] User not found: ${userId}`);
      return { success: false, message: '用户不存在' };
    }

    // 2. 调用 blockchain-service 挂失助记词
    try {
      const result = await this.blockchainClient.revokeMnemonic(
        account.accountSequence.value,
        reason,
      );
      this.logger.log(
        `[REVOKE] Mnemonic revoke result: ${JSON.stringify(result)}`,
      );

      // 3. 发布助记词挂失审计事件
      if (result.success) {
        await this.eventPublisher.publish(
          new MnemonicRevokedEvent({
            userId,
            accountSequence: account.accountSequence.value,
            reason,
            revokedAt: new Date(),
          }),
        );
      }

      return result;
    } catch (error) {
      this.logger.error(`[REVOKE] Failed to revoke mnemonic`, error);
      return { success: false, message: '挂失失败，请稍后重试' };
    }
  }

  // ============ 账户冻结/解冻相关 ============

  /**
   * 冻结账户 (POST /user/freeze)
   *
   * 用户主动冻结自己的账户，防止被盗用
   * 冻结后账户将无法进行任何操作
   */
  async freezeAccount(
    userId: string,
    reason: string,
  ): Promise<{ success: boolean; message: string }> {
    this.logger.log(
      `[FREEZE] Freezing account for user: ${userId}, reason: ${reason}`,
    );

    const account = await this.userRepository.findById(UserId.create(userId));
    if (!account) {
      this.logger.warn(`[FREEZE] User not found: ${userId}`);
      return { success: false, message: '用户不存在' };
    }

    try {
      account.freeze(reason);
      await this.userRepository.save(account);

      // 发布领域事件 (包含 UserAccountFrozenEvent)
      await this.eventPublisher.publishAll(account.domainEvents);
      account.clearDomainEvents();

      this.logger.log(
        `[FREEZE] Account frozen successfully for user: ${userId}`,
      );
      return { success: true, message: '账户已冻结' };
    } catch (error) {
      this.logger.error(`[FREEZE] Failed to freeze account`, error);
      if (error instanceof Error && error.message === '账户已冻结') {
        return { success: false, message: '账户已处于冻结状态' };
      }
      return { success: false, message: '冻结失败，请稍后重试' };
    }
  }

  /**
   * 解冻账户 (POST /user/unfreeze)
   *
   * 需要验证身份后才能解冻
   * 支持助记词或手机号验证
   */
  async unfreezeAccount(params: {
    userId: string;
    verifyMethod: 'mnemonic' | 'phone';
    mnemonic?: string;
    phoneNumber?: string;
    smsCode?: string;
  }): Promise<{ success: boolean; message: string }> {
    const { userId, verifyMethod, mnemonic, phoneNumber, smsCode } = params;
    this.logger.log(
      `[UNFREEZE] Unfreezing account for user: ${userId}, method: ${verifyMethod}`,
    );

    const account = await this.userRepository.findById(UserId.create(userId));
    if (!account) {
      this.logger.warn(`[UNFREEZE] User not found: ${userId}`);
      return { success: false, message: '用户不存在' };
    }

    // 验证身份
    if (verifyMethod === 'mnemonic') {
      if (!mnemonic) {
        return { success: false, message: '请提供助记词' };
      }

      // 调用 blockchain-service 验证助记词
      const verifyResult = await this.blockchainClient.verifyMnemonicByAccount({
        accountSequence: account.accountSequence.value,
        mnemonic,
      });

      if (!verifyResult.valid) {
        this.logger.warn(
          `[UNFREEZE] Mnemonic verification failed for user: ${userId}`,
        );
        return {
          success: false,
          message: verifyResult.message || '助记词验证失败',
        };
      }
    } else if (verifyMethod === 'phone') {
      if (!phoneNumber || !smsCode) {
        return { success: false, message: '请提供手机号和验证码' };
      }

      if (!account.phoneNumber) {
        return {
          success: false,
          message: '账户未绑定手机号，请使用助记词验证',
        };
      }

      const phone = PhoneNumber.create(phoneNumber);
      if (!account.phoneNumber.equals(phone)) {
        return { success: false, message: '手机号与账户不匹配' };
      }

      const cachedCode = await this.redisService.get(
        `sms:unfreeze:${phone.value}`,
      );
      if (cachedCode !== smsCode) {
        return { success: false, message: '验证码错误或已过期' };
      }

      // 清除验证码
      await this.redisService.delete(`sms:unfreeze:${phone.value}`);
    } else {
      return { success: false, message: '不支持的验证方式' };
    }

    try {
      account.unfreeze();
      await this.userRepository.save(account);

      // 发布解冻事件
      await this.eventPublisher.publish(
        new AccountUnfrozenEvent({
          userId,
          accountSequence: account.accountSequence.value,
          verifyMethod,
          unfrozenAt: new Date(),
        }),
      );

      this.logger.log(
        `[UNFREEZE] Account unfrozen successfully for user: ${userId}`,
      );
      return { success: true, message: '账户已解冻' };
    } catch (error) {
      this.logger.error(`[UNFREEZE] Failed to unfreeze account`, error);
      if (error instanceof Error && error.message === '账户未冻结') {
        return { success: false, message: '账户未处于冻结状态' };
      }
      return { success: false, message: '解冻失败，请稍后重试' };
    }
  }

  // ============ 密钥轮换相关 ============

  /**
   * 请求密钥轮换 (POST /user/key-rotation/request)
   *
   * 用户主动请求轮换 MPC 密钥对
   * 1. 验证当前助记词
   * 2. 发布密钥轮换请求事件
   * 3. MPC 系统后台执行轮换
   */
  async requestKeyRotation(params: {
    userId: string;
    currentMnemonic: string;
    reason: string;
  }): Promise<{ success: boolean; message: string; sessionId?: string }> {
    const { userId, currentMnemonic, reason } = params;
    this.logger.log(
      `[KEY_ROTATION] Requesting key rotation for user: ${userId}, reason: ${reason}`,
    );

    const account = await this.userRepository.findById(UserId.create(userId));
    if (!account) {
      this.logger.warn(`[KEY_ROTATION] User not found: ${userId}`);
      return { success: false, message: '用户不存在' };
    }

    if (!account.isActive) {
      return { success: false, message: '账户已冻结或注销，无法进行密钥轮换' };
    }

    // 验证当前助记词
    const verifyResult = await this.blockchainClient.verifyMnemonicByAccount({
      accountSequence: account.accountSequence.value,
      mnemonic: currentMnemonic,
    });

    if (!verifyResult.valid) {
      this.logger.warn(
        `[KEY_ROTATION] Mnemonic verification failed for user: ${userId}`,
      );
      return {
        success: false,
        message: verifyResult.message || '助记词验证失败',
      };
    }

    // 生成轮换会话ID
    const sessionId = crypto.randomUUID();

    // 发布密钥轮换请求事件
    await this.eventPublisher.publish(
      new KeyRotationRequestedEvent({
        sessionId,
        userId,
        accountSequence: account.accountSequence.value,
        reason,
        requestedAt: new Date(),
      }),
    );

    this.logger.log(
      `[KEY_ROTATION] Key rotation requested for user: ${userId}, sessionId: ${sessionId}`,
    );
    return {
      success: true,
      message: '密钥轮换请求已提交，请等待处理完成',
      sessionId,
    };
  }

  // ============ 恢复码相关 ============

  /**
   * 生成恢复码 (POST /user/backup-codes/generate)
   *
   * 生成一组一次性恢复码，用于在丢失助记词时恢复账户
   * 每次生成会使之前的恢复码失效
   */
  async generateBackupCodes(params: {
    userId: string;
    mnemonic: string;
  }): Promise<{ success: boolean; message: string; codes?: string[] }> {
    const { userId, mnemonic } = params;
    this.logger.log(
      `[BACKUP_CODES] Generating backup codes for user: ${userId}`,
    );

    const account = await this.userRepository.findById(UserId.create(userId));
    if (!account) {
      return { success: false, message: '用户不存在' };
    }

    if (!account.isActive) {
      return { success: false, message: '账户已冻结或注销' };
    }

    // 验证助记词
    const verifyResult = await this.blockchainClient.verifyMnemonicByAccount({
      accountSequence: account.accountSequence.value,
      mnemonic,
    });

    if (!verifyResult.valid) {
      return {
        success: false,
        message: verifyResult.message || '助记词验证失败',
      };
    }

    // 生成 8 个恢复码
    const codes: string[] = [];
    const hashedCodes: string[] = [];
    for (let i = 0; i < 8; i++) {
      const code = this.generateBackupCode();
      codes.push(code);
      // 存储哈希值，不存储明文
      hashedCodes.push(await this.hashBackupCode(code));
    }

    // 存储到 Redis（带过期时间，1年）
    const redisKey = `backup_codes:${account.accountSequence.value}`;
    await this.redisService.set(
      redisKey,
      JSON.stringify({
        codes: hashedCodes,
        generatedAt: new Date().toISOString(),
        usedCount: 0,
      }),
      365 * 24 * 60 * 60,
    ); // 1年

    this.logger.log(
      `[BACKUP_CODES] Generated 8 backup codes for user: ${userId}`,
    );
    return {
      success: true,
      message: '恢复码已生成，请妥善保管',
      codes,
    };
  }

  /**
   * 使用恢复码恢复账户 (POST /user/recover-by-backup-code)
   */
  async recoverByBackupCode(params: {
    accountSequence: string;
    backupCode: string;
    newDeviceId: string;
    deviceName?: string;
  }): Promise<RecoverAccountResult> {
    const { accountSequence, backupCode, newDeviceId, deviceName } = params;
    this.logger.log(
      `[BACKUP_CODES] Recovering account ${accountSequence} with backup code`,
    );

    const account = await this.userRepository.findByAccountSequence(
      AccountSequence.create(accountSequence),
    );

    if (!account) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence,
          recoveryMethod: 'mnemonic', // 使用 mnemonic 作为类型，因为恢复码是备用方案
          failureReason: '账户序列号不存在',
          deviceId: newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('账户序列号不存在');
    }

    if (!account.isActive) {
      throw new ApplicationError('账户已冻结或注销');
    }

    // 验证恢复码
    const redisKey = `backup_codes:${accountSequence}`;
    const storedData = await this.redisService.get(redisKey);

    if (!storedData) {
      throw new ApplicationError('未设置恢复码或恢复码已过期');
    }

    const { codes: hashedCodes, usedCount } = JSON.parse(storedData);
    const hashedInput = await this.hashBackupCode(backupCode);

    const codeIndex = hashedCodes.findIndex((h: string) => h === hashedInput);
    if (codeIndex === -1) {
      await this.eventPublisher.publish(
        new AccountRecoveryFailedEvent({
          accountSequence,
          recoveryMethod: 'mnemonic',
          failureReason: '恢复码无效',
          deviceId: newDeviceId,
          attemptedAt: new Date(),
        }),
      );
      throw new ApplicationError('恢复码无效');
    }

    // 标记该恢复码已使用（设为 null）
    hashedCodes[codeIndex] = null;
    await this.redisService.set(
      redisKey,
      JSON.stringify({
        codes: hashedCodes,
        generatedAt: JSON.parse(storedData).generatedAt,
        usedCount: usedCount + 1,
      }),
      365 * 24 * 60 * 60,
    );

    // 恢复账户
    account.addDevice(newDeviceId, deviceName);
    account.recordLogin();
    await this.userRepository.save(account);

    const tokens = await this.tokenService.generateTokenPair({
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      deviceId: newDeviceId,
    });

    await this.eventPublisher.publishAll(account.domainEvents);
    account.clearDomainEvents();

    // 发布恢复成功事件
    await this.eventPublisher.publish(
      new AccountRecoveredEvent({
        userId: account.userId.toString(),
        accountSequence: account.accountSequence.value,
        recoveryMethod: 'mnemonic', // 使用 mnemonic 作为类型
        deviceId: newDeviceId,
        deviceName,
        recoveredAt: new Date(),
      }),
    );

    this.logger.log(
      `[BACKUP_CODES] Account ${accountSequence} recovered with backup code`,
    );
    return {
      userId: account.userId.toString(),
      accountSequence: account.accountSequence.value,
      nickname: account.nickname,
      avatarUrl: account.avatarUrl,
      referralCode: account.referralCode.value,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  /**
   * 生成单个恢复码 (格式: XXXX-XXXX-XXXX)
   */
  private generateBackupCode(): string {
    const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // 去掉容易混淆的字符
    const segments: string[] = [];
    for (let i = 0; i < 3; i++) {
      let segment = '';
      for (let j = 0; j < 4; j++) {
        segment += chars.charAt(Math.floor(Math.random() * chars.length));
      }
      segments.push(segment);
    }
    return segments.join('-');
  }

  /**
   * 哈希恢复码
   */
  private async hashBackupCode(code: string): Promise<string> {
    const crypto = await import('crypto');
    return crypto.createHash('sha256').update(code).digest('hex');
  }

  /**
   * 验证短信验证码 (不登录/注册)
   */
  async verifySmsCode(command: {
    phoneNumber: string;
    smsCode: string;
    type: string;
  }): Promise<void> {
    this.logger.log(
      `Verifying SMS code for phone: ${this.maskPhoneNumber(command.phoneNumber)}`,
    );

    const phoneNumber = PhoneNumber.create(command.phoneNumber);

    // 从 Redis 获取验证码进行验证
    const cacheKey = `sms:${command.type.toLowerCase()}:${phoneNumber.value}`;
    const cachedCode = await this.redisService.get(cacheKey);

    if (!cachedCode || cachedCode !== command.smsCode) {
      throw new ApplicationError('验证码错误或已过期');
    }

    // 验证成功后删除验证码
    await this.redisService.delete(cacheKey);

    this.logger.log(
      `SMS code verified successfully for phone: ${this.maskPhoneNumber(command.phoneNumber)}`,
    );
  }

  /**
   * 设置登录密码
   */
  async setPassword(command: {
    userId: string;
    password: string;
  }): Promise<void> {
    this.logger.log(`Setting password for user: ${command.userId}`);

    const userId = UserId.create(command.userId);
    const user = await this.userRepository.findById(userId);

    if (!user) {
      throw new ApplicationError('用户不存在');
    }

    // 使用 bcrypt 哈希密码
    const bcrypt = await import('bcrypt');
    const saltRounds = 10;
    const passwordHash = await bcrypt.hash(command.password, saltRounds);

    // 更新数据库
    await this.prisma.userAccount.update({
      where: { userId: BigInt(command.userId) },
      data: { passwordHash },
    });

    this.logger.log(`Password set successfully for user: ${command.userId}`);
  }

  /**
   * 修改登录密码
   */
  async changePassword(command: {
    userId: string;
    oldPassword: string;
    newPassword: string;
  }): Promise<void> {
    this.logger.log(`Changing password for user: ${command.userId}`);

    const userId = UserId.create(command.userId);
    const user = await this.userRepository.findById(userId);

    if (!user) {
      throw new ApplicationError('用户不存在');
    }

    // 获取当前密码哈希
    const account = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(command.userId) },
      select: { passwordHash: true },
    });

    if (!account?.passwordHash) {
      throw new ApplicationError('尚未设置密码');
    }

    // 验证旧密码
    const bcrypt = await import('bcrypt');
    const isOldPasswordValid = await bcrypt.compare(
      command.oldPassword,
      account.passwordHash,
    );

    if (!isOldPasswordValid) {
      this.logger.warn(
        `Invalid old password for user: ${command.userId}`,
      );
      throw new ApplicationError('当前密码错误');
    }

    // 使用 bcrypt 哈希新密码
    const saltRounds = 10;
    const newPasswordHash = await bcrypt.hash(command.newPassword, saltRounds);

    // 更新数据库
    await this.prisma.userAccount.update({
      where: { userId: BigInt(command.userId) },
      data: { passwordHash: newPasswordHash },
    });

    this.logger.log(`Password changed successfully for user: ${command.userId}`);
  }

  /**
   * 发送提取验证短信
   */
  async sendWithdrawSmsCode(userId: string): Promise<void> {
    this.logger.log(`Sending withdraw SMS code for user: ${userId}`);

    const user = await this.userRepository.findById(UserId.create(userId));
    if (!user) {
      throw new ApplicationError('用户不存在');
    }

    if (!user.phoneNumber) {
      throw new ApplicationError('请先绑定手机号');
    }

    const code = this.generateSmsCode();
    const cacheKey = `sms:withdraw:${user.phoneNumber.value}`;

    // 先存储到 Redis，再发送短信（避免竞态条件）
    await this.redisService.set(cacheKey, code, 300); // 5分钟有效
    await this.smsService.sendVerificationCode(user.phoneNumber.value, code);

    this.logger.log(
      `Withdraw SMS code sent successfully to: ${this.maskPhoneNumber(user.phoneNumber.value)}`,
    );
  }

  /**
   * 验证提取短信验证码
   */
  async verifyWithdrawSmsCode(
    userId: string,
    smsCode: string,
  ): Promise<boolean> {
    this.logger.log(`Verifying withdraw SMS code for user: ${userId}`);

    const user = await this.userRepository.findById(UserId.create(userId));
    if (!user) {
      throw new ApplicationError('用户不存在');
    }

    if (!user.phoneNumber) {
      throw new ApplicationError('用户未绑定手机号');
    }

    const cacheKey = `sms:withdraw:${user.phoneNumber.value}`;
    const cachedCode = await this.redisService.get(cacheKey);

    const isValid = cachedCode === smsCode;

    if (isValid) {
      // 验证成功后删除验证码，防止重复使用
      await this.redisService.delete(cacheKey);
    }

    this.logger.log(
      `Withdraw SMS verification result for user ${userId}: ${isValid}`,
    );
    return isValid;
  }

  /**
   * 通过 accountSequence 解析区块链地址
   */
  async resolveAccountSequenceToAddress(
    accountSequence: string,
    chainType: string,
  ): Promise<string | null> {
    this.logger.log(
      `Resolving accountSequence ${accountSequence} to ${chainType} address`,
    );

    // 查询用户
    const user = await this.prisma.userAccount.findFirst({
      where: { accountSequence },
      include: {
        walletAddresses: true,
      },
    });

    if (!user) {
      this.logger.warn(
        `User not found for accountSequence: ${accountSequence}`,
      );
      throw new ApplicationError('未找到该充值ID对应的用户');
    }

    // 查找对应链的地址
    const walletAddress = user.walletAddresses.find(
      (w) => w.chainType === chainType.toUpperCase(),
    );

    if (!walletAddress) {
      this.logger.warn(
        `No ${chainType} address found for accountSequence: ${accountSequence}`,
      );
      throw new ApplicationError(`未找到该用户的 ${chainType} 地址`);
    }

    this.logger.log(`Resolved ${accountSequence} to ${walletAddress.address}`);
    return walletAddress.address;
  }

  /**
   * 通过钱包地址查询用户信息
   * 用于内部转账时判断目标地址是否属于系统内用户
   */
  async findUserByWalletAddress(
    chainType: string,
    address: string,
  ): Promise<{ accountSequence: string; userId: bigint } | null> {
    this.logger.log(
      `Finding user by wallet address: ${chainType} ${address}`,
    );

    // 查询钱包地址
    const walletAddress = await this.prisma.walletAddress.findFirst({
      where: {
        chainType: chainType.toUpperCase(),
        address: address.toLowerCase(),
      },
      select: {
        userId: true,
      },
    });

    if (!walletAddress) {
      this.logger.debug(`No user found for wallet address: ${address}`);
      return null;
    }

    // 查询用户的 accountSequence
    const user = await this.prisma.userAccount.findUnique({
      where: { userId: walletAddress.userId },
      select: {
        accountSequence: true,
        userId: true,
      },
    });

    if (!user) {
      this.logger.warn(`User not found for userId: ${walletAddress.userId}`);
      return null;
    }

    this.logger.log(
      `Found user ${user.accountSequence} for wallet address: ${address}`,
    );
    return {
      accountSequence: user.accountSequence,
      userId: user.userId,
    };
  }

  /**
   * 通过 accountSequence 查询用户信息
   * 用于系统账户转出时获取接收方详细信息
   */
  async findUserByAccountSequence(
    accountSequence: string,
  ): Promise<{
    accountSequence: string;
    userId: bigint;
    realName: string | null;
    walletAddress: string | null;
  } | null> {
    this.logger.log(`Finding user by accountSequence: ${accountSequence}`);

    // 查询用户
    const user = await this.prisma.userAccount.findUnique({
      where: { accountSequence },
      select: {
        userId: true,
        accountSequence: true,
        realName: true,
      },
    });

    if (!user) {
      this.logger.debug(`No user found for accountSequence: ${accountSequence}`);
      return null;
    }

    // 查询钱包地址（默认 KAVA 链）
    const walletAddress = await this.prisma.walletAddress.findFirst({
      where: {
        userId: user.userId,
        chainType: 'KAVA',
      },
      select: {
        address: true,
      },
    });

    this.logger.log(
      `Found user ${user.accountSequence}: realName=${user.realName}, walletAddress=${walletAddress?.address}`,
    );

    return {
      accountSequence: user.accountSequence,
      userId: user.userId,
      realName: user.realName,
      walletAddress: walletAddress?.address || null,
    };
  }

  /**
   * 验证用户登录密码
   *
   * @param userId 用户ID
   * @param password 待验证的密码
   * @returns 密码是否正确
   */
  async verifyPassword(userId: string, password: string): Promise<boolean> {
    this.logger.log(`Verifying password for user: ${userId}`);

    // 查询用户
    const user = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: { passwordHash: true },
    });

    if (!user || !user.passwordHash) {
      this.logger.warn(`User ${userId} has no password set`);
      throw new ApplicationError('请先设置登录密码');
    }

    // 使用 bcrypt 验证密码
    const bcrypt = await import('bcrypt');
    const isValid = await bcrypt.compare(password, user.passwordHash);

    this.logger.log(
      `Password verification result for user ${userId}: ${isValid}`,
    );
    return isValid;
  }

  // ============ 支付密码相关 ============

  /**
   * 查询是否已设置支付密码
   */
  async isLoginPasswordSet(userId: string): Promise<boolean> {
    const user = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: { passwordHash: true },
    });
    return !!user?.passwordHash;
  }

  async isPaymentPasswordSet(userId: string): Promise<boolean> {
    const user = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: { paymentPasswordHash: true },
    });
    return !!user?.paymentPasswordHash;
  }

  /**
   * 设置支付密码（首次设置，无需旧密码）
   */
  async setPaymentPassword(command: {
    userId: string;
    password: string;
  }): Promise<void> {
    this.logger.log(`Setting payment password for user: ${command.userId}`);

    const user = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(command.userId) },
      select: { paymentPasswordHash: true },
    });

    if (user?.paymentPasswordHash) {
      throw new ApplicationError('支付密码已设置，请使用修改支付密码功能');
    }

    const bcrypt = await import('bcrypt');
    const paymentPasswordHash = await bcrypt.hash(command.password, 10);

    await this.prisma.userAccount.update({
      where: { userId: BigInt(command.userId) },
      data: { paymentPasswordHash },
    });

    this.logger.log(`Payment password set for user: ${command.userId}`);
  }

  /**
   * 修改支付密码（需验证旧密码）
   */
  async changePaymentPassword(command: {
    userId: string;
    oldPassword: string;
    newPassword: string;
  }): Promise<void> {
    this.logger.log(`Changing payment password for user: ${command.userId}`);

    const user = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(command.userId) },
      select: { paymentPasswordHash: true },
    });

    if (!user?.paymentPasswordHash) {
      throw new ApplicationError('尚未设置支付密码，请先设置');
    }

    const bcrypt = await import('bcrypt');
    const isOldValid = await bcrypt.compare(command.oldPassword, user.paymentPasswordHash);
    if (!isOldValid) {
      throw new ApplicationError('旧支付密码错误');
    }

    const paymentPasswordHash = await bcrypt.hash(command.newPassword, 10);
    await this.prisma.userAccount.update({
      where: { userId: BigInt(command.userId) },
      data: { paymentPasswordHash },
    });

    this.logger.log(`Payment password changed for user: ${command.userId}`);
  }

  /**
   * 验证支付密码
   *
   * @returns 密码是否正确
   */
  async verifyPaymentPassword(userId: string, password: string): Promise<boolean> {
    this.logger.log(`Verifying payment password for user: ${userId}`);

    const user = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: { paymentPasswordHash: true },
    });

    if (!user?.paymentPasswordHash) {
      throw new ApplicationError('请先设置支付密码');
    }

    const bcrypt = await import('bcrypt');
    const isValid = await bcrypt.compare(password, user.paymentPasswordHash);

    this.logger.log(`Payment password verification result for user ${userId}: ${isValid}`);
    return isValid;
  }

  // ============ 找回支付密码相关 ============

  /**
   * 发送重置支付密码短信验证码（已登录用户）
   *
   * 向当前登录用户绑定的手机号发送验证码，用于忘记支付密码时的身份核验。
   * Redis key: sms:reset_payment_password:${phone}，有效期 5 分钟。
   *
   * @param userId 当前登录用户 ID（来自 JWT）
   */
  async sendResetPaymentPasswordSmsCode(userId: string): Promise<void> {
    this.logger.log(`[RESET_PAYMENT_PASSWORD] Sending SMS code for user: ${userId}`);

    const user = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: { phoneNumber: true, status: true },
    });

    if (!user) throw new ApplicationError('用户不存在');
    if (user.status !== 'ACTIVE') throw new ApplicationError('账户已冻结或注销');
    if (!user.phoneNumber) throw new ApplicationError('账户未绑定手机号');

    const code = this.generateSmsCode();
    const cacheKey = `sms:reset_payment_password:${user.phoneNumber}`;

    // 先存储到 Redis，再发送短信（避免竞态条件）
    await this.redisService.set(cacheKey, code, 300); // 5分钟有效
    await this.smsService.sendVerificationCode(user.phoneNumber, code);

    this.logger.log(
      `[RESET_PAYMENT_PASSWORD] SMS code sent to: ${this.maskPhoneNumber(user.phoneNumber)}`,
    );
  }

  /**
   * 重置支付密码（已登录用户，通过短信验证码验证身份）
   *
   * 流程：验证码校验 → 格式校验 → bcrypt 哈希 → 更新数据库 → 删除已用验证码
   *
   * @param userId 当前登录用户 ID（来自 JWT）
   * @param smsCode 短信验证码
   * @param newPassword 新支付密码（必须为 6 位纯数字）
   */
  async resetPaymentPassword(
    userId: string,
    smsCode: string,
    newPassword: string,
  ): Promise<void> {
    this.logger.log(`[RESET_PAYMENT_PASSWORD] Resetting payment password for user: ${userId}`);

    const user = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: { phoneNumber: true, status: true },
    });

    if (!user) throw new ApplicationError('用户不存在');
    if (user.status !== 'ACTIVE') throw new ApplicationError('账户已冻结或注销');
    if (!user.phoneNumber) throw new ApplicationError('账户未绑定手机号');

    // 1. 验证短信验证码
    const cacheKey = `sms:reset_payment_password:${user.phoneNumber}`;
    const cachedCode = await this.redisService.get(cacheKey);

    if (!cachedCode || cachedCode !== smsCode) {
      this.logger.warn(
        `[RESET_PAYMENT_PASSWORD] Invalid SMS code for user: ${userId}`,
      );
      throw new ApplicationError('验证码错误或已过期');
    }

    // 2. 验证新密码格式（6位纯数字）
    if (!/^\d{6}$/.test(newPassword)) {
      throw new ApplicationError('支付密码必须为6位数字');
    }

    // 3. bcrypt 哈希新密码并更新数据库
    const bcrypt = await import('bcrypt');
    const hash = await bcrypt.hash(newPassword, 10);

    await this.prisma.userAccount.update({
      where: { userId: BigInt(userId) },
      data: { paymentPasswordHash: hash },
    });

    // 4. 删除已使用的验证码，防止重复使用
    await this.redisService.delete(cacheKey);

    this.logger.log(`[RESET_PAYMENT_PASSWORD] Payment password reset successfully for user: ${userId}`);
  }

  /**
   * 遮蔽手机号
   */
  private maskPhoneNumber(phone: string): string {
    if (phone.length < 7) return phone;
    return phone.substring(0, 3) + '****' + phone.substring(phone.length - 4);
  }

  // ============ 找回密码相关 ============

  /**
   * 发送重置密码短信验证码
   *
   * @param phoneNumber 手机号
   */
  async sendResetPasswordSmsCode(phoneNumber: string): Promise<void> {
    this.logger.log(
      `[RESET_PASSWORD] Sending SMS code to: ${this.maskPhoneNumber(phoneNumber)}`,
    );

    const phone = PhoneNumber.create(phoneNumber);

    // 检查手机号是否已注册
    const account = await this.userRepository.findByPhoneNumber(phone);
    if (!account) {
      this.logger.warn(
        `[RESET_PASSWORD] Phone not registered: ${this.maskPhoneNumber(phoneNumber)}`,
      );
      throw new ApplicationError('该手机号未注册');
    }

    // 检查账户状态
    if (!account.isActive) {
      this.logger.warn(
        `[RESET_PASSWORD] Account inactive for phone: ${this.maskPhoneNumber(phoneNumber)}`,
      );
      throw new ApplicationError('账户已冻结或注销');
    }

    // 生成并发送验证码
    const code = this.generateSmsCode();
    const cacheKey = `sms:reset_password:${phone.value}`;

    // 先存储到 Redis，再发送短信（避免竞态条件）
    await this.redisService.set(cacheKey, code, 300); // 5分钟有效
    await this.smsService.sendVerificationCode(phone.value, code);

    this.logger.log(
      `[RESET_PASSWORD] SMS code sent successfully to: ${this.maskPhoneNumber(phoneNumber)}`,
    );
  }

  /**
   * 重置密码
   *
   * @param phoneNumber 手机号
   * @param smsCode 短信验证码
   * @param newPassword 新密码
   */
  async resetPassword(
    phoneNumber: string,
    smsCode: string,
    newPassword: string,
  ): Promise<void> {
    this.logger.log(
      `[RESET_PASSWORD] Resetting password for: ${this.maskPhoneNumber(phoneNumber)}`,
    );

    const phone = PhoneNumber.create(phoneNumber);

    // 1. 验证短信验证码
    const cacheKey = `sms:reset_password:${phone.value}`;
    const cachedCode = await this.redisService.get(cacheKey);

    if (!cachedCode || cachedCode !== smsCode) {
      this.logger.warn(
        `[RESET_PASSWORD] Invalid SMS code for: ${this.maskPhoneNumber(phoneNumber)}`,
      );
      throw new ApplicationError('验证码错误或已过期');
    }

    // 2. 查找用户
    const account = await this.userRepository.findByPhoneNumber(phone);
    if (!account) {
      this.logger.warn(
        `[RESET_PASSWORD] User not found for phone: ${this.maskPhoneNumber(phoneNumber)}`,
      );
      throw new ApplicationError('用户不存在');
    }

    // 3. 检查账户状态
    if (!account.isActive) {
      this.logger.warn(
        `[RESET_PASSWORD] Account inactive for phone: ${this.maskPhoneNumber(phoneNumber)}`,
      );
      throw new ApplicationError('账户已冻结或注销');
    }

    // 4. 验证密码长度
    if (newPassword.length < 6) {
      throw new ApplicationError('密码至少需要6位');
    }

    // 5. 更新密码
    const bcrypt = await import('bcrypt');
    const saltRounds = 10;
    const passwordHash = await bcrypt.hash(newPassword, saltRounds);

    await this.prisma.userAccount.update({
      where: { userId: account.userId.value },
      data: { passwordHash },
    });

    // 6. 删除验证码
    await this.redisService.delete(cacheKey);

    this.logger.log(
      `[RESET_PASSWORD] Password reset successfully for: ${this.maskPhoneNumber(phoneNumber)}`,
    );
  }

  // ============ 邮箱绑定相关 ============

  /**
   * 生成6位数字验证码
   */
  private generateEmailCode(): string {
    return Math.floor(100000 + Math.random() * 900000).toString();
  }

  /**
   * 脱敏邮箱地址
   */
  private maskEmail(email: string): string {
    const [local, domain] = email.split('@');
    if (!domain || local.length < 3) {
      return email;
    }
    const maskedLocal =
      local.substring(0, 2) + '***' + local.substring(local.length - 1);
    return `${maskedLocal}@${domain}`;
  }

  /**
   * 发送邮箱验证码
   */
  async sendEmailCode(command: {
    userId: string;
    email: string;
    purpose: 'BIND_EMAIL' | 'UNBIND_EMAIL';
  }): Promise<void> {
    this.logger.log(
      `Sending email code for user: ${command.userId}, purpose: ${command.purpose}`,
    );

    const user = await this.userRepository.findById(UserId.create(command.userId));
    if (!user) {
      throw new ApplicationError('用户不存在');
    }

    // 检查邮箱是否已被其他用户绑定
    if (command.purpose === 'BIND_EMAIL') {
      const existingUser = await this.prisma.userAccount.findFirst({
        where: {
          email: command.email.toLowerCase(),
          NOT: { userId: BigInt(command.userId) },
        },
      });
      if (existingUser) {
        throw new ApplicationError('该邮箱已被其他账户绑定');
      }
    }

    // 解绑时验证用户确实已绑定邮箱
    if (command.purpose === 'UNBIND_EMAIL') {
      const account = await this.prisma.userAccount.findUnique({
        where: { userId: BigInt(command.userId) },
        select: { email: true },
      });
      if (!account?.email) {
        throw new ApplicationError('您还未绑定邮箱');
      }
      // 解绑验证码发送到当前绑定的邮箱
      if (command.email.toLowerCase() !== account.email.toLowerCase()) {
        throw new ApplicationError('请使用当前绑定的邮箱接收验证码');
      }
    }

    const code = this.generateEmailCode();
    const cacheKey = `email:${command.purpose.toLowerCase()}:${command.email.toLowerCase()}`;

    // 先缓存验证码，再发送邮件（避免竞态条件）
    await this.redisService.set(cacheKey, code, 300); // 5分钟有效

    // 发送验证码邮件
    const result = await this.emailService.sendVerificationCode(
      command.email,
      code,
    );

    if (!result.success) {
      // 发送失败时删除缓存的验证码
      await this.redisService.delete(cacheKey);
      throw new ApplicationError(`发送验证码失败: ${result.message}`);
    }

    this.logger.log(
      `Email code sent successfully to: ${this.maskEmail(command.email)}`,
    );
  }

  /**
   * 绑定邮箱
   */
  async bindEmail(command: {
    userId: string;
    email: string;
    code: string;
  }): Promise<void> {
    this.logger.log(`Binding email for user: ${command.userId}`);

    const user = await this.userRepository.findById(UserId.create(command.userId));
    if (!user) {
      throw new ApplicationError('用户不存在');
    }

    const emailLower = command.email.toLowerCase();
    const cacheKey = `email:bind_email:${emailLower}`;
    const cachedCode = await this.redisService.get(cacheKey);

    if (!cachedCode) {
      throw new ApplicationError('验证码已过期，请重新获取');
    }

    if (cachedCode !== command.code) {
      throw new ApplicationError('验证码错误');
    }

    // 再次检查邮箱是否已被绑定
    const existingUser = await this.prisma.userAccount.findFirst({
      where: {
        email: emailLower,
        NOT: { userId: BigInt(command.userId) },
      },
    });
    if (existingUser) {
      throw new ApplicationError('该邮箱已被其他账户绑定');
    }

    // 更新用户邮箱并标记为已验证
    await this.prisma.userAccount.update({
      where: { userId: BigInt(command.userId) },
      data: {
        email: emailLower,
        emailVerified: true,
        emailVerifiedAt: new Date(),
      },
    });

    // 删除验证码
    await this.redisService.delete(cacheKey);

    this.logger.log(`Email bound successfully for user: ${command.userId}`);
  }

  /**
   * 解绑邮箱
   */
  async unbindEmail(command: { userId: string; code: string }): Promise<void> {
    this.logger.log(`Unbinding email for user: ${command.userId}`);

    const account = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(command.userId) },
      select: { email: true },
    });

    if (!account?.email) {
      throw new ApplicationError('您还未绑定邮箱');
    }

    const cacheKey = `email:unbind_email:${account.email.toLowerCase()}`;
    const cachedCode = await this.redisService.get(cacheKey);

    if (!cachedCode) {
      throw new ApplicationError('验证码已过期，请重新获取');
    }

    if (cachedCode !== command.code) {
      throw new ApplicationError('验证码错误');
    }

    // 解绑邮箱并清除验证状态
    await this.prisma.userAccount.update({
      where: { userId: BigInt(command.userId) },
      data: {
        email: null,
        emailVerified: false,
        emailVerifiedAt: null,
      },
    });

    // 删除验证码
    await this.redisService.delete(cacheKey);

    this.logger.log(`Email unbound successfully for user: ${command.userId}`);
  }

  /**
   * 获取邮箱绑定状态
   */
  async getEmailStatus(userId: string): Promise<{
    isBound: boolean;
    isVerified: boolean;
    email: string | null;
    verifiedAt: Date | null;
  }> {
    const account = await this.prisma.userAccount.findUnique({
      where: { userId: BigInt(userId) },
      select: {
        email: true,
        emailVerified: true,
        emailVerifiedAt: true,
      },
    });

    if (!account) {
      throw new ApplicationError('用户不存在');
    }

    // 脱敏处理
    let maskedEmail: string | null = null;
    if (account.email) {
      maskedEmail = this.maskEmail(account.email);
    }

    return {
      isBound: !!account.email,
      isVerified: account.emailVerified,
      email: maskedEmail,
      verifiedAt: account.emailVerifiedAt,
    };
  }
}
