import { Injectable, NotFoundException } from '@nestjs/common';

import type {
  WhmcsEventsAckRequest,
  WhmcsEventsAckResponse,
  WhmcsEventsResponse,
} from '@hexahost/contracts';
import type { Prisma } from '@hexahost/database';

import { PrismaService } from '../../prisma/prisma.service';

type InstallationRef = {
  id: string;
};

@Injectable()
export class WhmcsEventsService {
  constructor(private readonly prisma: PrismaService) {}

  async listEvents(
    installation: InstallationRef,
    cursor?: string,
    limit = 50,
  ): Promise<WhmcsEventsResponse> {
    const take = Math.min(Math.max(limit, 1), 100);

    const events = await this.prisma.integrationEvent.findMany({
      where: {
        installationId: installation.id,
        status: 'PENDING',
        ...(cursor
          ? {
              createdAt: {
                gt: await this.cursorCreatedAt(installation.id, cursor),
              },
            }
          : {}),
      },
      orderBy: { createdAt: 'asc' },
      take: take + 1,
    });

    const page = events.slice(0, take);
    const nextCursor =
      events.length > take ? (page[page.length - 1]?.id ?? null) : null;

    return {
      events: page.map((event) => ({
        id: event.id,
        eventType: event.eventType,
        payload: event.payload as Record<string, unknown>,
        status: event.status,
        createdAt: event.createdAt.toISOString(),
      })),
      nextCursor,
    };
  }

  async acknowledgeEvents(
    installation: InstallationRef,
    input: WhmcsEventsAckRequest,
  ): Promise<WhmcsEventsAckResponse> {
    const events = await this.prisma.integrationEvent.findMany({
      where: {
        installationId: installation.id,
        id: { in: input.eventIds },
      },
    });

    if (events.length !== input.eventIds.length) {
      throw new NotFoundException('One or more events were not found');
    }

    const now = new Date();

    if (input.deadLetter) {
      await this.prisma.integrationEvent.updateMany({
        where: {
          installationId: installation.id,
          id: { in: input.eventIds },
        },
        data: {
          status: 'DEAD_LETTER',
          deadLetteredAt: now,
          deadLetterReason: input.deadLetterReason ?? 'Marked dead letter by WHMCS',
        },
      });
    } else {
      await this.prisma.integrationEvent.updateMany({
        where: {
          installationId: installation.id,
          id: { in: input.eventIds },
        },
        data: {
          status: 'ACKNOWLEDGED',
          acknowledgedAt: now,
        },
      });

      const lastId = input.eventIds[input.eventIds.length - 1];
      if (lastId) {
        await this.prisma.integrationSyncCursor.upsert({
          where: { installationId: installation.id },
          create: {
            installationId: installation.id,
            eventsCursor: lastId,
          },
          update: {
            eventsCursor: lastId,
          },
        });
      }
    }

    return { acknowledged: events.length };
  }

  async emitEvent(
    installationId: string,
    eventType: string,
    payload: Record<string, unknown>,
    dedupeKey: string,
  ): Promise<void> {
    try {
      await this.prisma.integrationEvent.create({
        data: {
          installationId,
          eventType,
          payload: payload as Prisma.InputJsonValue,
          dedupeKey,
        },
      });
    } catch {
      // Duplicate dedupe key — event already emitted
    }
  }

  private async cursorCreatedAt(
    installationId: string,
    cursor: string,
  ): Promise<Date> {
    const event = await this.prisma.integrationEvent.findFirst({
      where: { id: cursor, installationId },
    });

    return event?.createdAt ?? new Date(0);
  }
}
