/**
 * FFAI Agent — projects API 集成测试（Phase 2）
 *
 * 覆盖：
 *   - CRUD happy path（POST/GET/PATCH/DELETE）
 *   - 校验：name required + name 长度 > 120 返 400
 *   - 跨 org 访问拒绝（INV-1）+ 非 owner 拒绝
 *   - DELETE 单事务：session.projectId 置 null 后才删项目本体
 *   - JWT 缺失 401
 */
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 - Projects API (Phase 2)', () => {
  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-PROJ-001] POST /agent/projects 创建项目；返回完整字段', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/v1/agent/projects')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'My Project', icon: '📁', color: 'blue', instructions: 'be concise' })
      .expect(201);

    const p = res.body.data;
    expect(p.name).toBe('My Project');
    expect(p.icon).toBe('📁');
    expect(p.color).toBe('blue');
    expect(p.instructions).toBe('be concise');
    expect(p.organizationId).toBe(orgId);
    expect(p.createdById).toBe(adminUserId);
  });

  it('[AGENT-PROJ-002] POST /agent/projects 校验：name required + name 长度 > 120 → 400', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/agent/projects')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: '' })
      .expect(400);

    await request(app.getHttpServer())
      .post('/api/v1/agent/projects')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'x'.repeat(121) })
      .expect(400);
  });

  it('[AGENT-PROJ-003] GET /agent/projects 列表按 updatedAt desc 倒序', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/agent/projects')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'p-old' });
    await new Promise((r) => setTimeout(r, 10));
    await request(app.getHttpServer())
      .post('/api/v1/agent/projects')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'p-new' });

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

    expect(res.body.data.items.length).toBeGreaterThanOrEqual(2);
    expect(res.body.data.items[0].name).toBe('p-new');
  });

  it('[AGENT-PROJ-004] PATCH /agent/projects/:id 部分字段更新', async () => {
    const created = await request(app.getHttpServer())
      .post('/api/v1/agent/projects')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'orig' });

    const updated = await request(app.getHttpServer())
      .patch(`/api/v1/agent/projects/${created.body.data.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'renamed', color: 'red' })
      .expect(200);

    expect(updated.body.data.name).toBe('renamed');
    expect(updated.body.data.color).toBe('red');
  });

  it('[AGENT-PROJ-005] DELETE /agent/projects/:id 单事务：内含 session.projectId 置 null，再删项目', async () => {
    const proj = await request(app.getHttpServer())
      .post('/api/v1/agent/projects')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'will-delete' });

    const sess = await prisma.agentSession.create({
      data: {
        organizationId: orgId,
        createdById: adminUserId,
        title: 's-in-proj',
        projectId: proj.body.data.id,
      },
    });

    await request(app.getHttpServer())
      .delete(`/api/v1/agent/projects/${proj.body.data.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(200);

    const projRow = await prisma.agentProject.findUnique({ where: { id: proj.body.data.id } });
    expect(projRow).toBeNull();
    const sessRow = await prisma.agentSession.findUnique({ where: { id: sess.id } });
    expect(sessRow?.projectId).toBeNull();
  });

  it('[AGENT-PROJ-006] PATCH / DELETE 跨 org 必拒（INV-1） → 403', async () => {
    const otherOrg = await prisma.organization.create({
      data: { code: `T_PROJ_${Date.now()}`, name: 'other-org-INV1' },
    });
    const proj = await prisma.agentProject.create({
      data: {
        organizationId: otherOrg.id,
        createdById: '00000000-0000-0000-0000-000000000000',
        name: 'cross-org',
      },
    });
    await request(app.getHttpServer())
      .patch(`/api/v1/agent/projects/${proj.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'pwned' })
      .expect(403);
    await request(app.getHttpServer())
      .delete(`/api/v1/agent/projects/${proj.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(403);
  });

  it('[AGENT-PROJ-007] 无 JWT → 401', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/agent/projects')
      .set('X-Organization-Id', orgId)
      .send({ name: 'no-auth' })
      .expect(401);
  });

  it('[AGENT-PROJ-008] PATCH /agent/projects/:id 非法 UUID → 400 Invalid id format (全局 filter 兜底)', async () => {
    // 验证 AllExceptionsFilter 在非 meeting-attendance 模块（走 NestJS 标准 throw 路径）
    // 对 Prisma P2023 的兜底：非法 UUID → 400 而非 500。
    // meeting-attendance 走 @Res() 自己的 helper，不经过本 filter。
    const res = await request(app.getHttpServer())
      .patch('/api/v1/agent/projects/not-a-valid-uuid')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ name: 'updated' })
      .expect(400);

    expect(res.body.success).toBe(false);
    expect(res.body.error?.code).toBe('INVALID_ID_FORMAT');
    expect(res.body.error?.message).toMatch(/invalid id format/i);
  });
});
