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 { MessageService, type MessagePrincipal } from '../messaging/message.service';
import { ActorResolver } from '../identity/actor.resolver';
import { RuleEngine } from '../routing/rule-engine';
import { RouteSimulator } from '../routing/route-simulator';
import { RouteService } from '../routing/route.service';
import { AiJobService } from '../ai/ai.service';
import { AiBudgetGuard } from '../ai/ai-budget.guard';
import { OutboundService } from '../adapters/outbound.service';
import { IdempotencyService } from '../idempotency/idempotency.service';
import { CapabilityBroker } from '../capability/capability.broker';
import { CapabilityProviderRegistry } from '../capability/capability.registry';
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);
const outbound = () => new OutboundService(asService, new CapabilityBroker(makeFakePorts(), new CapabilityProviderRegistry()), new IdempotencyService(asService));
const routeSvc = () => new RouteService(asService, new RouteSimulator(asService, new RuleEngine()), outbound(), actors);

async function makeInteraction(text: string): Promise<{ interactionId: string; scopeId: 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()}`);
  const i = await prisma.iiosInteraction.findUniqueOrThrow({ where: { id: msg.id } });
  return { interactionId: msg.id, scopeId: i.scopeId };
}

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

describe('audit link (P9 — trace appears in DB at decision points)', () => {
  it('approving a route decision writes an audit link carrying the request trace id', async () => {
    const { interactionId, scopeId } = await makeInteraction('party at 9 PM');
    await prisma.iiosRouteBinding.create({
      data: { scopeId, originChannelType: 'WHATSAPP', originRef: 'g', destinationChannelType: 'PORTAL', destinationRef: 'adults', requiresReview: true, enabled: true },
    });
    const { decisions } = await routeSvc().simulate(interactionId, { channelType: 'WHATSAPP', ref: 'g' });
    const decisionId = decisions[0]!.id;

    await runWithTrace('trace-approve-1', async () => {
      await routeSvc().approve(decisionId, user);
    });

    const link = await prisma.iiosAuditLink.findFirstOrThrow({ where: { resourceId: decisionId } });
    expect(link.action).toBe('route.decision.approved');
    expect(link.resourceType).toBe('route_decision');
    expect(link.traceId).toBe('trace-approve-1');
    expect(link.actorRefId).toBeTruthy();
  });

  it('accepting an AI artifact writes an audit link for it', async () => {
    const { interactionId } = await makeInteraction('party at 9 PM');
    const { artifact } = await ai().runJob({ interactionId, jobType: 'CLASSIFY', principal: user });
    await runWithTrace('trace-accept-1', async () => {
      await ai().accept(artifact!.id, user);
    });
    const link = await prisma.iiosAuditLink.findFirstOrThrow({ where: { resourceId: artifact!.id, action: 'ai.artifact.accepted' } });
    expect(link.traceId).toBe('trace-accept-1');
  });
});
