/**
 * Audit Interceptor 集成测试
 *
 * 关键：必须使用本目录 _helpers.ts 的 createAuditTestApp（不 override
 * APP_INTERCEPTOR），否则审计拦截器不会运行。
 */

import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { setupIntegrationTest } from '../../helpers/test-setup.helper';
import { cleanupDatabase } from '../../helpers/cleanup.helper';
import { createAuditTestApp } from './_helpers';

describe('Audit Interceptor', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let adminToken: string;
  let beforeCount: number;

  beforeAll(async () => {
    app = await createAuditTestApp();
    prisma = app.get<PrismaService>(PrismaService);
  });

  beforeEach(async () => {
    const ctx = await setupIntegrationTest(app, prisma);
    adminToken = ctx.adminToken;
    beforeCount = await prisma.auditLog.count();
  });

  afterEach(async () => {
    await cleanupDatabase(prisma);
  });

  afterAll(async () => {
    await app.close();
  });

  // 拦截器仅对带 @Auditable() 的 handler 生效。/auth/login 是已知 @Auditable 端点。
  const triggerAuditableCall = async () => {
    await request(app.getHttpServer())
      .post('/api/v1/auth/login')
      .send({ username: 'wrong_user_t_audit_int', password: 'AnyPass!' });
    // 异步落库
    await new Promise((r) => setTimeout(r, 400));
  };

  it('[AUDIT-INT-001] @Auditable 端点调用后 audit_log 至少 +1 条', async () => {
    await triggerAuditableCall();

    const after = await prisma.auditLog.count();
    expect(after).toBeGreaterThan(beforeCount);
  });

  it('[AUDIT-INT-002] region 字段写入后小写归一', async () => {
    await triggerAuditableCall();

    const log = await prisma.auditLog.findFirst({
      orderBy: { when: 'desc' },
    });
    expect(log).not.toBeNull();
    expect(log!.region).toBe(log!.region.toLowerCase());
    expect(log!.region).toBe('cn');
  });

  it('[AUDIT-INT-003] 5W1H 字段全部非空，currentHash 非空', async () => {
    await triggerAuditableCall();

    const log = await prisma.auditLog.findFirst({
      orderBy: { when: 'desc' },
    });
    expect(log).not.toBeNull();
    expect(log!.who).toBeTruthy();
    expect(log!.what).toBeTruthy();
    expect(log!.when).toBeTruthy();
    expect(log!.where).toBeTruthy();
    expect(log!.how).toBeTruthy();
    expect(log!.currentHash).toBeTruthy();
    expect(log!.currentHash.length).toBe(64); // SHA-256 hex
  });

  it('[AUDIT-INT-004] currentHash 全局唯一', async () => {
    // 触发多条审计日志（每次错误登录会产生独立审计记录）
    for (let i = 0; i < 3; i++) {
      await triggerAuditableCall();
    }
    const recent = await prisma.auditLog.findMany({
      orderBy: { when: 'desc' },
      take: 5,
      select: { currentHash: true },
    });
    const hashes = recent.map((r) => r.currentHash);
    const unique = new Set(hashes);
    expect(unique.size).toBe(hashes.length);
  });

  it('[AUDIT-INT-005] 错误密码登录记录 status=FAILED 与 errorMessage 非空', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/auth/login')
      .send({ username: 'no_such_user_t_audit', password: 'WrongPass!' })
      .expect(401);

    // 给拦截器异步落库一些时间
    await new Promise((r) => setTimeout(r, 200));

    const failed = await prisma.auditLog.findFirst({
      where: { status: 'FAILED' },
      orderBy: { when: 'desc' },
    });
    expect(failed).not.toBeNull();
    expect(failed!.errorMessage).toBeTruthy();
  });
});