import { createHmac, timingSafeEqual } from "crypto";
import { AlchemyWebhookPayload } from "../types";
import { WebhookRequestError } from "./AlchemyWebhookPayloadValidator";

export class AlchemyWebhookVerificationService {
  constructor(private signingKeys: Record<string, string> | string) {}

  verify(
    payload: unknown,
    headers: Record<string, string | string[] | undefined>,
    rawBody?: string
  ): AlchemyWebhookPayload {
    if (!rawBody) {
      throw new WebhookRequestError("Missing raw webhook body", 400);
    }

    if (!payload || typeof payload !== "object") {
      throw new WebhookRequestError("Missing webhook payload", 400);
    }

    const signature = this.getHeaderValue(headers["x-alchemy-signature"]);
    if (!signature) {
      throw new WebhookRequestError("Missing x-alchemy-signature header", 401);
    }

    // Extract webhookId from payload to determine which signing key to use
    const webhookPayload = payload as Partial<AlchemyWebhookPayload>;
    const webhookId = webhookPayload.webhookId;

    if (!webhookId) {
      throw new WebhookRequestError("Missing webhookId in payload", 400);
    }

    const signingKey = this.getSigningKey(webhookId);
    if (!signingKey) {
      throw new WebhookRequestError(`No signing key configured for webhook ${webhookId}`, 401);
    }

    const expectedSignature = createHmac("sha256", signingKey)
      .update(rawBody, "utf8")
      .digest("hex");

    const provided = Buffer.from(signature.trim().toLowerCase(), "utf8");
    const expected = Buffer.from(expectedSignature, "utf8");

    if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) {
      throw new WebhookRequestError("Invalid Alchemy webhook signature", 401);
    }

    return payload as AlchemyWebhookPayload;
  }

  private getSigningKey(webhookId: string): string | undefined {
    if (typeof this.signingKeys === 'string') {
      // Backward compatibility: single signing key
      return this.signingKeys;
    }

    return this.signingKeys[webhookId];
  }

  private getHeaderValue(header: string | string[] | undefined): string | undefined {
    return Array.isArray(header) ? header[0] : header;
  }
}