import prisma from "@shieldai/db";
import { createHmac, timingSafeEqual } from "crypto";
import { DataSource, WebhookEventType } from "@shieldai/types";

export class WebhookHandler {
  private secret: string;

  constructor(secret?: string) {
    if (secret) {
      this.secret = secret;
    } else if (process.env.DARKWATCH_WEBHOOK_SECRET) {
      this.secret = process.env.DARKWATCH_WEBHOOK_SECRET;
    } else {
      console.warn("[Webhook] DARKWATCH_WEBHOOK_SECRET not set — signature verification will fail");
      this.secret = "";
    }
  }

  /**
   * Verify HMAC signature of incoming webhook payload.
   */
  verifySignature(payload: string, signature: string | string[]): boolean {
    if (!this.secret) return false;
    if (!signature) return false;

    const sigArray = Array.isArray(signature) ? signature : [signature];
    const expected = this.computeSignature(payload);

    for (const sig of sigArray) {
      if (timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
        return true;
      }
    }

    return false;
  }

  /**
   * Process an incoming webhook event.
   * Validates, stores, and triggers appropriate action.
   */
  async processEvent(
    eventType: string,
    payload: Record<string, unknown>,
    source?: string,
    signature?: string
  ): Promise<{ eventId: string; scanTriggered: boolean }> {
    const payloadStr = JSON.stringify(payload);

    if (!signature || !this.verifySignature(payloadStr, signature)) {
      throw new Error("Webhook signature verification failed");
    }

    const eventTypeNormalized = this.normalizeEventType(eventType);

    const event = await prisma.webhookEvent.create({
      data: {
        eventType: eventTypeNormalized,
        payload: payloadStr,
        source,
        signature,
      },
    });

    let scanTriggered = false;

    if (eventTypeNormalized === WebhookEventType.SCAN_TRIGGER) {
      const userId = payload.userId as string | undefined;
      const source = (payload.dataSource as string) || undefined;

      if (userId) {
        scanTriggered = await this.triggerScanFromWebhook(event.id, userId, source);
      }
    }

    await prisma.webhookEvent.update({
      where: { id: event.id },
      data: {
        processed: true,
        processedAt: new Date(),
      },
    });

    return { eventId: event.id, scanTriggered };
  }

  /**
   * Trigger a scan job from a webhook event.
   */
  private async triggerScanFromWebhook(
    eventId: string,
    userId: string,
    dataSource?: string
  ): Promise<boolean> {
    try {
      const user = await prisma.user.findUnique({ where: { id: userId } });

      if (!user) {
        return false;
      }

      const job = await prisma.scanJob.create({
        data: {
          userId,
          status: "PENDING",
          source: (dataSource as DataSource) || undefined,
          scheduledBy: "webhook",
        },
      });

      await prisma.webhookEvent.update({
        where: { id: eventId },
        data: { scanJobId: job.id },
      });

      return true;
    } catch (err) {
      console.error(`[Webhook] Scan trigger failed for event ${eventId}:`, err);
      return false;
    }
  }

  /**
   * Get webhook event history.
   */
  async getEventHistory(limit = 50, offset = 0) {
    return prisma.webhookEvent.findMany({
      orderBy: { createdAt: "desc" },
      take: limit,
      skip: offset,
      include: { scanJob: true },
    });
  }

  /**
   * Get events for a specific user (via linked scan jobs).
   */
  async getUserEvents(userId: string, limit = 50, offset = 0) {
    return prisma.webhookEvent.findMany({
      where: {
        scanJob: { userId },
      },
      orderBy: { createdAt: "desc" },
      take: limit,
      skip: offset,
    });
  }

  /**
   * Process unprocessed webhook events (retry mechanism).
   */
  async processPendingEvents(): Promise<number> {
    const pending = await prisma.webhookEvent.findMany({
      where: {
        processed: false,
        eventType: WebhookEventType.SCAN_TRIGGER,
      },
      orderBy: { createdAt: "asc" },
      take: 50,
    });

    let processed = 0;

    for (const event of pending) {
      try {
        const payload = JSON.parse(event.payload) as Record<string, unknown>;
        const userId = payload.userId as string | undefined;

        if (userId) {
          const success = await this.triggerScanFromWebhook(
            event.id,
            userId,
            payload.dataSource as string | undefined
          );

          if (success) {
            await prisma.webhookEvent.update({
              where: { id: event.id },
              data: { processed: true, processedAt: new Date() },
            });
            processed++;
          }
        }
      } catch (err) {
        console.error(`[Webhook] Retry failed for event ${event.id}:`, err);
      }
    }

    return processed;
  }

  private computeSignature(payload: string): string {
    return createHmac("sha256", this.secret).update(payload).digest("hex");
  }

  private normalizeEventType(eventType: string): WebhookEventType {
    const upper = eventType.toUpperCase().replace(/\s+/g, "_");
    const validTypes: WebhookEventType[] = [WebhookEventType.SCAN_TRIGGER, WebhookEventType.BREACH_DETECTED, WebhookEventType.SUBSCRIPTION_CHANGE];
    if (!validTypes.includes(upper as WebhookEventType)) {
      throw new Error(`Unknown event type: ${eventType}`);
    }
    return upper as WebhookEventType;
  }
}
