import {
  Injectable,
  Logger,
  UnauthorizedException,
  ConflictException,
  ForbiddenException,
  BadRequestException,
  Inject,
} from '@nestjs/common';
import { USER_REPOSITORY, IUserRepository } from '../../domain/repositories/user.repository.interface';
import { REFRESH_TOKEN_REPOSITORY, IRefreshTokenRepository } from '../../domain/repositories/refresh-token.repository.interface';
import { TokenService } from './token.service';
import { SmsService } from './sms.service';
import { EmailService } from './email.service';
import { Password } from '../../domain/value-objects/password.vo';
import { Phone } from '../../domain/value-objects/phone.vo';
import { Email } from '../../domain/value-objects/email.vo';
import { UserRole, UserStatus } from '../../domain/entities/user.entity';
import { SmsVerificationType } from '../../domain/entities/sms-verification.entity';
import { EmailVerificationType } from '../../domain/entities/email-verification.entity';
import { EventPublisherService } from './event-publisher.service';

export interface RegisterDto {
  phone: string;
  smsCode: string;
  password?: string;
  nickname?: string;
  referralCode?: string; // 推荐人的推荐码（可选）
}

export interface LoginDto {
  identifier: string; // phone or email
  password: string;
  deviceInfo?: string;
  ipAddress?: string;
}

export interface AuthTokens {
  accessToken: string;
  refreshToken: string;
  expiresIn: number;
}

export interface AuthResult {
  user: {
    id: string;
    phone: string | null;
    email: string | null;
    nickname: string | null;
    avatarUrl: string | null;
    role: string;
    kycLevel: number;
    walletMode: string;
  };
  tokens: AuthTokens;
}

@Injectable()
export class AuthService {
  private readonly logger = new Logger('AuthService');

  constructor(
    @Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
    @Inject(REFRESH_TOKEN_REPOSITORY) private readonly refreshTokenRepo: IRefreshTokenRepository,
    private readonly tokenService: TokenService,
    private readonly smsService: SmsService,
    private readonly emailService: EmailService,
    private readonly eventPublisher: EventPublisherService,
  ) {}

  /* ── Register ── */

  async register(dto: RegisterDto): Promise<AuthResult> {
    const phone = Phone.create(dto.phone);

    // Check duplicate
    const existing = await this.userRepo.findByPhone(phone.value);
    if (existing) {
      throw new ConflictException('该手机号已注册');
    }

    // Verify SMS code
    await this.smsService.verifyCode(dto.phone, dto.smsCode, SmsVerificationType.REGISTER);

    // Hash password (if not provided, generate a random one for SMS-only registration)
    const rawPassword = dto.password || require('crypto').randomBytes(16).toString('hex');
    const password = await Password.create(rawPassword);

    // Create user
    const user = await this.userRepo.create({
      phone: phone.value,
      email: null,
      passwordHash: password.value,
      nickname: dto.nickname || null,
      role: UserRole.USER,
      status: UserStatus.ACTIVE,
      kycLevel: 0,
      walletMode: 'standard',
    });

    // Generate tokens
    const tokens = await this.tokenService.generateTokenPair(user.id, user.role, user.kycLevel);
    await this.tokenService.storeRefreshToken(user.id, tokens.refreshToken);

    // Publish event
    await this.eventPublisher.publishUserRegistered({
      userId: user.id,
      phone: user.phone,
      email: user.email,
      role: user.role,
      referralCode: dto.referralCode?.toUpperCase() ?? null,
      timestamp: new Date().toISOString(),
    });

    this.logger.log(`User registered: ${user.id} phone=${phone.masked}`);
    return { user: this.toUserDto(user), tokens };
  }

  /* ── Password Login ── */

  async login(dto: LoginDto): Promise<AuthResult> {
    // Normalize phone numbers to E.164 before lookup (e.g. 18926762721 → +8618926762721)
    let identifier = dto.identifier;
    try {
      identifier = Phone.create(dto.identifier).value;
    } catch {
      // Not a valid phone number — treat as email, use as-is
    }
    const user = await this.userRepo.findByPhoneOrEmail(identifier);
    if (!user) {
      throw new UnauthorizedException('账号或密码错误');
    }

    this.checkUserStatus(user);
    this.checkAccountLock(user);

    // Verify password
    const password = Password.fromHash(user.passwordHash);
    const valid = await password.verify(dto.password);
    if (!valid) {
      const lockInfo = user.recordLoginFailure();
      await this.userRepo.save(user);

      if (lockInfo.lockMinutes) {
        await this.eventPublisher.publishAccountLocked({
          userId: user.id,
          lockedUntil: user.lockedUntil!.toISOString(),
          failCount: user.loginFailCount,
          timestamp: new Date().toISOString(),
        });
        throw new ForbiddenException(
          `登录失败次数过多，账号已锁定 ${lockInfo.lockMinutes} 分钟`,
        );
      }
      throw new UnauthorizedException(
        `账号或密码错误，还剩 ${lockInfo.remainingAttempts} 次尝试机会`,
      );
    }

    // Success
    user.recordLoginSuccess(dto.ipAddress);
    await this.userRepo.save(user);

    const tokens = await this.tokenService.generateTokenPair(user.id, user.role, user.kycLevel);
    await this.tokenService.storeRefreshToken(user.id, tokens.refreshToken, dto.deviceInfo, dto.ipAddress);

    await this.eventPublisher.publishUserLoggedIn({
      userId: user.id,
      ipAddress: dto.ipAddress || null,
      deviceInfo: dto.deviceInfo || null,
      timestamp: new Date().toISOString(),
    });

    return { user: this.toUserDto(user), tokens };
  }

  /* ── SMS Login ── */

  async loginWithPhone(
    rawPhone: string,
    smsCode: string,
    deviceInfo?: string,
    ipAddress?: string,
  ): Promise<AuthResult> {
    const phone = Phone.create(rawPhone);

    // Verify SMS code
    await this.smsService.verifyCode(rawPhone, smsCode, SmsVerificationType.LOGIN);

    const user = await this.userRepo.findByPhone(phone.value);
    if (!user) {
      throw new UnauthorizedException('该手机号未注册');
    }

    this.checkUserStatus(user);

    // SMS login bypasses lockout (code-verified)
    user.recordLoginSuccess(ipAddress);
    await this.userRepo.save(user);

    const tokens = await this.tokenService.generateTokenPair(user.id, user.role, user.kycLevel);
    await this.tokenService.storeRefreshToken(user.id, tokens.refreshToken, deviceInfo, ipAddress);

    await this.eventPublisher.publishUserLoggedIn({
      userId: user.id,
      ipAddress: ipAddress || null,
      deviceInfo: deviceInfo || null,
      timestamp: new Date().toISOString(),
    });

    return { user: this.toUserDto(user), tokens };
  }

  /* ── Reset Password ── */

  async resetPassword(
    rawPhone: string,
    smsCode: string,
    newPassword: string,
  ): Promise<void> {
    const phone = Phone.create(rawPhone);

    // Verify SMS code
    await this.smsService.verifyCode(rawPhone, smsCode, SmsVerificationType.RESET_PASSWORD);

    const user = await this.userRepo.findByPhone(phone.value);
    if (!user) {
      throw new BadRequestException('该手机号未注册');
    }

    // Set new password
    const passwordVo = await Password.create(newPassword);
    user.passwordHash = passwordVo.value;

    // Clear lockout
    user.loginFailCount = 0;
    user.lockedUntil = null;
    await this.userRepo.save(user);

    // Revoke all refresh tokens (force re-login)
    await this.refreshTokenRepo.revokeByUserId(user.id);

    await this.eventPublisher.publishPasswordReset({
      userId: user.id,
      phone: phone.value,
      timestamp: new Date().toISOString(),
    });

    this.logger.log(`Password reset: userId=${user.id} phone=${phone.masked}`);
  }

  /* ── Change Phone ── */

  async changePhone(
    userId: string,
    newRawPhone: string,
    newSmsCode: string,
  ): Promise<void> {
    const newPhone = Phone.create(newRawPhone);

    // Verify new phone SMS code
    await this.smsService.verifyCode(newRawPhone, newSmsCode, SmsVerificationType.CHANGE_PHONE);

    const user = await this.userRepo.findById(userId);
    if (!user) {
      throw new UnauthorizedException('用户不存在');
    }

    // Check new phone not already used
    const existing = await this.userRepo.findByPhone(newPhone.value);
    if (existing) {
      throw new ConflictException('该手机号已被其他账户使用');
    }

    const oldPhone = user.phone;
    user.phone = newPhone.value;
    await this.userRepo.save(user);

    await this.eventPublisher.publishPhoneChanged({
      userId: user.id,
      oldPhone: oldPhone || '',
      newPhone: newPhone.value,
      timestamp: new Date().toISOString(),
    });

    this.logger.log(`Phone changed: userId=${user.id} new=${newPhone.masked}`);
  }

  /* ── Token Refresh ── */

  async refreshToken(refreshToken: string): Promise<AuthTokens> {
    const payload = await this.tokenService.verifyRefreshToken(refreshToken);

    const user = await this.userRepo.findById(payload.sub);
    if (!user || user.status !== UserStatus.ACTIVE) {
      throw new UnauthorizedException('用户不存在或已停用');
    }

    await this.tokenService.revokeRefreshToken(refreshToken);

    const tokens = await this.tokenService.generateTokenPair(user.id, user.role, user.kycLevel);
    await this.tokenService.storeRefreshToken(user.id, tokens.refreshToken);
    return tokens;
  }

  /* ── Logout ── */

  async logout(userId: string): Promise<void> {
    await this.refreshTokenRepo.revokeByUserId(userId);

    await this.eventPublisher.publishUserLoggedOut({
      userId,
      timestamp: new Date().toISOString(),
    });

    this.logger.log(`User logged out: ${userId}`);
  }

  /* ── Change Password ── */

  async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void> {
    const user = await this.userRepo.findById(userId);
    if (!user) throw new UnauthorizedException('用户不存在');

    const currentPassword = Password.fromHash(user.passwordHash);
    const valid = await currentPassword.verify(oldPassword);
    if (!valid) throw new UnauthorizedException('当前密码错误');

    const newHash = await Password.create(newPassword);
    user.passwordHash = newHash.value;
    await this.userRepo.save(user);

    await this.refreshTokenRepo.revokeByUserId(userId);

    await this.eventPublisher.publishPasswordChanged({
      userId,
      timestamp: new Date().toISOString(),
    });
  }

  /* ── Email Registration ── */

  async registerByEmail(dto: {
    email: string;
    emailCode: string;
    password: string;
    nickname?: string;
    referralCode?: string;
  }): Promise<AuthResult> {
    const email = Email.create(dto.email);

    const existing = await this.userRepo.findByEmail(email.value);
    if (existing) {
      throw new ConflictException('该邮箱已注册');
    }

    await this.emailService.verifyCode(dto.email, dto.emailCode, EmailVerificationType.REGISTER);

    const password = await Password.create(dto.password);
    const user = await this.userRepo.create({
      phone: null,
      email: email.value,
      passwordHash: password.value,
      nickname: dto.nickname || null,
      role: UserRole.USER,
      status: UserStatus.ACTIVE,
      kycLevel: 0,
      walletMode: 'standard',
    });

    const tokens = await this.tokenService.generateTokenPair(user.id, user.role, user.kycLevel);
    await this.tokenService.storeRefreshToken(user.id, tokens.refreshToken);

    await this.eventPublisher.publishUserRegistered({
      userId: user.id,
      phone: user.phone,
      email: user.email,
      role: user.role,
      referralCode: dto.referralCode?.toUpperCase() ?? null,
      timestamp: new Date().toISOString(),
    });

    this.logger.log(`User registered by email: ${user.id} email=${email.masked}`);
    return { user: this.toUserDto(user), tokens };
  }

  /* ── Email Code Login ── */

  async loginWithEmail(
    rawEmail: string,
    emailCode: string,
    deviceInfo?: string,
    ipAddress?: string,
  ): Promise<AuthResult> {
    const email = Email.create(rawEmail);

    await this.emailService.verifyCode(rawEmail, emailCode, EmailVerificationType.LOGIN);

    const user = await this.userRepo.findByEmail(email.value);
    if (!user) {
      throw new UnauthorizedException('该邮箱未注册');
    }

    this.checkUserStatus(user);
    user.recordLoginSuccess(ipAddress);
    await this.userRepo.save(user);

    const tokens = await this.tokenService.generateTokenPair(user.id, user.role, user.kycLevel);
    await this.tokenService.storeRefreshToken(user.id, tokens.refreshToken, deviceInfo, ipAddress);

    await this.eventPublisher.publishUserLoggedIn({
      userId: user.id,
      ipAddress: ipAddress || null,
      deviceInfo: deviceInfo || null,
      timestamp: new Date().toISOString(),
    });

    return { user: this.toUserDto(user), tokens };
  }

  /* ── Reset Password via Email ── */

  async resetPasswordByEmail(
    rawEmail: string,
    emailCode: string,
    newPassword: string,
  ): Promise<void> {
    const email = Email.create(rawEmail);

    await this.emailService.verifyCode(rawEmail, emailCode, EmailVerificationType.RESET_PASSWORD);

    const user = await this.userRepo.findByEmail(email.value);
    if (!user) {
      throw new BadRequestException('该邮箱未注册');
    }

    const passwordVo = await Password.create(newPassword);
    user.passwordHash = passwordVo.value;
    user.loginFailCount = 0;
    user.lockedUntil = null;
    await this.userRepo.save(user);

    await this.refreshTokenRepo.revokeByUserId(user.id);

    await this.eventPublisher.publishPasswordReset({
      userId: user.id,
      phone: user.phone || '',
      timestamp: new Date().toISOString(),
    });

    this.logger.log(`Password reset by email: userId=${user.id} email=${email.masked}`);
  }

  /* ── Send SMS Code (delegates to SmsService) ── */

  async sendSmsCode(rawPhone: string, type: SmsVerificationType): Promise<{ expiresIn: number }> {
    return this.smsService.sendCode(rawPhone, type);
  }

  /* ── Send Email Code (delegates to EmailService) ── */

  async sendEmailCode(rawEmail: string, type: EmailVerificationType): Promise<{ expiresIn: number }> {
    return this.emailService.sendCode(rawEmail, type);
  }

  /* ── Private Helpers ── */

  private checkUserStatus(user: any): void {
    if (user.status === UserStatus.FROZEN) {
      throw new ForbiddenException('账号已被冻结');
    }
    if (user.status === UserStatus.DELETED) {
      throw new UnauthorizedException('账号不存在');
    }
  }

  private checkAccountLock(user: any): void {
    if (user.isLocked) {
      const mins = Math.ceil(user.lockRemainingSeconds / 60);
      throw new ForbiddenException(
        `账号已锁定，请 ${mins} 分钟后再试`,
      );
    }
  }

  private toUserDto(user: any) {
    return {
      id: user.id,
      phone: user.phone,
      email: user.email,
      nickname: user.nickname,
      avatarUrl: user.avatarUrl,
      role: user.role,
      kycLevel: user.kycLevel,
      walletMode: user.walletMode,
    };
  }
}
