/**
 * AI Usage Token API Integration Tests (7 cases)
 *
 * 基于 docs/modules/it-operations/ai-usage/09-test-scenarios.md
 */
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { PrismaClient } from '@prisma/client';
import { createTestApp } from '../../helpers/app.helper';
import {
  setupAiUsageTestContext,
  AiUsageTestContext,
  cleanupAiUsageData,
  disconnect,
} from './_helpers';

describe('AI Usage Token API', () => {
  let app: INestApplication;
  let prisma: PrismaClient;
  let ctx: AiUsageTestContext;

  beforeAll(async () => {
    app = await createTestApp();
    prisma = new PrismaClient();
  });

  beforeEach(async () => {
    await cleanupAiUsageData();
    ctx = await setupAiUsageTestContext(app);
  });

  afterAll(async () => {
    await cleanupAiUsageData();
    await prisma.$disconnect();
    await disconnect();
    await app.close();
  });

  it('1. 员工生成 token：响应含 token 字段（一次性），DB 落 bcrypt hash', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/me/tokens')
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .send({ name: 'test-my-mac' });
    expect(res.status).toBeLessThan(300);
    const body = res.body.data ?? res.body;
    expect(body.token).toMatch(/^ffai_/);
    expect(body.prefix).toBe(body.token.slice(0, 12));
    expect(body.token.length).toBeGreaterThan(30);
    const dbRow = await prisma.aiUsageToken.findUnique({ where: { id: body.id } });
    expect(dbRow?.tokenHash).not.toBe(body.token);
    expect(dbRow?.tokenHash.startsWith('$2')).toBe(true); // bcrypt format
  });

  it('2. 第二次 GET token 列表：响应不含明文', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/ai-usage/me/tokens')
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .send({ name: 'test-list-1' });
    const res = await request(app.getHttpServer())
      .get('/api/v1/ai-usage/me/tokens')
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId);
    const items = res.body.data?.items ?? res.body.items;
    expect(items.length).toBeGreaterThan(0);
    for (const item of items) {
      expect(item.token).toBeUndefined();
      expect(item.tokenHash).toBeUndefined();
      expect(item.prefix).toMatch(/^ffai_/);
    }
  });

  it('3. 员工撤销自己的 token：DB revokedAt 写入', async () => {
    const createRes = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/me/tokens')
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .send({ name: 'test-revoke-self' });
    const id = (createRes.body.data ?? createRes.body).id;
    await request(app.getHttpServer())
      .delete(`/api/v1/ai-usage/me/tokens/${id}`)
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .expect(204);
    const row = await prisma.aiUsageToken.findUnique({ where: { id } });
    expect(row?.revokedAt).not.toBeNull();
  });

  it('4. 员工撤销别人的 token → 404（DataScope 过滤）', async () => {
    const otherTokenRes = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/me/tokens')
      .set('Authorization', `Bearer ${ctx.adminToken}`)
      .set('X-Organization-Id', ctx.organizationId) // admin 的 token
      .send({ name: 'test-other-user-token' });
    const otherId = (otherTokenRes.body.data ?? otherTokenRes.body).id;
    const res = await request(app.getHttpServer())
      .delete(`/api/v1/ai-usage/me/tokens/${otherId}`)
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId);
    expect(res.status).toBe(404);
  });

  it('5. Admin 代撤员工 token → 成功', async () => {
    const tokenRes = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/me/tokens')
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .send({ name: 'test-admin-revokes-this' });
    const id = (tokenRes.body.data ?? tokenRes.body).id;
    const revokeRes = await request(app.getHttpServer())
      .post(`/api/v1/ai-usage/tokens/${id}/revoke`)
      .set('Authorization', `Bearer ${ctx.adminToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .send({ reason: 'test-employee-left' });
    expect(revokeRes.status).toBe(200);
    const row = await prisma.aiUsageToken.findUnique({ where: { id } });
    expect(row?.revokedAt).not.toBeNull();
    expect(row?.revokedById).toBe(ctx.adminUser.id);
  });

  it('6. 撤销后立即用该 token 上报 → 401', async () => {
    const tokenRes = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/me/tokens')
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .send({ name: 'test-revoke-then-use' });
    const data = tokenRes.body.data ?? tokenRes.body;
    const plainToken = data.token;
    const tokenId = data.id;
    await request(app.getHttpServer())
      .delete(`/api/v1/ai-usage/me/tokens/${tokenId}`)
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .expect(204);
    const ingestRes = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set('Authorization', `Bearer ${plainToken}`)
      .set('X-Device-Id', 'test-device-revoked')
      .set('X-Hostname', 'test-host')
      .set('X-Os-Platform', 'LINUX')
      .send({ events: [] });
    expect(ingestRes.status).toBe(401);
  });

  it('7. 同员工生成 5 个 token：全部 ACTIVE', async () => {
    for (let i = 0; i < 5; i++) {
      await request(app.getHttpServer())
        .post('/api/v1/ai-usage/me/tokens')
        .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
        .send({ name: `test-multi-${i}` });
    }
    const rows = await prisma.aiUsageToken.findMany({
      where: { userId: ctx.memberUser.id, revokedAt: null },
    });
    expect(rows.length).toBe(5);
  });

  it('8. 跨 org 隔离：admin 看不到 / 撤不了另一个 org 的 token', async () => {
    // 在 ctx.org 下生成一个 token（属于 memberUser）
    const createRes = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/me/tokens')
      .set('Authorization', `Bearer ${ctx.memberToken}`)
      .set('X-Organization-Id', ctx.organizationId)
      .send({ name: 'cross-org-target' });
    const targetTokenId = (createRes.body.data ?? createRes.body).id;

    // 建另一个 org + 该 org 的 admin（重用 ctx.adminToken 但切到 otherOrg header）
    // 这里简化：直接在 prisma 层造一个不带 ai-usage 权限的环境，admin 用 ctx.adminToken
    // 但 X-Organization-Id 设为一个不存在的 org → currentOrganizationId 不会被注入 → 403
    const fakeOrgId = '00000000-0000-0000-0000-000000000000';

    // (a) 列 token：admin 带其他 org header
    const listRes = await request(app.getHttpServer())
      .get('/api/v1/ai-usage/tokens?status=all')
      .set('Authorization', `Bearer ${ctx.adminToken}`)
      .set('X-Organization-Id', fakeOrgId);
    // 要么 200 但 items 不含 target（org 不匹配），要么 403（org 上下文校验失败）
    if (listRes.status === 200) {
      const items = (listRes.body.data ?? listRes.body).items ?? [];
      expect(items.find((i: any) => i.id === targetTokenId)).toBeUndefined();
    } else {
      expect(listRes.status).toBe(403);
    }

    // (b) admin 跨 org 撤销 → 必须 404 / 403（不应成功）
    const revokeRes = await request(app.getHttpServer())
      .post(`/api/v1/ai-usage/tokens/${targetTokenId}/revoke`)
      .set('Authorization', `Bearer ${ctx.adminToken}`)
      .set('X-Organization-Id', fakeOrgId)
      .send({ reason: 'cross-org attempt' });
    expect([403, 404]).toContain(revokeRes.status);

    // 确认 token 在 DB 中仍未被撤销
    const stillActive = await prisma.aiUsageToken.findUnique({ where: { id: targetTokenId } });
    expect(stillActive?.revokedAt).toBeNull();
  });
});
