import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { HashChainService } from './services/hash-chain.service';
import { orderLogsByHashChain } from './utils/order-logs-by-hash-chain';
import {
  AuditAction,
  AuditStatus,
  ComplianceLevel,
  RiskLevel,
  Prisma,
} from '@prisma/client';
import { randomUUID as uuidv4 } from 'crypto';

/**
 * 创建审计日志 DTO
 */
export interface CreateAuditLogDto {
  // 多租户/多区域
  region: string;
  tenantId: string;

  // 5W1H
  who: string;
  what: string;
  where: string;
  why?: string;
  how: string;

  // 操作详情
  module: string;
  action: AuditAction;
  entityType: string;
  entityId: string;

  // 变更内容
  oldValue?: any;
  newValue?: any;
  changes?: any;

  // 上下文
  userId?: string; // 可选：匿名操作或系统操作可以为空
  sessionId?: string;
  traceId?: string;
  requestId?: string;

  // 环境
  ipAddress: string;
  userAgent: string;
  deviceId?: string;
  geoLocation?: string;

  // 业务
  businessType?: string;
  businessKey?: string;

  // 状态
  status?: AuditStatus;
  errorMessage?: string;
  duration?: number;

  // 合规
  isFinancial?: boolean;
  isSensitive?: boolean;
  riskLevel?: RiskLevel;
  complianceLevel?: ComplianceLevel;
}

/**
 * 查询过滤器
 */
export interface AuditQueryFilters {
  // 多租户
  region: string;
  tenantId: string;

  // 过滤条件
  userId?: string;
  module?: string;
  action?: AuditAction;
  entityType?: string;
  entityId?: string;
  status?: AuditStatus;
  startDate?: Date;
  endDate?: Date;
  isFinancial?: boolean;
  isSensitive?: boolean;
  riskLevel?: RiskLevel;
  complianceLevel?: ComplianceLevel;
  traceId?: string;
  sessionId?: string;
  keyword?: string;

  // 排序
  sortBy?: 'when' | 'module' | 'action' | 'userId';
  sortOrder?: 'asc' | 'desc';

  // 分页
  page?: number;
  limit?: number;
}

/**
 * 异步完整性检查任务
 */
export interface IntegrityCheckJob {
  jobId: string;
  region: string;
  tenantId: string;
  status: 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED';
  createdAt: Date;
  startedAt?: Date;
  completedAt?: Date;
  scope?: 'ALL' | 'FINANCIAL_ONLY' | 'SENSITIVE_ONLY' | 'HIGH_RISK_ONLY';
  startDate?: Date;
  endDate?: Date;
  result?: {
    verified: boolean;
    totalRecords: number;
    passCount: number;
    failCount: number;
    failures?: any[];
  };
  error?: string;
}

// 最大查询时间跨度（天）
const MAX_QUERY_DAYS = 90;
const MAX_FINANCIAL_QUERY_DAYS = 365;
const MAX_EXPORT_COUNT = 100000;

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly hashChain: HashChainService,
  ) {}

  // 注入告警服务（可选依赖，避免循环依赖）
  private alertService?: any;

  setAlertService(alertService: any) {
    this.alertService = alertService;
  }

  /**
   * 创建审计日志
   */
  async log(dto: CreateAuditLogDto) {
    try {
      // 1. 计算保留年限（基于合规级别）
      const retentionYears = this.calculateRetentionYears(
        dto.complianceLevel,
        dto.isFinancial,
      );

      // 2. 获取上一条日志的哈希（同一租户内）
      const previousLog = await this.getLatestLog(dto.region, dto.tenantId);
      const previousHash = previousLog?.currentHash || 'GENESIS';

      // 3. 准备日志数据
      const logData = {
        id: uuidv4(),
        region: dto.region,
        tenantId: dto.tenantId,
        who: dto.who,
        what: dto.what,
        when: new Date(),
        where: dto.where,
        why: dto.why || null,
        how: dto.how,
        module: dto.module,
        action: dto.action,
        entityType: dto.entityType,
        entityId: dto.entityId,
        oldValue: dto.oldValue ? this.sanitizeData(dto.oldValue) : null,
        newValue: dto.newValue ? this.sanitizeData(dto.newValue) : null,
        changes: dto.changes ? this.sanitizeData(dto.changes) : null,
        userId: dto.userId || null, // 允许为 null（匿名操作或系统操作）
        sessionId: dto.sessionId || uuidv4(), 
        traceId: dto.traceId || uuidv4(),
        requestId: dto.requestId || uuidv4(),
        ipAddress: dto.ipAddress,
        userAgent: dto.userAgent,
        deviceId: dto.deviceId || null,
        geoLocation: dto.geoLocation || null,
        businessType: dto.businessType || null,
        businessKey: dto.businessKey || null,
        status: dto.status || AuditStatus.SUCCESS,
        errorMessage: dto.errorMessage || null,
        duration: dto.duration || null,
        isFinancial: dto.isFinancial || false,
        isSensitive: dto.isSensitive || false,
        riskLevel: dto.riskLevel || RiskLevel.MEDIUM,
        complianceLevel: dto.complianceLevel || ComplianceLevel.MEDIUM,
        retentionYears,
        previousHash,
      };

      // 4. 计算当前哈希
      const currentHash = await this.hashChain.generateHash(logData);

      // 5. 生成 HMAC 数字签名
      const signature = await this.hashChain.generateSignature({
        ...logData,
        currentHash,
      });

      // 6. 保存到数据库
      const auditLog = await this.prisma.auditLog.create({
        data: {
          ...logData,
          currentHash,
          signature,
        } as any,
      });

      this.logger.debug(`Audit log created: ${auditLog.id} with signature`);

      // 7. 异步触发告警检查（不阻塞主流程）
      if (this.alertService) {
        // 异步执行，不等待结果
        setImmediate(() => {
          this.alertService.handleRealtimeAlert(auditLog).catch((error: any) => {
            this.logger.error('Alert handling failed', error);
          });
        });
      }

      return auditLog;
    } catch (error) {
      this.logger.error('Failed to create audit log', error);
      // 审计日志失败不应该影响主业务
      throw error;
    }
  }

  /**
   * 计算保留年限
   */
  private calculateRetentionYears(
    complianceLevel?: ComplianceLevel,
    isFinancial?: boolean,
  ): number {
    // 财务操作强制 7 年
    if (isFinancial) return 7;

    switch (complianceLevel) {
      case ComplianceLevel.HIGH:
        return 7;
      case ComplianceLevel.MEDIUM:
        return 5;
      case ComplianceLevel.LOW:
        return 3;
      default:
        return 5;
    }
  }

  /**
   * 获取最新的审计日志（租户内）
   */
  private async getLatestLog(region: string, tenantId: string) {
    return this.prisma.auditLog.findFirst({
      where: { region, tenantId },
      orderBy: { when: 'desc' },
      select: { currentHash: true },
    });
  }

  /**
   * 敏感字段脱敏
   */
  private sanitizeData(data: any): any {
    if (!data || typeof data !== 'object') return data;

    const sensitiveFields = [
      'password',
      'passwordHash',
      'token',
      'accessToken',
      'refreshToken',
      'secret',
      'apiKey',
      'privateKey',
      'creditCard',
      'ssn',
      'idCard',
      'bankCard',
      'cvv',
    ];

    const sanitize = (obj: any): any => {
      if (typeof obj !== 'object' || obj === null) return obj;

      if (Array.isArray(obj)) {
        return obj.map((item) => sanitize(item));
      }

      const result: any = {};
      for (const [key, value] of Object.entries(obj)) {
        const lowerKey = key.toLowerCase();
        if (sensitiveFields.some((f) => lowerKey.includes(f.toLowerCase()))) {
          result[key] = '***REDACTED***';
        } else if (typeof value === 'object') {
          result[key] = sanitize(value);
        } else {
          result[key] = value;
        }
      }
      return result;
    };

    return sanitize(data);
  }

  /**
   * 查询审计日志
   */
  async query(filters: AuditQueryFilters) {
    const {
      region,
      tenantId,
      page = 1,
      limit = 50,
      startDate,
      endDate,
      sortBy = 'when',
      sortOrder = 'desc',
      keyword,
      ...otherFilters
    } = filters;

    // 验证时间跨度
    if (startDate && endDate) {
      const daysDiff = Math.ceil(
        (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24),
      );
      if (daysDiff > MAX_QUERY_DAYS) {
        throw new BadRequestException({
          code: 'AUDIT_QUERY_TIME_RANGE_TOO_LARGE',
          message: `时间范围过大，最大允许 ${MAX_QUERY_DAYS} 天`,
        });
      }
    }

    // 构建查询条件
    // region 大小写不敏感：历史数据存在 'CN'/'cn' 混杂，新数据统一为小写
    const whereClause: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      ...otherFilters,
    };

    // 时间范围
    if (startDate || endDate) {
      whereClause.when = {};
      if (startDate) whereClause.when.gte = startDate;
      if (endDate) whereClause.when.lte = endDate;
    }

    // 关键词搜索
    if (keyword) {
      whereClause.OR = [
        { what: { contains: keyword, mode: 'insensitive' } },
        { why: { contains: keyword, mode: 'insensitive' } },
        { user: { username: { contains: keyword, mode: 'insensitive' } } },
      ];
    }

    // 构建排序
    const orderBy: any = {};
    orderBy[sortBy] = sortOrder;

    const [items, total] = await Promise.all([
      this.prisma.auditLog.findMany({
        where: whereClause,
        include: {
          user: {
            select: {
              id: true,
              username: true,
              displayName: true,
            },
          },
        },
        orderBy,
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.auditLog.count({ where: whereClause }),
    ]);

    const totalPages = Math.ceil(total / limit);

    return {
      items: items.map((item) => this.formatAuditLog(item)),
      total,
      page,
      limit,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    };
  }

  /**
   * 获取单条审计日志详情
   */
  async getById(id: string, region: string, tenantId: string) {
    const log = await this.prisma.auditLog.findFirst({
      where: { id, region: { equals: region, mode: 'insensitive' }, tenantId },
      include: {
        user: {
          select: {
            id: true,
            username: true,
            displayName: true,
          },
        },
        sensitiveOps: true,
      },
    });

    if (!log) {
      return null;
    }

    return this.formatAuditLog(log, true);
  }

  /**
   * 单条审计日志完整性校验
   * 重新计算哈希并与存储的 currentHash 对比，可检出 DB 直接改动。
   */
  async verifySingleLog(id: string, region: string, tenantId: string) {
    const log = await this.prisma.auditLog.findFirst({
      where: { id, region: { equals: region, mode: 'insensitive' }, tenantId },
    });

    if (!log) {
      return null;
    }

    const calculatedHash = await this.hashChain.generateHash(log);
    const valid = calculatedHash === log.currentHash;

    return {
      id: log.id,
      valid,
      storedHash: log.currentHash,
      calculatedHash,
      verifiedAt: new Date().toISOString(),
    };
  }

  /**
   * 获取实体的审计历史
   */
  async getEntityHistory(
    entityType: string,
    entityId: string,
    region: string,
    tenantId: string,
    options: { page?: number; limit?: number; includeDiff?: boolean } = {},
  ) {
    const { page = 1, limit = 50, includeDiff = true } = options;

    const whereClause: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      entityType: { equals: entityType, mode: 'insensitive' },
      entityId,
    };

    const [items, total] = await Promise.all([
      this.prisma.auditLog.findMany({
        where: whereClause,
        include: {
          user: {
            select: {
              id: true,
              username: true,
              displayName: true,
            },
          },
        },
        orderBy: { when: 'asc' }, // 时间线视图，从早到晚
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.auditLog.count({ where: whereClause }),
    ]);

    const totalPages = Math.ceil(total / limit);

    return {
      entityType,
      entityId,
      history: items.map((item) => ({
        id: item.id,
        action: item.action,
        when: item.when.toISOString(),
        who: item.who,
        ...(includeDiff && {
          oldValue: item.oldValue,
          newValue: item.newValue,
          changes: item.changes,
        }),
        status: item.status,
        user: item.user,
      })),
      total,
      page,
      limit,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    };
  }

  /**
   * 获取用户的操作历史
   */
  async getUserHistory(
    userId: string,
    region: string,
    tenantId: string,
    filters: {
      startDate?: Date;
      endDate?: Date;
      module?: string;
      action?: AuditAction;
      isSensitive?: boolean;
      isFinancial?: boolean;
      riskLevel?: RiskLevel;
      page?: number;
      limit?: number;
    } = {},
  ) {
    const { page = 1, limit = 50, startDate, endDate, ...otherFilters } = filters;

    const whereClause: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      userId,
      ...otherFilters,
    };

    if (startDate || endDate) {
      whereClause.when = {};
      if (startDate) whereClause.when.gte = startDate;
      if (endDate) whereClause.when.lte = endDate;
    }

    const [items, total, summary] = await Promise.all([
      this.prisma.auditLog.findMany({
        where: whereClause,
        orderBy: { when: 'desc' },
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.auditLog.count({ where: whereClause }),
      this.getUserOperationSummary(userId, region, tenantId),
    ]);

    // 获取用户信息
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { id: true, username: true, displayName: true },
    });

    const totalPages = Math.ceil(total / limit);

    return {
      userId,
      username: user?.username,
      displayName: user?.displayName,
      operations: items.map((item) => ({
        id: item.id,
        action: item.action,
        module: item.module,
        when: item.when.toISOString(),
        entityType: item.entityType,
        entityId: item.entityId,
        what: item.what,
        status: item.status,
        riskLevel: item.riskLevel,
        isFinancial: item.isFinancial,
        isSensitive: item.isSensitive,
      })),
      summary,
      total,
      page,
      limit,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    };
  }

  /**
   * 获取用户操作统计摘要
   */
  private async getUserOperationSummary(
    userId: string,
    region: string,
    tenantId: string,
  ) {
    const baseWhere = { region, tenantId, userId };

    const [
      total,
      byAction,
      byModule,
      sensitiveCount,
      financialCount,
      failedCount,
    ] = await Promise.all([
      this.prisma.auditLog.count({ where: baseWhere }),
      this.prisma.auditLog.groupBy({
        by: ['action'],
        where: baseWhere,
        _count: true,
      }),
      this.prisma.auditLog.groupBy({
        by: ['module'],
        where: baseWhere,
        _count: true,
      }),
      this.prisma.auditLog.count({
        where: { ...baseWhere, isSensitive: true },
      }),
      this.prisma.auditLog.count({
        where: { ...baseWhere, isFinancial: true },
      }),
      this.prisma.auditLog.count({
        where: { ...baseWhere, status: AuditStatus.FAILED },
      }),
    ]);

    return {
      total,
      byAction: Object.fromEntries(
        byAction.map((item) => [item.action, item._count]),
      ),
      byModule: Object.fromEntries(
        byModule.map((item) => [item.module, item._count]),
      ),
      sensitiveCount,
      financialCount,
      failedCount,
    };
  }

  /**
   * 获取财务相关操作日志
   */
  async getFinancialLogs(
    region: string,
    tenantId: string,
    filters: {
      startDate: Date;
      endDate: Date;
      userId?: string;
      action?: AuditAction;
      module?: string;
      sortBy?: string;
      sortOrder?: 'asc' | 'desc';
      page?: number;
      limit?: number;
    },
  ) {
    const {
      startDate,
      endDate,
      page = 1,
      limit = 50,
      sortBy = 'when',
      sortOrder = 'desc',
      ...otherFilters
    } = filters;

    // 验证时间跨度（财务查询最大 1 年）
    const daysDiff = Math.ceil(
      (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24),
    );
    if (daysDiff > MAX_FINANCIAL_QUERY_DAYS) {
      throw new BadRequestException({
        code: 'AUDIT_QUERY_TIME_RANGE_TOO_LARGE',
        message: `财务日志查询时间范围过大，最大允许 ${MAX_FINANCIAL_QUERY_DAYS} 天`,
      });
    }

    const whereClause: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      isFinancial: true,
      when: { gte: startDate, lte: endDate },
      ...otherFilters,
    };

    const orderBy: any = {};
    orderBy[sortBy] = sortOrder;

    const [items, total, summary] = await Promise.all([
      this.prisma.auditLog.findMany({
        where: whereClause,
        include: {
          user: {
            select: {
              id: true,
              username: true,
              displayName: true,
            },
          },
        },
        orderBy,
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.auditLog.count({ where: whereClause }),
      this.getFinancialSummary(region, tenantId, startDate, endDate),
    ]);

    const totalPages = Math.ceil(total / limit);

    return {
      items: items.map((item) => ({
        ...this.formatAuditLog(item),
        oldValue: item.oldValue,
        newValue: item.newValue,
        retentionYears: item.retentionYears,
      })),
      summary,
      total,
      page,
      limit,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    };
  }

  /**
   * 获取财务操作统计摘要
   */
  private async getFinancialSummary(
    region: string,
    tenantId: string,
    startDate: Date,
    endDate: Date,
  ) {
    const baseWhere: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      isFinancial: true,
      when: { gte: startDate, lte: endDate },
    };

    const [total, byAction, byModule] = await Promise.all([
      this.prisma.auditLog.count({ where: baseWhere }),
      this.prisma.auditLog.groupBy({
        by: ['action'],
        where: baseWhere,
        _count: true,
      }),
      this.prisma.auditLog.groupBy({
        by: ['module'],
        where: baseWhere,
        _count: true,
      }),
    ]);

    return {
      total,
      byAction: Object.fromEntries(
        byAction.map((item) => [item.action, item._count]),
      ),
      byModule: Object.fromEntries(
        byModule.map((item) => [item.module, item._count]),
      ),
    };
  }

  /**
   * 获取敏感操作日志
   */
  async getSensitiveLogs(
    region: string,
    tenantId: string,
    filters: {
      startDate?: Date;
      endDate?: Date;
      userId?: string;
      action?: AuditAction;
      riskLevel?: RiskLevel;
      page?: number;
      limit?: number;
    } = {},
  ) {
    const { page = 1, limit = 50, startDate, endDate, ...otherFilters } = filters;

    const whereClause: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      isSensitive: true,
      ...otherFilters,
    };

    if (startDate || endDate) {
      whereClause.when = {};
      if (startDate) whereClause.when.gte = startDate;
      if (endDate) whereClause.when.lte = endDate;
    }

    const [items, total] = await Promise.all([
      this.prisma.auditLog.findMany({
        where: whereClause,
        include: {
          user: {
            select: {
              id: true,
              username: true,
              displayName: true,
            },
          },
          sensitiveOps: true,
        },
        orderBy: { when: 'desc' },
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.auditLog.count({ where: whereClause }),
    ]);

    const totalPages = Math.ceil(total / limit);

    return {
      items: items.map((item) => ({
        ...this.formatAuditLog(item),
        mfaVerified: item.sensitiveOps?.[0]?.mfaVerified || false,
        deviceId: item.deviceId,
        geoLocation: item.geoLocation,
        requiresApproval: item.sensitiveOps?.[0]?.requiresApproval || false,
        approvedBy: item.sensitiveOps?.[0]?.approvedBy,
        approvalTime: item.sensitiveOps?.[0]?.approvalTime?.toISOString(),
      })),
      total,
      page,
      limit,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    };
  }

  /**
   * 按追踪ID获取完整请求链路
   */
  async getByTraceId(traceId: string, region: string, tenantId: string) {
    // 历史数据 region 大小写混杂，统一不敏感比较
    const logs = await this.prisma.auditLog.findMany({
      where: { traceId, region: { equals: region, mode: 'insensitive' }, tenantId },
      orderBy: { when: 'asc' },
    });

    const totalDuration = logs.reduce(
      (sum, log) => sum + (log.duration || 0),
      0,
    );

    return {
      traceId,
      logs: logs.map((log) => ({
        id: log.id,
        action: log.action,
        module: log.module,
        when: log.when.toISOString(),
        duration: log.duration,
        status: log.status,
        errorMessage: log.errorMessage,
      })),
      totalDuration,
    };
  }

  /**
   * 统计分析
   */
  async getStatistics(
    region: string,
    tenantId: string,
    startDate: Date,
    endDate: Date,
    filters?: { module?: string; userId?: string },
  ) {
    const baseWhere: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      when: { gte: startDate, lte: endDate },
      ...(filters?.module && { module: filters.module }),
      ...(filters?.userId && { userId: filters.userId }),
    };

    const [
      total,
      successCount,
      failedCount,
      financialCount,
      sensitiveCount,
      byModule,
      byAction,
    ] = await Promise.all([
      this.prisma.auditLog.count({ where: baseWhere }),
      this.prisma.auditLog.count({
        where: { ...baseWhere, status: AuditStatus.SUCCESS },
      }),
      this.prisma.auditLog.count({
        where: { ...baseWhere, status: AuditStatus.FAILED },
      }),
      this.prisma.auditLog.count({
        where: { ...baseWhere, isFinancial: true },
      }),
      this.prisma.auditLog.count({
        where: { ...baseWhere, isSensitive: true },
      }),
      this.prisma.auditLog.groupBy({
        by: ['module'],
        where: baseWhere,
        _count: true,
      }),
      this.prisma.auditLog.groupBy({
        by: ['action'],
        where: baseWhere,
        _count: true,
      }),
    ]);

    // 按天统计
    const byDay = await this.getStatisticsByDay(
      region,
      tenantId,
      startDate,
      endDate,
    );

    // 计算成功率
    const moduleStats = await Promise.all(
      byModule.map(async (item) => {
        const successInModule = await this.prisma.auditLog.count({
          where: {
            ...baseWhere,
            module: item.module,
            status: AuditStatus.SUCCESS,
          },
        });
        return {
          module: item.module,
          count: item._count,
          successRate:
            item._count > 0
              ? Math.round((successInModule / item._count) * 100 * 10) / 10
              : 100,
        };
      }),
    );

    return {
      period: {
        start: startDate.toISOString(),
        end: endDate.toISOString(),
      },
      summary: {
        total,
        success: successCount,
        failed: failedCount,
        financial: financialCount,
        sensitive: sensitiveCount,
      },
      byModule: moduleStats,
      byAction: byAction.map((item) => ({
        action: item.action,
        count: item._count,
      })),
      byDay,
    };
  }

  /**
   * 按天统计
   */
  private async getStatisticsByDay(
    region: string,
    tenantId: string,
    startDate: Date,
    endDate: Date,
  ) {
    // 使用原生 SQL 进行按天分组
    // 注意: "when" 是 PostgreSQL 保留字，需要用双引号包围
    const result = await this.prisma.$queryRaw<
      Array<{ date: string; count: bigint }>
    >`
      SELECT DATE("when" AT TIME ZONE 'UTC') as date, COUNT(*) as count
      FROM platform_audit.audit_log
      WHERE region = ${region}
        AND tenant_id = ${tenantId}
        AND "when" >= ${startDate}
        AND "when" <= ${endDate}
      GROUP BY DATE("when" AT TIME ZONE 'UTC')
      ORDER BY date ASC
    `;

    return result.map((item) => ({
      date: item.date,
      count: Number(item.count),
    }));
  }

  /**
   * 验证审计日志完整性
   */
  async verifyIntegrity(
    region: string,
    tenantId: string,
    startDate?: Date,
    endDate?: Date,
  ) {
    const startTime = Date.now();

    const whereClause: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
    };

    if (startDate || endDate) {
      whereClause.when = {};
      if (startDate) whereClause.when.gte = startDate;
      if (endDate) whereClause.when.lte = endDate;
    }

    const logs = await this.prisma.auditLog.findMany({
      where: whereClause,
      orderBy: { when: 'asc' },
    });

    // when 是 timestamptz(3)（ms 精度），同毫秒多条 orderBy when 的 tie-break 不稳定，
    // 会让 verifyHashChain 拿到的顺序与写入顺序不同 → previousHash 链伪断。
    // 按 previousHash → currentHash 拓扑遍历重排：写入侧本身就用 prevHash 串链，
    // 拓扑序 == 写入序，与 when 精度无关。不可达节点（链断/缺失）追加到尾部，
    // 让 verifyHashChain 仍能把它们标成 HASH_CHAIN_BROKEN。
    const ordered = orderLogsByHashChain(logs);

    const result = await this.hashChain.verifyHashChain(ordered);

    const duration = Date.now() - startTime;

    // 记录校验结果
    await this.prisma.auditIntegrityCheckLog.create({
      data: {
        region,
        tenantId,
        checkType: 'HASH_CHAIN',
        startTime: startDate || logs[0]?.when || new Date(),
        endTime: endDate || logs[logs.length - 1]?.when || new Date(),
        recordCount: logs.length,
        passCount: logs.length - result.failures.length,
        failCount: result.failures.length,
        failures: result.failures.length > 0 ? result.failures : null,
        status: result.success ? 'SUCCESS' : 'FAILED',
      } as any,
    });

    return {
      verified: result.success,
      totalRecords: logs.length,
      passCount: logs.length - result.failures.length,
      failCount: result.failures.length,
      failures: result.failures.map((f) => ({
        logId: f.logId,
        type: f.type,
        message: f.message,
        expectedHash: f.expectedHash,
        actualHash: f.actualHash,
      })),
      verifiedAt: new Date().toISOString(),
      duration,
    };
  }

  /**
   * 预估导出数量
   */
  async estimateExportCount(
    region: string,
    tenantId: string,
    startDate: Date,
    endDate: Date,
    filters?: any,
  ): Promise<number> {
    const whereClause: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      when: { gte: startDate, lte: endDate },
      ...filters,
    };

    return this.prisma.auditLog.count({ where: whereClause });
  }

  // ========== 完整性检查任务管理（异步） ==========
  // 内存任务队列：重启会丢；多实例不共享。生产应升级到 Redis / DB。
  private integrityJobs: Map<string, IntegrityCheckJob> = new Map();

  @Cron('0 2 * * *')
  async dailyIntegrityCheck() {
    await this.runScheduledIntegrityCheck('daily', 1);
  }

  @Cron('0 3 * * 0')
  async weeklyIntegrityCheck() {
    await this.runScheduledIntegrityCheck('weekly', 7);
  }

  @Cron('0 * * * *')
  async cleanupOldIntegrityJobs() {
    const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000);
    for (const [jobId, job] of this.integrityJobs.entries()) {
      if (job.createdAt < cutoff) {
        this.integrityJobs.delete(jobId);
      }
    }
  }

  async triggerAsyncIntegrityCheck(
    region: string,
    tenantId: string,
    options: {
      startDate?: Date;
      endDate?: Date;
      scope?: 'ALL' | 'FINANCIAL_ONLY' | 'SENSITIVE_ONLY' | 'HIGH_RISK_ONLY';
    } = {},
  ): Promise<string> {
    const jobId = uuidv4();
    const job: IntegrityCheckJob = {
      jobId,
      region,
      tenantId,
      status: 'QUEUED',
      createdAt: new Date(),
      scope: options.scope || 'ALL',
      startDate: options.startDate,
      endDate: options.endDate,
    };
    this.integrityJobs.set(jobId, job);

    this.executeAsyncIntegrityCheck(job).catch((error) => {
      this.logger.error(`Async integrity check job ${jobId} failed:`, error);
      job.status = 'FAILED';
      job.error = error.message;
    });

    return jobId;
  }

  async getIntegrityJobStatus(
    jobId: string,
    region: string,
    tenantId: string,
  ): Promise<IntegrityCheckJob | null> {
    const job = this.integrityJobs.get(jobId);
    if (!job || job.region !== region || job.tenantId !== tenantId) {
      return null;
    }
    return job;
  }

  private async executeAsyncIntegrityCheck(job: IntegrityCheckJob): Promise<void> {
    job.status = 'RUNNING';
    job.startedAt = new Date();
    try {
      const result = await this.verifyIntegrity(
        job.region,
        job.tenantId,
        job.startDate,
        job.endDate,
      );
      job.status = 'COMPLETED';
      job.completedAt = new Date();
      job.result = {
        verified: result.verified,
        totalRecords: result.totalRecords,
        passCount: result.passCount,
        failCount: result.failCount,
        failures: result.failures,
      };
      this.logger.log(
        `Async integrity check ${job.jobId} completed. Verified: ${result.verified}`,
      );
    } catch (error) {
      job.status = 'FAILED';
      job.completedAt = new Date();
      job.error = error.message;
      throw error;
    }
  }

  private async runScheduledIntegrityCheck(
    label: 'daily' | 'weekly',
    daysBack: number,
  ) {
    this.logger.log(`Starting ${label} integrity check...`);
    try {
      const tenants = await this.getActiveTenants();
      for (const tenant of tenants) {
        const startDate = new Date();
        startDate.setDate(startDate.getDate() - daysBack);
        startDate.setHours(0, 0, 0, 0);
        const endDate = new Date();
        if (label === 'daily') endDate.setHours(0, 0, 0, 0);

        const result = await this.verifyIntegrity(
          tenant.region,
          tenant.tenantId,
          startDate,
          endDate,
        );

        if (result.verified) {
          this.logger.log(
            `${label} integrity check passed for ${tenant.tenantId}. Verified ${result.totalRecords} records.`,
          );
        } else {
          this.logger.error(
            `${label} integrity check FAILED for ${tenant.tenantId}! Found ${result.failCount} violations.`,
          );
        }
      }
    } catch (error) {
      this.logger.error(`${label} integrity check failed with error:`, error);
      throw error;
    }
  }

  private async getActiveTenants(): Promise<
    Array<{ region: string; tenantId: string }>
  > {
    const result = await this.prisma.auditLog.findMany({
      select: { region: true, tenantId: true },
      distinct: ['region', 'tenantId'],
    });
    return result.length > 0 ? result : [{ region: 'cn', tenantId: 'default' }];
  }

  /**
   * 导出审计日志
   */
  async exportLogs(
    region: string,
    tenantId: string,
    startDate: Date,
    endDate: Date,
    filters?: any,
    fields?: string[],
  ) {
    // 先预估数量
    const estimatedCount = await this.estimateExportCount(
      region,
      tenantId,
      startDate,
      endDate,
      filters,
    );

    if (estimatedCount > MAX_EXPORT_COUNT) {
      throw new BadRequestException({
        code: 'AUDIT_EXPORT_TOO_LARGE',
        message: `导出数据量过大，预估 ${estimatedCount} 条，超过限制 ${MAX_EXPORT_COUNT} 条`,
        details: {
          estimatedCount,
          maxAllowed: MAX_EXPORT_COUNT,
        },
      });
    }

    const whereClause: Prisma.AuditLogWhereInput = {
      region: { equals: region, mode: 'insensitive' },
      tenantId,
      when: { gte: startDate, lte: endDate },
      ...filters,
    };

    const logs = await this.prisma.auditLog.findMany({
      where: whereClause,
      include: {
        user: {
          select: {
            username: true,
            displayName: true,
            email: true,
          },
        },
      },
      orderBy: { when: 'asc' },
    });

    // 如果指定了字段，则只返回指定字段
    if (fields && fields.length > 0) {
      return logs.map((log) => {
        const filtered: any = {};
        for (const field of fields) {
          if (field in log) {
            filtered[field] = (log as any)[field];
          }
        }
        return filtered;
      });
    }

    return logs;
  }

  /**
   * 格式化审计日志（includeDetails=true 返回详情字段）
   */
  private formatAuditLog(item: any, includeDetails = false) {
    const base = {
      id: item.id,
      region: item.region,
      tenantId: item.tenantId,
      who: item.who,
      what: item.what,
      when: item.when.toISOString(),
      where: item.where,
      why: item.why,
      how: item.how,
      module: item.module,
      action: item.action,
      entityType: item.entityType,
      entityId: item.entityId,
      userId: item.userId,
      ipAddress: item.ipAddress,
      userAgent: item.userAgent,
      status: item.status,
      duration: item.duration,
      riskLevel: item.riskLevel,
      complianceLevel: item.complianceLevel,
      isFinancial: item.isFinancial,
      isSensitive: item.isSensitive,
      user: item.user,
    };

    if (!includeDetails) return base;

    return {
      ...base,
      oldValue: item.oldValue,
      newValue: item.newValue,
      changes: item.changes,
      sessionId: item.sessionId,
      traceId: item.traceId,
      requestId: item.requestId,
      deviceId: item.deviceId,
      geoLocation: item.geoLocation,
      businessType: item.businessType,
      businessKey: item.businessKey,
      errorMessage: item.errorMessage,
      previousHash: item.previousHash,
      currentHash: item.currentHash,
      signature: item.signature,
      createdAt: item.createdAt.toISOString(),
      archivedAt: item.archivedAt ? item.archivedAt.toISOString() : null,
      retentionYears: item.retentionYears,
    };
  }
}
