import { Injectable, OnModuleInit } from '@nestjs/common';
import { CloudEvent, IIOS_EVENTS } from '@insignia/iios-contracts';
import { PrismaService } from '../prisma/prisma.service';
import { OutboxBus } from '../outbox/outbox.bus';
import { DlqService } from '../outbox/dlq.service';
import { ProjectionCursorService } from '../projection/projection-cursor.service';
import { AdapterRegistry } from './adapter.registry';
import { IngestService } from '../interactions/ingest.service';

/**
 * Async normalize→ingest for verified raw events (the "process separately" half
 * of the ACK-fast webhook). Idempotent via the processed-event ledger; reuses
 * the kernel IngestService. Carries the raw event's traceId into the interaction.
 */
@Injectable()
export class RawEventProjector implements OnModuleInit {
  private readonly consumer = 'raw-event-projector';

  constructor(
    private readonly prisma: PrismaService,
    private readonly bus: OutboxBus,
    private readonly dlq: DlqService,
    private readonly registry: AdapterRegistry,
    private readonly ingest: IngestService,
    private readonly cursor: ProjectionCursorService,
  ) {}

  onModuleInit(): void {
    this.dlq.registerHandler(this.consumer, (e) => this.onRawReceived(e));
    this.bus.on(IIOS_EVENTS.rawReceived, (p) => void this.onRawReceived(p as CloudEvent).catch((err) => this.dlq.onConsumerFailure(this.consumer, p as CloudEvent, err)));
  }

  async onRawReceived(event: CloudEvent): Promise<void> {
    if (!(await this.claim(event.id))) return; // duplicate → no apply, no cursor advance
    await this.applyRawReceived(event);
    await this.cursor.advance(this.consumer, event);
  }

  private async applyRawReceived(event: CloudEvent): Promise<void> {
    const { rawEventId } = event.data as { rawEventId: string };
    const raw = await this.prisma.iiosInboundRawEvent.findUnique({ where: { id: rawEventId } });
    if (!raw || raw.status !== 'RECEIVED') return;

    const reg = this.registry.get(raw.channelType);
    if (!reg) {
      await this.prisma.iiosInboundRawEvent.update({ where: { id: rawEventId }, data: { status: 'FAILED' } });
      return;
    }

    try {
      const req = reg.adapter.normalize(raw.payload, { scope: reg.config.scope });
      req.traceId = raw.traceId ?? undefined;
      const key = `${raw.channelType}:${raw.externalEventId ?? raw.id}`;
      const res = await this.ingest.ingest(req, key);
      await this.prisma.iiosInboundRawEvent.update({
        where: { id: rawEventId },
        data: { status: 'NORMALIZED', interactionId: res.interactionId },
      });
    } catch {
      await this.prisma.iiosInboundRawEvent.update({ where: { id: rawEventId }, data: { status: 'FAILED' } });
    }
  }

  private async claim(eventId: string): Promise<boolean> {
    const res = await this.prisma.iiosProcessedEvent.createMany({
      data: [{ consumerName: this.consumer, eventId }],
      skipDuplicates: true,
    });
    return res.count > 0;
  }
}
