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 { RouteService } from './route.service';

/**
 * Auto-routing (P6). Reacts to `interaction.normalized` — i.e. messages that
 * arrived through a channel (adapter ingest). Native DMs have no origin channel,
 * so they are never routed. For each matching binding it simulates a decision;
 * only **AUTOMATIC + ALLOW** executes (to the P5 sandbox). MANUAL/REVIEW/DENY
 * stay pending for an admin; SIMULATION_ONLY never executes. Idempotent.
 */
@Injectable()
export class RouteProjector implements OnModuleInit {
  private readonly consumer = 'route-projector';

  constructor(
    private readonly prisma: PrismaService,
    private readonly bus: OutboxBus,
    private readonly dlq: DlqService,
    private readonly routes: RouteService,
    private readonly cursor: ProjectionCursorService,
  ) {}

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

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

  private async applyNormalized(event: CloudEvent): Promise<void> {
    const { interactionId } = event.data as { interactionId: string };
    const interaction = await this.prisma.iiosInteraction.findUnique({
      where: { id: interactionId },
      include: { channel: true },
    });
    if (!interaction?.channel) return; // no origin channel → not routable (e.g. native DM)

    const { decisions } = await this.routes.simulate(interactionId, {
      channelType: interaction.channel.channelType,
      ref: interaction.channel.externalRef ?? undefined,
    });

    for (const d of decisions) {
      if (d.decisionState !== 'ALLOW') continue;
      const binding = await this.prisma.iiosRouteBinding.findUnique({ where: { id: d.routeBindingId } });
      if (binding?.mode === 'AUTOMATIC') await this.routes.execute(d, binding);
    }
  }

  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;
  }
}
