/**
 * Audit 完整性校验集成测试 (verify-integrity / integrity-check)
 *
 * 用 sandbox tenantId 串自己的小哈希链，不动 25 条种子记录。
 */

import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { createTestApp } from '../../helpers/app.helper';
import { setupIntegrationTest } from '../../helpers/test-setup.helper';
import { cleanupDatabase } from '../../helpers/cleanup.helper';
import {
  cleanupAuditSandbox,
  computeHash,
  insertAuditLogViaPrisma,
  uniqueTenantId,
} from './_helpers';

describe('Audit Integrity (verify-integrity / integrity-check)', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let adminToken: string;

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

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

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

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

  it('[AUDIT-VI-001] verify-integrity 返回 verified=true（sandbox tenant，3 条干净链）', async () => {
    // 不打 cn/default：跨 suite 累积的 audit_log（cleanupAllData 不清此表）+ when
    // 列 ms 精度（platform_audit.prisma:11）→ orderBy when ASC 的 tie-break 不稳定，
    // verify 拿到的顺序可能与写入顺序不同 → previousHash 链断 → flaky verified=false。
    // 改用 sandbox tenantId 隔离，并直接调用 service（HTTP controller 从 user.tenantId
    // 取域，无法注入 sandbox）。HTTP 路径覆盖由 AUDIT-VI-002 (POST /integrity-check) 承担。
    const tenantId = uniqueTenantId();
    try {
      await insertAuditLogViaPrisma(prisma, { tenantId, what: 'a' });
      await insertAuditLogViaPrisma(prisma, { tenantId, what: 'b' });
      await insertAuditLogViaPrisma(prisma, { tenantId, what: 'c' });

      const auditService = app.get(
        require('@/core/observability/audit/audit.service').AuditService,
      );
      const result = await auditService.verifyIntegrity('cn', tenantId);

      if (!result.verified) {
        // 失败时把 failures 详情打出来，避免下次回归只剩 expected/received 两行没法定位
        // eslint-disable-next-line no-console
        console.error('AUDIT-VI-001 failures:', JSON.stringify(result.failures, null, 2));
      }
      expect(result.verified).toBe(true);
      expect(result.failCount).toBe(0);
      expect(result.totalRecords).toBe(3);
    } finally {
      await cleanupAuditSandbox(prisma, tenantId);
    }
  });

  it('[AUDIT-VI-002] integrity-check 接受 4 个 scope 值（B1 修复回归）', async () => {
    for (const scope of ['ALL', 'FINANCIAL_ONLY', 'SENSITIVE_ONLY', 'HIGH_RISK_ONLY']) {
      const res = await request(app.getHttpServer())
        .post('/api/v1/audit/integrity-check')
        .set('Authorization', `Bearer ${adminToken}`)
        .send({ scope })
        .expect(201);
      expect(res.body.data.jobId).toBeTruthy();
      expect(res.body.data.status).toBe('QUEUED');
    }
  });

  it('[AUDIT-VI-003] integrity-check 拒绝非法 scope', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/audit/integrity-check')
      .set('Authorization', `Bearer ${adminToken}`)
      .send({ scope: 'INVALID_SCOPE' })
      .expect(400);
  });

  it('[AUDIT-VI-004] sandbox 链：篡改 newValue 后报 HASH_MISMATCH', async () => {
    const tenantId = uniqueTenantId();
    try {
      // 串 3 条干净链
      const a = await insertAuditLogViaPrisma(prisma, { tenantId, what: 'a' });
      const b = await insertAuditLogViaPrisma(prisma, { tenantId, what: 'b' });
      await insertAuditLogViaPrisma(prisma, { tenantId, what: 'c' });

      // 篡改 b：直接改 newValue（不重算 currentHash）
      await prisma.auditLog.update({
        where: { id: b.id },
        data: { newValue: { tampered: true } },
      });

      // 用 service 通过 controller 校验该 tenant
      // 测试管理员的 tenantId='default'，无法直接打 controller。
      // 改：直接调 service.verifyIntegrity（通过 app.get(AuditService)）。
      const auditService = app.get(
        require('@/core/observability/audit/audit.service').AuditService,
      );
      const result = await auditService.verifyIntegrity('cn', tenantId);

      expect(result.verified).toBe(false);
      expect(result.failCount).toBeGreaterThan(0);
      expect(
        result.failures.some((f: any) => f.type === 'HASH_MISMATCH'),
      ).toBe(true);
    } finally {
      await cleanupAuditSandbox(prisma, tenantId);
    }
  });

  it('[AUDIT-VI-005] sandbox 链：删除中间记录后报 HASH_CHAIN_BROKEN', async () => {
    const tenantId = uniqueTenantId();
    try {
      await insertAuditLogViaPrisma(prisma, { tenantId, what: 'a' });
      const b = await insertAuditLogViaPrisma(prisma, { tenantId, what: 'b' });
      await insertAuditLogViaPrisma(prisma, { tenantId, what: 'c' });

      // 删除中间记录
      await prisma.auditLog.delete({ where: { id: b.id } });

      const auditService = app.get(
        require('@/core/observability/audit/audit.service').AuditService,
      );
      const result = await auditService.verifyIntegrity('cn', tenantId);

      expect(result.verified).toBe(false);
      expect(
        result.failures.some((f: any) => f.type === 'HASH_CHAIN_BROKEN'),
      ).toBe(true);
    } finally {
      await cleanupAuditSandbox(prisma, tenantId);
    }
  });

  it('[AUDIT-VI-006] computeHash 与后端算法一致（自检）', async () => {
    const tenantId = uniqueTenantId();
    try {
      const log = await insertAuditLogViaPrisma(prisma, { tenantId, what: 'self-check' });
      // 重新加载完整记录，重算 hash 应等于存储 hash
      const fresh = await prisma.auditLog.findUnique({ where: { id: log.id } });
      const recomputed = computeHash(fresh);
      expect(recomputed).toBe(fresh!.currentHash);
    } finally {
      await cleanupAuditSandbox(prisma, tenantId);
    }
  });

  it('[AUDIT-VI-007] GET /integrity-check/:jobId 不存在返回 404', async () => {
    await request(app.getHttpServer())
      .get('/api/v1/audit/integrity-check/00000000-0000-0000-0000-000000000000')
      .set('Authorization', `Bearer ${adminToken}`)
      .expect(404);
  });

  // 拓扑排序回归测试不放这里：pg orderBy when 在同 ms tie-break 上 *实际* 多数情况
  // 返回 heap order（≈ 插入序），无法稳定复现 bug。纯逻辑校验放在
  // backend/scripts/probes/audit-order-logs-by-hash-chain.ts，直接喂打乱顺序的数组。
});