/**
 * FFAI Agent — artifact API + file_save path traversal 集成测试
 *
 * 覆盖：
 *   - GET /agent/artifacts?sessionId=... 列本 org artifact
 *   - GET /agent/artifacts/:id 跨 org artifact 必拒（403）
 *   - POST /agent/tools/file_save 路径遍历 (..) 必拒（400/403，关闭 hard_block 回归）
 *   - GET /agent/artifacts 缺 sessionId 返 400
 */
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaService } from '@/core/database/prisma/prisma.service';
import { cleanupDatabase } from '../../helpers/cleanup.helper';
import { createTestApp } from '../../helpers/app.helper';
import { setupIntegrationTest } from '../../helpers/test-setup.helper';

describe('FFAI Agent - Artifact API + file_save 路径遍历回归', () => {
  let app: INestApplication;
  let prisma: PrismaService;
  let adminToken: string;
  let adminUserId: string;
  let orgId: string;

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

  beforeEach(async () => {
    const ctx = await setupIntegrationTest(app, prisma);
    adminToken = ctx.adminToken;
    adminUserId = ctx.adminUser.id;
    const org = await prisma.organization.findFirst({ orderBy: { createdAt: 'asc' } });
    orgId = org!.id;
  });

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

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

  it('[AGENT-ART-001] GET /agent/artifacts?sessionId=... 列出本 org artifact', async () => {
    const sess = await prisma.agentSession.create({
      data: { organizationId: orgId, createdById: adminUserId, title: 't_art_001' },
    });
    await prisma.agentArtifact.create({
      data: {
        organizationId: orgId,
        sessionId: sess.id,
        createdById: adminUserId,
        type: 'TABLE',
        title: 't_art_001_artifact',
        data: { columns: ['a'], rows: [[1]] } as never,
      },
    });

    const res = await request(app.getHttpServer())
      .get(`/api/v1/agent/artifacts?sessionId=${sess.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(200);

    expect(res.body.data.items.length).toBeGreaterThanOrEqual(1);
    expect(res.body.data.items[0].title).toBe('t_art_001_artifact');
  });

  it('[AGENT-ART-002] GET /agent/artifacts/:id 跨 org artifact 必拒', async () => {
    const otherOrg = await prisma.organization.create({
      data: { code: `T_art_${Date.now()}`, name: 'other-org-art' },
    });
    const otherSess = await prisma.agentSession.create({
      data: {
        organizationId: otherOrg.id,
        createdById: '00000000-0000-0000-0000-000000000000',
        title: 't_art_002_other',
      },
    });
    const otherArt = await prisma.agentArtifact.create({
      data: {
        organizationId: otherOrg.id,
        sessionId: otherSess.id,
        createdById: '00000000-0000-0000-0000-000000000000',
        type: 'TABLE',
        title: 't_art_002_other_artifact',
        data: { columns: ['a'], rows: [[1]] } as never,
      },
    });

    // 当前用户在 orgId（非 otherOrg），不应能拿
    const res = await request(app.getHttpServer())
      .get(`/api/v1/agent/artifacts/${otherArt.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId);
    expect([403, 404]).toContain(res.status);
  });

  it('[AGENT-ART-003] GET /agent/artifacts 缺 sessionId 返 400', async () => {
    await request(app.getHttpServer())
      .get('/api/v1/agent/artifacts')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(400);
  });

  /**
   * file_save 路径遍历回归（PR #393 AI review hard_block）
   *
   * 攻击向量：LLM 在 TAOR 或客户端 POST /agent/tools/file_save {path:'../../../tmp/pwn'}
   * 写出 storage root。LocalStorageAdapter 现已在 upload() 加 path.resolve + 前缀断言。
   *
   * 测试覆盖：
   *   - path:'../../../etc/pwn' → 400 BadRequest（LocalStorageAdapter 阻断）
   *   - path:'/absolute/escape' → 400（path.resolve 结果不在 root 内）
   *   - path:'normal/safe.txt' → 200 OK（白名单路径）
   */
  it('[AGENT-ART-004] POST /agent/tools/file_save 路径遍历必拒（hard_block 回归）', async () => {
    const sess = await prisma.agentSession.create({
      data: { organizationId: orgId, createdById: adminUserId, title: 't_art_004_safefs' },
    });

    // file_save tool 把错误包成 {ok:false, errorMessage}，HTTP 201；改查 body
    // ../ 路径遍历
    const r1 = await request(app.getHttpServer())
      .post('/api/v1/agent/tools/file_save')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({
        sessionId: sess.id,
        input: { path: '../../../etc/pwn.txt', content: 'evil', mimeType: 'text/plain' },
      })
      .expect(201);
    expect(r1.body.data.ok).toBe(false);
    expect(r1.body.data.errorMessage).toMatch(/path escapes/i);

    // 绝对路径越狱
    const r2 = await request(app.getHttpServer())
      .post('/api/v1/agent/tools/file_save')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({
        sessionId: sess.id,
        input: { path: '/etc/shadow_pwn.txt', content: 'evil', mimeType: 'text/plain' },
      })
      .expect(201);
    expect(r2.body.data.ok).toBe(false);
    expect(r2.body.data.errorMessage).toMatch(/path escapes/i);

    // 正向白名单：合法相对路径应该通过（防止 guard 误杀）
    const r3 = await request(app.getHttpServer())
      .post('/api/v1/agent/tools/file_save')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({
        sessionId: sess.id,
        input: { path: `t_art_004/${Date.now()}.txt`, content: 'hello', mimeType: 'text/plain' },
      })
      .expect(201);
    expect(r3.body.data.ok).toBe(true);
    expect(r3.body.data.output.sizeBytes).toBe(5);
  });
});
