import { randomUUID } from 'node:crypto';
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { PrismaClient } from '@prisma/client';
import { resetDb } from '../test-utils/reset-db';
import { makeFakePorts, makeFakeInference } from '@insignia/iios-testkit';
import { IIOS_EVENTS } from '@insignia/iios-contracts';
import { MessageService, type MessagePrincipal } from '../messaging/message.service';
import { ActorResolver } from '../identity/actor.resolver';
import { AiJobService } from '../ai/ai.service';
import { AiBudgetGuard } from '../ai/ai-budget.guard';
import { runWithTrace } from './trace-context';
import type { PrismaService } from '../prisma/prisma.service';

const url = process.env.DATABASE_URL ?? 'postgresql://iios:iios@localhost:5434/iios?schema=public';
const prisma = new PrismaClient({ datasources: { db: { url } } });
const asService = prisma as unknown as PrismaService;
const actors = new ActorResolver(asService);
const user: MessagePrincipal = { userId: 'alice', orgId: 'org_demo', appId: 'portal-demo' };

const ai = () => new AiJobService(asService, makeFakePorts(), makeFakeInference(), new AiBudgetGuard(asService), actors);

async function makeInteraction(text: string): Promise<string> {
  const m = new MessageService(asService, makeFakePorts(), actors);
  const { threadId } = await m.openThread(null, user);
  const msg = await m.send(threadId, user, { content: text }, `k-${randomUUID()}`);
  return msg.id;
}

beforeAll(async () => { await prisma.$connect(); });
afterAll(async () => { await prisma.$disconnect(); });
beforeEach(async () => { await resetDb(prisma); });

describe('event trace propagation (P9 observability)', () => {
  it('an emitted CloudEvent carries the request trace id in correlationId + traceparent', async () => {
    const interactionId = await makeInteraction('There is a party at 9 PM tonight!');
    await runWithTrace('trace-xyz-123', async () => {
      await ai().runJob({ interactionId, jobType: 'CLASSIFY', principal: user });
    });
    const evt = await prisma.iiosOutboxEvent.findFirstOrThrow({ where: { eventType: IIOS_EVENTS.aiArtifactProposed } });
    const ce = evt.cloudEvent as { traceparent?: string; insignia?: { correlationId?: string; idempotencyKey?: string } };
    expect(ce.insignia?.correlationId).toBe('trace-xyz-123');
    expect(ce.traceparent).toMatch(/^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/);
    expect(ce.insignia?.idempotencyKey).toBeTruthy(); // idempotency key still present
  });

  it('falls back to a generated trace id when no request context is active', async () => {
    const interactionId = await makeInteraction('hello');
    await ai().runJob({ interactionId, jobType: 'SUMMARIZE', principal: user }); // no runWithTrace
    const evt = await prisma.iiosOutboxEvent.findFirstOrThrow({ where: { eventType: IIOS_EVENTS.aiArtifactProposed } });
    const ce = evt.cloudEvent as { insignia?: { correlationId?: string } };
    expect(ce.insignia?.correlationId).toBeTruthy(); // a correlation id is always present
  });
});
