/**
 * 管理员核心服务
 * 
 * 功能描述：
 * - 管理员登录校验（仅允许 role=9）
 * - 生成/验证管理员签名Token（HMAC-SHA256）
 * - 启动时可选引导创建管理员账号（通过环境变量启用）
 * 
 * 职责分离：
 * - 管理员身份认证和授权
 * - Token签名生成和验证
 * - 管理员账户引导创建
 * - 密码安全处理和验证
 * 
 * 最近修改：
 * - 2026-01-07: 代码规范优化 - 修复常量命名(saltRounds -> SALT_ROUNDS)
 * - 2026-01-07: 代码规范优化 - 完善文件头注释和方法注释规范
 * 
 * @author jianuo
 * @version 1.0.1
 * @since 2025-12-19
 * @lastModified 2026-01-07
 */

import { BadRequestException, Inject, Injectable, Logger, OnModuleInit, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
import { Users } from '../db/users/users.entity';
import { UsersService } from '../db/users/users.service';
import { UsersMemoryService } from '../db/users/users_memory.service';

export interface AdminLoginRequest {
  identifier: string;
  password: string;
}

export interface AdminAuthPayload {
  adminId: string;
  username: string;
  role: number;
  iat: number;
  exp: number;
}

export interface AdminLoginResult {
  admin: {
    id: string;
    username: string;
    nickname: string;
    role: number;
  };
  access_token: string;
  expires_at: number;
}

/**
 * 管理员核心服务
 * 
 * 职责：
 * - 管理员登录认证和Token生成
 * - Token签名验证和有效期检查
 * - 管理员密码重置功能
 * - 启动时管理员账户引导创建
 * 
 * 主要方法：
 * - login() - 管理员登录认证
 * - verifyToken() - Token验证和解析
 * - resetUserPassword() - 管理员重置用户密码
 * - onModuleInit() - 模块初始化时的引导创建
 * 
 * 使用场景：
 * - 后台管理系统的管理员认证
 * - 管理员权限验证和授权
 * - 系统启动时的管理员账户初始化
 */
@Injectable()
export class AdminCoreService implements OnModuleInit {
  private readonly logger = new Logger(AdminCoreService.name);

  constructor(
    private readonly configService: ConfigService,
    @Inject('UsersService') private readonly usersService: UsersService | UsersMemoryService,
  ) {}

  /**
   * 模块初始化时执行管理员引导创建
   * 
   * 业务逻辑：
   * 1. 检查是否启用管理员引导功能
   * 2. 如果启用则调用引导创建方法
   * 3. 处理引导创建过程中的异常情况
   * 
   * @returns Promise<void> 无返回值
   * 
   * @example
   * ```typescript
   * // 在模块初始化时自动调用
   * await adminCoreService.onModuleInit();
   * ```
   */
  async onModuleInit(): Promise<void> {
    await this.bootstrapAdminIfEnabled();
  }

  /**
   * 管理员登录认证
   * 
   * 业务逻辑：
   * 1. 根据标识符查找用户（用户名/邮箱/手机号）
   * 2. 验证用户存在性和管理员权限（role=9）
   * 3. 检查用户是否设置了密码
   * 4. 验证密码正确性
   * 5. 生成带有效期的签名Token
   * 6. 返回管理员信息和访问令牌
   * 
   * @param request 登录请求数据，包含标识符和密码
   * @returns 认证结果，包含管理员信息和访问令牌
   * @throws UnauthorizedException 管理员账号不存在时
   * @throws UnauthorizedException 无管理员权限时
   * @throws UnauthorizedException 管理员账户未设置密码时
   * @throws UnauthorizedException 密码错误时
   * 
   * @example
   * ```typescript
   * const result = await adminCoreService.login({
   *   identifier: 'admin@example.com',
   *   password: 'Admin123456'
   * });
   * console.log(result.admin.username); // 'admin'
   * console.log(result.access_token); // 'eyJ...'
   * ```
   */
  async login(request: AdminLoginRequest): Promise<AdminLoginResult> {
    const { identifier, password } = request;

    const adminUser = await this.findUserByIdentifier(identifier);
    if (!adminUser) {
      throw new UnauthorizedException('管理员账号不存在');
    }

    if (adminUser.role !== 9) {
      throw new UnauthorizedException('无管理员权限');
    }

    if (!adminUser.password_hash) {
      throw new UnauthorizedException('管理员账户未设置密码，无法登录');
    }

    const ok = await bcrypt.compare(password, adminUser.password_hash);
    if (!ok) {
      throw new UnauthorizedException('密码错误');
    }

    const ttlSeconds = this.getAdminTokenTtlSeconds();
    const now = Date.now();
    const payload: AdminAuthPayload = {
      adminId: adminUser.id.toString(),
      username: adminUser.username,
      role: adminUser.role,
      iat: now,
      exp: now + ttlSeconds * 1000,
    };

    const token = this.signPayload(payload);

    return {
      admin: {
        id: adminUser.id.toString(),
        username: adminUser.username,
        nickname: adminUser.nickname,
        role: adminUser.role,
      },
      access_token: token,
      expires_at: payload.exp,
    };
  }

  /**
   * 校验管理员Token并返回Payload
   * 
   * 业务逻辑：
   * 1. 获取Token签名密钥
   * 2. 分离Token的载荷部分和签名部分
   * 3. 验证Token格式的有效性
   * 4. 使用HMAC-SHA256验证签名
   * 5. 解析载荷JSON数据
   * 6. 验证管理员权限和Token有效期
   * 7. 返回解析后的载荷信息
   * 
   * @param token 待验证的Token字符串
   * @returns 解析后的管理员认证载荷
   * @throws UnauthorizedException Token格式错误时
   * @throws UnauthorizedException Token签名无效时
   * @throws UnauthorizedException Token解析失败时
   * @throws UnauthorizedException 无管理员权限时
   * @throws UnauthorizedException Token已过期时
   * 
   * @example
   * ```typescript
   * const payload = adminCoreService.verifyToken('eyJ...');
   * console.log(payload.adminId); // '1'
   * console.log(payload.role); // 9
   * ```
   */
  verifyToken(token: string): AdminAuthPayload {
    const secret = this.getAdminTokenSecret();
    const [payloadPart, signaturePart] = token.split('.');

    if (!payloadPart || !signaturePart) {
      throw new UnauthorizedException('Token格式错误');
    }

    const expected = this.hmacSha256Base64Url(payloadPart, secret);
    if (!this.safeEqual(signaturePart, expected)) {
      throw new UnauthorizedException('Token签名无效');
    }

    const payloadJson = Buffer.from(this.base64UrlToBase64(payloadPart), 'base64').toString('utf-8');
    let payload: AdminAuthPayload;

    try {
      payload = JSON.parse(payloadJson) as AdminAuthPayload;
    } catch {
      throw new UnauthorizedException('Token解析失败');
    }

    if (!payload?.adminId || payload.role !== 9) {
      throw new UnauthorizedException('无管理员权限');
    }

    if (typeof payload.exp !== 'number' || Date.now() > payload.exp) {
      throw new UnauthorizedException('Token已过期');
    }

    return payload;
  }

  /**
   * 管理员重置用户密码
   * 
   * 业务逻辑：
   * 1. 验证新密码强度要求
   * 2. 使用bcrypt生成密码哈希值
   * 3. 更新用户的密码哈希字段
   * 4. 完成密码重置操作
   * 
   * @param userId 要重置密码的用户ID
   * @param newPassword 新密码明文
   * @returns Promise<void> 无返回值
   * @throws BadRequestException 密码强度不符合要求时
   * 
   * @example
   * ```typescript
   * await adminCoreService.resetUserPassword(
   *   BigInt(123),
   *   'NewPassword123'
   * );
   * ```
   */
  async resetUserPassword(userId: bigint, newPassword: string): Promise<void> {
    this.validatePasswordStrength(newPassword);

    const passwordHash = await this.hashPassword(newPassword);
    await this.usersService.update(userId, { password_hash: passwordHash });
  }

  private async findUserByIdentifier(identifier: string): Promise<Users | null> {
    const byUsername = await this.usersService.findByUsername(identifier);
    if (byUsername) return byUsername;

    if (this.isEmail(identifier)) {
      const byEmail = await this.usersService.findByEmail(identifier);
      if (byEmail) return byEmail;
    }

    if (this.isPhoneNumber(identifier)) {
      const users = await this.usersService.findAll(1000, 0);
      return users.find((u: Users) => u.phone === identifier) || null;
    }

    return null;
  }

  private async bootstrapAdminIfEnabled(): Promise<void> {
    const enabled = this.configService.get<string>('ADMIN_BOOTSTRAP_ENABLED', 'false') === 'true';
    if (!enabled) return;

    const username = this.configService.get<string>('ADMIN_USERNAME');
    const password = this.configService.get<string>('ADMIN_PASSWORD');
    const nickname = this.configService.get<string>('ADMIN_NICKNAME', '管理员');

    if (!username || !password) {
      this.logger.warn('已启用管理员引导，但未配置 ADMIN_USERNAME / ADMIN_PASSWORD，跳过创建');
      return;
    }

    const existing = await this.usersService.findByUsername(username);
    if (existing) {
      if (existing.role !== 9) {
        this.logger.warn(`管理员引导发现同名用户但role!=9：${username}，跳过`);
      }
      return;
    }

    this.validatePasswordStrength(password);
    const passwordHash = await this.hashPassword(password);

    await this.usersService.create({
      username,
      password_hash: passwordHash,
      nickname,
      role: 9,
      email_verified: true,
    });

    this.logger.log(`管理员账号已创建：${username} (role=9)`);
  }

  private getAdminTokenSecret(): string {
    const secret = this.configService.get<string>('ADMIN_TOKEN_SECRET');
    if (!secret || secret.length < 16) {
      throw new BadRequestException('ADMIN_TOKEN_SECRET 未配置或过短（至少16字符）');
    }
    return secret;
  }

  private getAdminTokenTtlSeconds(): number {
    const raw = this.configService.get<string>('ADMIN_TOKEN_TTL_SECONDS', '28800'); // 8h
    const parsed = Number(raw);
    if (!Number.isFinite(parsed) || parsed <= 0) {
      return 28800;
    }
    return parsed;
  }

  private signPayload(payload: AdminAuthPayload): string {
    const secret = this.getAdminTokenSecret();
    const payloadJson = JSON.stringify(payload);
    const payloadPart = this.base64ToBase64Url(Buffer.from(payloadJson, 'utf-8').toString('base64'));
    const signature = this.hmacSha256Base64Url(payloadPart, secret);
    return `${payloadPart}.${signature}`;
  }

  private hmacSha256Base64Url(data: string, secret: string): string {
    const digest = crypto.createHmac('sha256', secret).update(data).digest('base64');
    return this.base64ToBase64Url(digest);
  }

  private base64ToBase64Url(base64: string): string {
    return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
  }

  private base64UrlToBase64(base64Url: string): string {
    const padded = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    const padLen = (4 - (padded.length % 4)) % 4;
    return padded + '='.repeat(padLen);
  }

  private safeEqual(a: string, b: string): boolean {
    const aBuf = Buffer.from(a);
    const bBuf = Buffer.from(b);
    if (aBuf.length !== bBuf.length) return false;
    return crypto.timingSafeEqual(aBuf, bBuf);
  }

  private isEmail(value: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
  }

  private isPhoneNumber(value: string): boolean {
    return /^\+?[0-9\-\s]{6,20}$/.test(value);
  }

  private validatePasswordStrength(password: string): void {
    if (password.length < 8) {
      throw new BadRequestException('密码长度至少8位');
    }

    if (password.length > 128) {
      throw new BadRequestException('密码长度不能超过128位');
    }

    const hasLetter = /[a-zA-Z]/.test(password);
    const hasNumber = /\d/.test(password);

    if (!hasLetter || !hasNumber) {
      throw new BadRequestException('密码必须包含字母和数字');
    }
  }

  private async hashPassword(password: string): Promise<string> {
    const SALT_ROUNDS = 12;
    return await bcrypt.hash(password, SALT_ROUNDS);
  }
}
