/**
 * 管理员控制器
 * 
 * 功能描述：
 * - 提供管理员登录认证接口
 * - 提供用户管理相关接口（查询、重置密码）
 * - 提供系统日志查询和下载功能
 * 
 * 职责分离：
 * - HTTP请求处理和参数验证
 * - 业务逻辑委托给AdminService处理
 * - 权限控制通过AdminGuard实现
 * 
 * API端点：
 * - POST /admin/auth/login                管理员登录
 * - GET  /admin/users                     用户列表（需要管理员Token）
 * - GET  /admin/users/:id                 用户详情（需要管理员Token）
 * - POST /admin/users/:id/reset-password  重置指定用户密码（需要管理员Token）
 * - GET  /admin/logs/runtime              获取运行日志尾部（需要管理员Token）
 * 
 * 最近修改：
 * - 2026-01-09: 代码质量优化 - 将同步文件系统操作改为异步操作，避免阻塞事件循环 (修改者: moyin)
 * - 2026-01-07: 代码规范优化 - 修正文件命名规范，更新作者信息和修改记录
 * - 2026-01-08: 注释规范优化 - 补充方法注释，添加@param、@returns、@throws和@example (修改者: moyin)
 * 
 * @author moyin
 * @version 1.0.4
 * @since 2025-12-19
 * @lastModified 2026-01-09
 */

import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query, UseGuards, ValidationPipe, UsePipes, Res, Logger } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiProduces, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { AdminGuard } from './admin.guard';
import { AdminService } from './admin.service';
import { AdminLoginDto, AdminResetPasswordDto } from './admin_login.dto';
import { 
  AdminLoginResponseDto, 
  AdminUsersResponseDto, 
  AdminCommonResponseDto, 
  AdminUserResponseDto, 
  AdminRuntimeLogsResponseDto 
} from './admin_response.dto';
import { Throttle, ThrottlePresets } from '../../core/security_core/throttle.decorator';
import { getCurrentTimestamp } from './admin_utils';
import type { Response } from 'express';
import * as fs from 'fs';
import * as path from 'path';
import { spawn } from 'child_process';
import { pipeline } from 'stream';

@ApiTags('admin')
@Controller('admin')
export class AdminController {
  private readonly logger = new Logger(AdminController.name);

  constructor(private readonly adminService: AdminService) {}

  /**
   * 管理员登录
   * 
   * 功能描述：
   * 验证管理员身份并生成JWT Token，仅允许role=9的账户登录后台
   * 
   * 业务逻辑：
   * 1. 验证登录标识符和密码
   * 2. 检查用户角色是否为管理员(role=9)
   * 3. 生成JWT Token
   * 4. 返回登录结果和Token
   * 
   * @param dto 登录请求数据
   * @returns 登录结果，包含Token和管理员信息
   * 
   * @throws UnauthorizedException 当登录失败时
   * @throws ForbiddenException 当权限不足或账户被禁用时
   * @throws TooManyRequestsException 当登录尝试过于频繁时
   * 
   * @example
   * ```typescript
   * const result = await adminController.login({
   *   identifier: 'admin',
   *   password: 'Admin123456'
   * });
   * ```
   */
  @ApiOperation({ summary: '管理员登录', description: '仅允许 role=9 的账户登录后台' })
  @ApiBody({ type: AdminLoginDto })
  @ApiResponse({ status: 200, description: '登录成功', type: AdminLoginResponseDto })
  @ApiResponse({ status: 401, description: '登录失败' })
  @ApiResponse({ status: 403, description: '权限不足或账户被禁用' })
  @ApiResponse({ status: 429, description: '登录尝试过于频繁' })
  @Throttle(ThrottlePresets.LOGIN)
  @Post('auth/login')
  @HttpCode(HttpStatus.OK)
  @UsePipes(new ValidationPipe({ transform: true }))
  async login(@Body() dto: AdminLoginDto) {
    return await this.adminService.login(dto.identifier, dto.password);
  }

  /**
   * 获取用户列表
   * 
   * 功能描述：
   * 分页获取系统中的用户列表，支持限制数量和偏移量参数
   * 
   * 业务逻辑：
   * 1. 解析查询参数（limit和offset）
   * 2. 调用用户服务获取用户列表
   * 3. 格式化用户数据
   * 4. 返回分页结果
   * 
   * @param limit 返回数量，默认100，可选参数
   * @param offset 偏移量，默认0，可选参数
   * @returns 用户列表和分页信息
   * 
   * @example
   * ```typescript
   * // 获取前20个用户
   * const result = await adminController.listUsers('20', '0');
   * ```
   */
  @ApiBearerAuth('JWT-auth')
  @ApiOperation({ summary: '获取用户列表', description: '后台用户管理：分页获取用户列表' })
  @ApiQuery({ name: 'limit', required: false, description: '返回数量（默认100）' })
  @ApiQuery({ name: 'offset', required: false, description: '偏移量（默认0）' })
  @ApiResponse({ status: 200, description: '获取成功', type: AdminUsersResponseDto })
  @UseGuards(AdminGuard)
  @Get('users')
  async listUsers(
    @Query('limit') limit?: string,
    @Query('offset') offset?: string,
  ) {
    const parsedLimit = limit ? Number(limit) : 100;
    const parsedOffset = offset ? Number(offset) : 0;
    return await this.adminService.listUsers(parsedLimit, parsedOffset);
  }

  /**
   * 获取用户详情
   * 
   * 功能描述：
   * 根据用户ID获取指定用户的详细信息
   * 
   * 业务逻辑：
   * 1. 验证用户ID格式
   * 2. 查询用户详细信息
   * 3. 格式化用户数据
   * 4. 返回用户详情
   * 
   * @param id 用户ID字符串
   * @returns 用户详细信息
   * 
   * @throws NotFoundException 当用户不存在时
   * 
   * @example
   * ```typescript
   * const result = await adminController.getUser('123');
   * ```
   */
  @ApiBearerAuth('JWT-auth')
  @ApiOperation({ summary: '获取用户详情' })
  @ApiParam({ name: 'id', description: '用户ID' })
  @ApiResponse({ status: 200, description: '获取成功', type: AdminUserResponseDto })
  @UseGuards(AdminGuard)
  @Get('users/:id')
  async getUser(@Param('id') id: string) {
    return await this.adminService.getUser(BigInt(id));
  }

  /**
   * 重置用户密码
   * 
   * 功能描述：
   * 管理员直接为指定用户设置新密码，新密码需满足密码强度规则
   * 
   * 业务逻辑：
   * 1. 验证用户ID和新密码格式
   * 2. 检查用户是否存在
   * 3. 验证密码强度规则
   * 4. 更新用户密码
   * 5. 记录操作日志
   * 
   * @param id 用户ID字符串
   * @param dto 密码重置请求数据
   * @returns 重置结果
   * 
   * @throws NotFoundException 当用户不存在时
   * @throws BadRequestException 当密码不符合强度规则时
   * @throws TooManyRequestsException 当操作过于频繁时
   * 
   * @example
   * ```typescript
   * const result = await adminController.resetPassword('123', {
   *   newPassword: 'NewPass1234'
   * });
   * ```
   */
  @ApiBearerAuth('JWT-auth')
  @ApiOperation({ summary: '重置用户密码', description: '管理员直接为用户设置新密码（需满足密码强度规则）' })
  @ApiParam({ name: 'id', description: '用户ID' })
  @ApiBody({ type: AdminResetPasswordDto })
  @ApiResponse({ status: 200, description: '重置成功', type: AdminCommonResponseDto })
  @ApiResponse({ status: 429, description: '操作过于频繁' })
  @UseGuards(AdminGuard)
  @Throttle(ThrottlePresets.ADMIN_OPERATION)
  @Post('users/:id/reset-password')
  @HttpCode(HttpStatus.OK)
  @UsePipes(new ValidationPipe({ transform: true }))
  async resetPassword(@Param('id') id: string, @Body() dto: AdminResetPasswordDto) {
    return await this.adminService.resetPassword(BigInt(id), dto.newPassword);
  }

  @ApiBearerAuth('JWT-auth')
  @ApiOperation({ summary: '获取运行日志尾部', description: '从 logs/ 目录读取最近的日志行（默认200行）' })
  @ApiQuery({ name: 'lines', required: false, description: '返回行数（默认200，最大2000）' })
  @ApiResponse({ status: 200, description: '获取成功', type: AdminRuntimeLogsResponseDto })
  @UseGuards(AdminGuard)
  @Get('logs/runtime')
  async getRuntimeLogs(@Query('lines') lines?: string) {
    const parsedLines = lines ? Number(lines) : undefined;
    return await this.adminService.getRuntimeLogs(parsedLines);
  }

  @ApiBearerAuth('JWT-auth')
  @ApiOperation({ summary: '下载全部运行日志', description: '将 logs/ 目录打包为 tar.gz 并下载（需要管理员Token）' })
  @ApiProduces('application/gzip')
  @ApiResponse({ status: 200, description: '打包下载成功（tar.gz 二进制流）' })
  @UseGuards(AdminGuard)
  @Get('logs/archive')
  async downloadLogsArchive(@Res() res: Response) {
    const logDir = this.adminService.getLogDirAbsolutePath();

    // 验证日志目录
    const dirValidation = await this.validateLogDirectory(logDir, res);
    if (!dirValidation.isValid) {
      return;
    }

    // 设置响应头
    this.setArchiveResponseHeaders(res);

    // 创建并处理tar进程
    await this.createAndHandleTarProcess(logDir, res);
  }

  /**
   * 验证日志目录是否存在且可用
   * 
   * @param logDir 日志目录路径
   * @param res 响应对象
   * @returns 验证结果
   */
  private async validateLogDirectory(logDir: string, res: Response): Promise<{ isValid: boolean }> {
    try {
      const stats = await fs.promises.stat(logDir);
      if (!stats.isDirectory()) {
        res.status(404).json({ success: false, message: '日志目录不可用' });
        return { isValid: false };
      }
      return { isValid: true };
    } catch (error) {
      res.status(404).json({ success: false, message: '日志目录不存在' });
      return { isValid: false };
    }
  }

  /**
   * 设置文件下载的响应头
   * 
   * @param res 响应对象
   */
  private setArchiveResponseHeaders(res: Response): void {
    const ts = getCurrentTimestamp().replace(/[:.]/g, '-');
    const filename = `logs-${ts}.tar.gz`;

    res.setHeader('Content-Type', 'application/gzip');
    res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
    res.setHeader('Cache-Control', 'no-store');
  }

  /**
   * 创建并处理tar进程
   * 
   * @param logDir 日志目录路径
   * @param res 响应对象
   */
  private async createAndHandleTarProcess(logDir: string, res: Response): Promise<void> {
    const parentDir = path.dirname(logDir);
    const baseName = path.basename(logDir);

    const tar = spawn('tar', ['-czf', '-', '-C', parentDir, baseName], {
      stdio: ['ignore', 'pipe', 'pipe'],
    });

    // 处理tar进程的stderr输出
    tar.stderr.on('data', (chunk: Buffer) => {
      const msg = chunk.toString('utf8').trim();
      if (msg) {
        this.logger.warn(`tar stderr: ${msg}`);
      }
    });

    // 处理tar进程错误
    tar.on('error', (err: any) => {
      this.handleTarProcessError(err, res);
    });

    // 处理数据流和进程退出
    await this.handleTarStreams(tar, res);
  }

  /**
   * 处理tar进程错误
   * 
   * @param err 错误对象
   * @param res 响应对象
   */
  private handleTarProcessError(err: any, res: Response): void {
    this.logger.error('打包日志失败（tar 进程启动失败）', err?.stack || String(err));
    if (!res.headersSent) {
      const msg = err?.code === 'ENOENT' ? '服务器缺少 tar 命令，无法打包日志' : '日志打包失败';
      res.status(500).json({ success: false, message: msg });
    } else {
      res.end();
    }
  }

  /**
   * 处理tar进程的数据流和退出
   * 
   * @param tar tar进程
   * @param res 响应对象
   */
  private async handleTarStreams(tar: any, res: Response): Promise<void> {
    const pipelinePromise = new Promise<void>((resolve, reject) => {
      pipeline(tar.stdout, res, (err) => (err ? reject(err) : resolve()));
    });

    const exitPromise = new Promise<void>((resolve, reject) => {
      tar.on('close', (code) => {
        if (code === 0) {
          resolve();
        } else {
          reject(new Error(`tar exited with code ${code ?? 'unknown'}`));
        }
      });
    });

    try {
      await pipelinePromise;
      await exitPromise;
    } catch (err) {
      this.logger.error('打包日志失败（tar 执行或输出失败）', err instanceof Error ? err.stack : String(err));
      if (!res.headersSent) {
        res.status(500).json({ success: false, message: '日志打包失败' });
      } else {
        res.end();
      }
    }
  }
}
