/**
 * FFAI Agent — memories API 集成测试（Phase 2）
 *
 * 覆盖：
 *   - CRUD happy path
 *   - 校验：content required + > 4000 → 400；scope/projectId/personaId 一致性
 *   - 跨 org 拒绝 + 非 owner 拒绝
 *   - 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 - Memories 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-MEM-001] POST /agent/memories 默认 scope=GLOBAL + source=user', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'I prefer concise responses' })
      .expect(201);

    expect(res.body.data.content).toBe('I prefer concise responses');
    expect(res.body.data.scope).toBe('GLOBAL');
    expect(res.body.data.source).toBe('user');
    expect(res.body.data.organizationId).toBe(orgId);
    expect(res.body.data.createdById).toBe(adminUserId);
  });

  it('[AGENT-MEM-002] POST 校验：content required + > 4000 → 400', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: '' })
      .expect(400);
    await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'x'.repeat(4001) })
      .expect(400);
  });

  it('[AGENT-MEM-003] POST 校验：scope=GLOBAL 不能带 projectId/personaId → 400', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({
        content: 'leaked-scope',
        scope: 'GLOBAL',
        projectId: '00000000-0000-0000-0000-000000000000',
      })
      .expect(400);
  });

  it('[AGENT-MEM-004] GET /agent/memories 仅列自己；按 scope 过滤', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'mine-global' });
    // 别人的记忆（同 org）— 不应被自己看见
    await prisma.agentMemory.create({
      data: {
        organizationId: orgId,
        createdById: '00000000-0000-0000-0000-000000000000',
        content: 'someone-elses',
        scope: 'GLOBAL',
      },
    });
    const res = await request(app.getHttpServer())
      .get('/api/v1/agent/memories?scope=GLOBAL')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(200);
    const contents = res.body.data.items.map((m: { content: string }) => m.content);
    expect(contents).toContain('mine-global');
    expect(contents).not.toContain('someone-elses');
  });

  it('[AGENT-MEM-005] PATCH / DELETE 跨 org → 403', async () => {
    const otherOrg = await prisma.organization.create({
      data: { code: `T_MEM_${Date.now()}`, name: 'other-org-mem' },
    });
    const m = await prisma.agentMemory.create({
      data: {
        organizationId: otherOrg.id,
        createdById: '00000000-0000-0000-0000-000000000000',
        content: 'cross-org-memory',
        scope: 'GLOBAL',
      },
    });
    await request(app.getHttpServer())
      .patch(`/api/v1/agent/memories/${m.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'pwned' })
      .expect(403);
    await request(app.getHttpServer())
      .delete(`/api/v1/agent/memories/${m.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(403);
  });

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

  it('[AGENT-MEM-007] POST scope=PROJECT 传他人 projectId → 403（防挂自己 memory 进他人 project）', async () => {
    const otherOrg = await prisma.organization.create({
      data: { code: `T_MEM_X_${Date.now()}`, name: 'other-org-mem-x' },
    });
    const foreignProj = await prisma.agentProject.create({
      data: {
        organizationId: otherOrg.id,
        createdById: '00000000-0000-0000-0000-000000000000',
        name: 'foreign-project',
      },
    });
    await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'pwn-attempt', scope: 'PROJECT', projectId: foreignProj.id })
      .expect(403);
  });

  it('[AGENT-MEM-008] POST 接受 category 字段，默认 USER', async () => {
    const def = await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'default-cat' })
      .expect(201);
    expect(def.body.data.category).toBe('USER');
    expect(def.body.data.ownerScope).toBe('USER');

    const explicit = await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'explicit-fb', category: 'FEEDBACK' })
      .expect(201);
    expect(explicit.body.data.category).toBe('FEEDBACK');
  });

  it('[AGENT-MEM-009] GET ?category=FEEDBACK 仅返回该类别', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'fb-1', category: 'FEEDBACK' });
    await request(app.getHttpServer())
      .post('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'usr-1', category: 'USER' });
    const res = await request(app.getHttpServer())
      .get('/api/v1/agent/memories?category=FEEDBACK')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(200);
    const contents = res.body.data.items.map((m: { content: string }) => m.content);
    expect(contents).toContain('fb-1');
    expect(contents).not.toContain('usr-1');
  });

  it('[AGENT-MEM-010] PATCH 可改 category', async () => {
    const created = await prisma.agentMemory.create({
      data: {
        organizationId: orgId,
        createdById: adminUserId,
        content: 'reclassify-me',
        scope: 'GLOBAL',
        category: 'USER',
      },
    });
    const res = await request(app.getHttpServer())
      .patch(`/api/v1/agent/memories/${created.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ category: 'REFERENCE' })
      .expect(200);
    expect(res.body.data.category).toBe('REFERENCE');
  });

  it('[AGENT-MEM-011] GET 包含 ownerScope=ORG 的共享 memory（同 org 内任何用户可见）', async () => {
    // 直接以 ORG ownerScope 写入（admin 入口要 admin role，集成测试 itadmin 有 Administrator role 可走 POST 但这里直接写表更稳）
    await prisma.agentMemory.create({
      data: {
        organizationId: orgId,
        createdById: null,
        ownerScope: 'ORG',
        content: 'org-shared-policy',
        scope: 'GLOBAL',
        category: 'PROJECT',
        source: 'system',
      },
    });
    const res = await request(app.getHttpServer())
      .get('/api/v1/agent/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(200);
    const items: { content: string; ownerScope: string }[] = res.body.data.items;
    expect(items.some((m) => m.content === 'org-shared-policy' && m.ownerScope === 'ORG')).toBe(
      true,
    );
  });

  it('[AGENT-MEM-012] PATCH / DELETE 普通用户尝试改 ORG memory → 403', async () => {
    const orgMem = await prisma.agentMemory.create({
      data: {
        organizationId: orgId,
        createdById: null,
        ownerScope: 'ORG',
        content: 'cant-touch-this',
        scope: 'GLOBAL',
        category: 'PROJECT',
      },
    });
    await request(app.getHttpServer())
      .patch(`/api/v1/agent/memories/${orgMem.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'pwned' })
      .expect(403);
    await request(app.getHttpServer())
      .delete(`/api/v1/agent/memories/${orgMem.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(403);
  });

  it('[AGENT-MEM-013] POST /agent/admin/memories admin 创建 ORG memory（itadmin 有 Administrator 角色）', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/v1/agent/admin/memories')
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .send({ content: 'admin-created-org', category: 'PROJECT' })
      .expect(201);
    expect(res.body.data.ownerScope).toBe('ORG');
    expect(res.body.data.createdById).toBeNull();
    expect(res.body.data.organizationId).toBe(orgId);
    expect(res.body.data.source).toBe('system');
  });

  it('[AGENT-MEM-014] DELETE /agent/admin/memories/:id 仅对 ORG memory 生效（USER memory → 403）', async () => {
    const userMem = await prisma.agentMemory.create({
      data: {
        organizationId: orgId,
        createdById: adminUserId,
        ownerScope: 'USER',
        content: 'private',
        scope: 'GLOBAL',
        category: 'USER',
      },
    });
    await request(app.getHttpServer())
      .delete(`/api/v1/agent/admin/memories/${userMem.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(403);
    const orgMem = await prisma.agentMemory.create({
      data: {
        organizationId: orgId,
        createdById: null,
        ownerScope: 'ORG',
        content: 'shared',
        scope: 'GLOBAL',
        category: 'PROJECT',
      },
    });
    await request(app.getHttpServer())
      .delete(`/api/v1/agent/admin/memories/${orgMem.id}`)
      .set('Authorization', `Bearer ${adminToken}`)
      .set('X-Organization-Id', orgId)
      .expect(200);
    const stillThere = await prisma.agentMemory.findUnique({ where: { id: orgMem.id } });
    expect(stillThere).toBeNull();
  });
});
