import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { PrismaClient } from '@prisma/client';
import { resetDb } from '../test-utils/reset-db';
import { makeFakePorts, portalMessageBasic } from '@insignia/iios-testkit';
import { PolicyDeniedError } from '@insignia/iios-contracts';
import { IngestService } from './ingest.service';
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 } } });

async function clean(): Promise<void> {
  await resetDb(prisma);
}

const svc = (ports = makeFakePorts()) => new IngestService(prisma as unknown as PrismaService, ports);

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

describe('IngestService — kernel gates', () => {
  it('idempotent: same idempotency key twice → exactly 1 interaction + 1 outbox event', async () => {
    const s = svc();
    const first = await s.ingest(portalMessageBasic, 'idem-1');
    const second = await s.ingest(portalMessageBasic, 'idem-1');

    expect(second.interactionId).toBe(first.interactionId);
    expect(second.state).toBe('DUPLICATE');
    expect(await prisma.iiosInteraction.count()).toBe(1);
    expect(await prisma.iiosOutboxEvent.count()).toBe(1);
    expect(await prisma.iiosMessagePart.count()).toBe(1);
  });

  it('fail-closed: OPA deny → PolicyDeniedError and nothing written', async () => {
    const ports = makeFakePorts();
    ports.opa.deny('blocked by policy');
    const s = svc(ports);

    await expect(s.ingest(portalMessageBasic, 'idem-2')).rejects.toBeInstanceOf(PolicyDeniedError);
    expect(await prisma.iiosInteraction.count()).toBe(0);
    expect(await prisma.iiosOutboxEvent.count()).toBe(0);
    expect(await prisma.iiosSourceHandle.count()).toBe(0);
  });

  it('unresolved handle: MDM ambiguous → SourceHandle stays UNVERIFIED, null canonical id', async () => {
    const ports = makeFakePorts();
    ports.mdm.ambiguous(0.42);
    const s = svc(ports);

    await s.ingest(portalMessageBasic, 'idem-3');
    const handle = await prisma.iiosSourceHandle.findFirstOrThrow();
    expect(handle.canonicalEntityId).toBeNull();
    expect(handle.verificationState).toBe('UNVERIFIED');
    expect(handle.confidence).toBe(0.42);
  });

  it('writes the normalized CloudEvent into the outbox', async () => {
    const s = svc();
    await s.ingest(portalMessageBasic, 'idem-4');
    const event = await prisma.iiosOutboxEvent.findFirstOrThrow();
    expect(event.eventType).toBe('com.insignia.iios.interaction.normalized.v1');
    expect(event.status).toBe('PENDING');
  });
});
