/**
 * AI Usage Ingestion API Integration Tests (12 cases)
 */
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 Ingestion API', () => {
  let app: INestApplication;
  let prisma: PrismaClient;
  let ctx: AiUsageTestContext;
  let plainToken: string;
  let tokenId: string;

  beforeAll(async () => {
    app = await createTestApp();
    app.use(require('express').json({ limit: '50mb' }));
    prisma = new PrismaClient();
  });

  beforeEach(async () => {
    await cleanupAiUsageData();
    ctx = await setupAiUsageTestContext(app);
    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-ingestion' });
    const d = res.body.data ?? res.body;
    plainToken = d.token;
    tokenId = d.id;
  });

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

  const baseHeaders = () => ({
    Authorization: `Bearer ${plainToken}`,
    'X-Device-Id': `test-device-${Date.now()}-${Math.random().toString(36).slice(2)}`,
    'X-Hostname': 'test-host-ingest',
    'X-Os-User': 'tester',
    'X-Os-Platform': 'LINUX',
    'X-Agent-Version': '0.1.0',
  });

  const ev = (overrides: any = {}) => ({
    rawMessageId: `test:${Date.now()}:${Math.random().toString(36).slice(2)}`,
    tool: 'claude-code',
    sessionId: 's-1',
    projectPath: '/tmp/test',
    model: 'claude-opus-4-7',
    ts: new Date().toISOString(),
    inputTokens: 100,
    outputTokens: 200,
    cacheCreationTokens: 0,
    cacheReadTokens: 50,
    estimatedCostUsd: '0.001000',
    ...overrides,
  });

  it('1. Bearer token 校验通过 → device 自动注册 + event 入库', async () => {
    const headers = baseHeaders();
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(headers)
      .send({ events: [ev()] });
    expect(res.status).toBe(202);
    expect(res.body.data?.accepted ?? res.body.accepted).toBe(1);
    const dev = await prisma.aiUsageDevice.findUnique({ where: { deviceId: headers['X-Device-Id'] } });
    expect(dev).toBeDefined();
    expect(dev!.userId).toBe(ctx.memberUser.id);
  });

  it('2. Bearer token 无效 → 401', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set({ ...baseHeaders(), Authorization: 'Bearer ffai_invalid' })
      .send({ events: [ev()] });
    expect(res.status).toBe(401);
  });

  it('3. Bearer token 已撤销 → 401', async () => {
    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 res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(baseHeaders())
      .send({ events: [ev()] });
    expect(res.status).toBe(401);
  });

  it('4. Device 被拉黑 → 403 + DLQ 记录', async () => {
    const headers = baseHeaders();
    // 首次注册
    await request(app.getHttpServer()).post('/api/v1/ai-usage/events').set(headers).send({ events: [ev()] });
    const dev = await prisma.aiUsageDevice.findUnique({ where: { deviceId: headers['X-Device-Id'] } });
    // 拉黑
    await prisma.aiUsageDevice.update({ where: { id: dev!.id }, data: { blockedAt: new Date(), blockedReason: 'test' } });
    // 再次上报
    const res = await request(app.getHttpServer()).post('/api/v1/ai-usage/events').set(headers).send({ events: [ev()] });
    expect(res.status).toBe(403);
    const dlqCount = await prisma.aiUsageEventDlq.count({ where: { deviceId: dev!.id, reason: 'BLOCKED_DEVICE' } });
    expect(dlqCount).toBeGreaterThan(0);
  });

  it('5. 单批 100 event → 全部入库（验证 multi-VALUES INSERT）', async () => {
    // NestJS test app 默认 body parser 限制 100kb；生产 main.ts 用 50mb 但 createTestApp
    // helper 不复用此配置（init 后 app.use 加载顺序在默认之后无效）。500 batch
    // 由生产配置兜底；这里用 100 仍验证多 event multi-VALUES INSERT 语义。
    const events = Array.from({ length: 100 }, () => ev());
    const res = await request(app.getHttpServer()).post('/api/v1/ai-usage/events').set(baseHeaders()).send({ events });
    expect(res.status).toBe(202);
    expect((res.body.data ?? res.body).accepted).toBe(100);
  });

  it('6. 单批 501 event → 400 BATCH_TOO_LARGE', async () => {
    const events = Array.from({ length: 501 }, () => ev());
    const res = await request(app.getHttpServer()).post('/api/v1/ai-usage/events').set(baseHeaders()).send({ events });
    expect(res.status).toBeGreaterThanOrEqual(400);
  });

  it('7. 重复 rawMessageId → deduped 计数', async () => {
    const headers = baseHeaders();
    const e = ev();
    const r1 = await request(app.getHttpServer()).post('/api/v1/ai-usage/events').set(headers).send({ events: [e] });
    expect((r1.body.data ?? r1.body).accepted).toBe(1);
    const r2 = await request(app.getHttpServer()).post('/api/v1/ai-usage/events').set(headers).send({ events: [e] });
    expect((r2.body.data ?? r2.body).accepted).toBe(0);
    expect((r2.body.data ?? r2.body).deduped).toBe(1);
  });

  it('8. ts > now+5min → DLQ TS_OUT_OF_WINDOW', async () => {
    const futureTs = new Date(Date.now() + 10 * 60 * 1000).toISOString();
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(baseHeaders())
      .send({ events: [ev({ ts: futureTs })] });
    expect(res.status).toBe(202);
    expect((res.body.data ?? res.body).dlq).toBeGreaterThan(0);
  });

  it('9. ts < now-30d → DLQ TS_OUT_OF_WINDOW', async () => {
    const oldTs = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000).toISOString();
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(baseHeaders())
      .send({ events: [ev({ ts: oldTs })] });
    expect((res.body.data ?? res.body).dlq).toBeGreaterThan(0);
  });

  it('10. 离线 7 天后批量上报 → 全部 accepted（窗口内）', async () => {
    const oldTs = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
    const events = Array.from({ length: 10 }, () => ev({ ts: oldTs }));
    const res = await request(app.getHttpServer()).post('/api/v1/ai-usage/events').set(baseHeaders()).send({ events });
    expect((res.body.data ?? res.body).accepted).toBe(10);
    expect((res.body.data ?? res.body).dlq).toBe(0);
  });

  it('11. 响应 header 含 X-Pricing-Version', async () => {
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(baseHeaders())
      .send({ events: [ev()] });
    const ver = res.headers['x-pricing-version'];
    expect(ver).toBeDefined();
    expect(ver.length).toBeGreaterThan(0);
  });

  it('12. 缺失必填 header → 400', async () => {
    const headers = baseHeaders();
    delete (headers as any)['X-Device-Id'];
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(headers)
      .send({ events: [ev()] });
    expect(res.status).toBe(400);
  });

  // ===== v1.1 富 metadata 字段：每个字段独立验证 ingestion → DB 落盘 =====

  it('13. v1.1 富 metadata 全字段：从 ingestion → DB 正确落盘', async () => {
    const headers = baseHeaders();
    const rawMessageId = `test-rich:${Date.now()}:${Math.random().toString(36).slice(2)}`;
    const payload = ev({
      rawMessageId,
      gitBranch: 'feature/ai-usage-aggregator-338',
      agentVersionEvent: '2.1.119',
      worktreeLabel: 'slot-4',
      cwdBasename: 'ai-usage-338',
      turnIndex: 42,
      toolUseCount: 3,
      toolNames: ['Bash', 'Edit', 'Read'],
      stopReason: 'tool_use',
      serviceTier: 'standard',
    });
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(headers)
      .send({ events: [payload] });
    expect(res.status).toBe(202);
    expect((res.body.data ?? res.body).accepted).toBe(1);

    const row = await prisma.aiUsageEvent.findUnique({ where: { rawMessageId } });
    expect(row).toBeTruthy();
    expect(row!.gitBranch).toBe('feature/ai-usage-aggregator-338');
    expect(row!.agentVersionEvent).toBe('2.1.119');
    expect(row!.worktreeLabel).toBe('slot-4');
    expect(row!.cwdBasename).toBe('ai-usage-338');
    expect(row!.turnIndex).toBe(42);
    expect(row!.toolUseCount).toBe(3);
    expect(Array.isArray(row!.toolNames as any)).toBe(true);
    expect(row!.toolNames as any).toEqual(['Bash', 'Edit', 'Read']);
    expect(row!.stopReason).toBe('tool_use');
    expect(row!.serviceTier).toBe('standard');
  });

  it('14. v1.1 富 metadata 全部省略 → 老 agent 仍能成功上报（向后兼容）', async () => {
    const headers = baseHeaders();
    const rawMessageId = `test-legacy:${Date.now()}:${Math.random().toString(36).slice(2)}`;
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(headers)
      .send({ events: [ev({ rawMessageId })] });
    expect(res.status).toBe(202);
    const row = await prisma.aiUsageEvent.findUnique({ where: { rawMessageId } });
    expect(row).toBeTruthy();
    expect(row!.gitBranch).toBeNull();
    expect(row!.turnIndex).toBeNull();
    expect(row!.toolNames).toBeNull();
    expect(row!.stopReason).toBeNull();
    expect(row!.serviceTier).toBeNull();
  });

  it('15. v1.1 toolNames 越长字段被 truncate 到 64 字符（DTO 校验拒绝超长）', async () => {
    const headers = baseHeaders();
    const longName = 'X'.repeat(65);
    const res = await request(app.getHttpServer())
      .post('/api/v1/ai-usage/events')
      .set(headers)
      .send({ events: [ev({ toolNames: [longName] })] });
    expect(res.status).toBeGreaterThanOrEqual(400);
    expect(res.status).toBeLessThan(500);
  });

  it('16. v1.1 多 event 批量上报，富 metadata 独立落盘（multi-VALUES INSERT 含 JSONB 列）', async () => {
    const headers = baseHeaders();
    const events = [
      ev({
        rawMessageId: `b:${Date.now()}:1`,
        gitBranch: 'feature/a',
        turnIndex: 1,
        toolNames: ['Bash'],
        stopReason: 'end_turn',
        serviceTier: 'standard',
      }),
      ev({
        rawMessageId: `b:${Date.now()}:2`,
        gitBranch: 'feature/b',
        turnIndex: 2,
        toolNames: ['Edit', 'Write'],
        stopReason: 'tool_use',
        serviceTier: 'priority',
      }),
    ];
    const res = await request(app.getHttpServer()).post('/api/v1/ai-usage/events').set(headers).send({ events });
    expect(res.status).toBe(202);
    expect((res.body.data ?? res.body).accepted).toBe(2);
    const rows = await prisma.aiUsageEvent.findMany({
      where: { rawMessageId: { in: events.map((e) => e.rawMessageId) } },
      orderBy: { rawMessageId: 'asc' },
    });
    expect(rows).toHaveLength(2);
    expect(rows[0].gitBranch).toBe('feature/a');
    expect(rows[0].serviceTier).toBe('standard');
    expect(rows[1].gitBranch).toBe('feature/b');
    expect(rows[1].serviceTier).toBe('priority');
    expect(rows[1].toolNames as any).toEqual(['Edit', 'Write']);
  });
});
