import { createHmac, timingSafeEqual } from 'crypto';
import type { IWebhookFunctions } from 'n8n-workflow';

/**
 * Verifies the GitHub webhook signature using HMAC-SHA256.
 *
 * GitHub sends a signature in the `X-Hub-Signature-256` header in the format:
 * `sha256=<HMAC hex digest>`
 *
 * This function computes the expected signature using the stored webhook secret
 * and compares it with the provided signature using a constant-time comparison.
 *
 * @returns true only if the stored secret is present and the signature matches
 */
export function verifySignature(this: IWebhookFunctions): boolean {
	// Get the secret from workflow static data (set during webhook creation)
	const webhookData = this.getWorkflowStaticData('node');
	const webhookSecret = webhookData.webhookSecret as string | undefined;

	// A webhook can sit without a stored secret until it is re-registered, and
	// rejecting is the safe answer for that window. Say so, or a stopped trigger
	// looks identical to a bad signature.
	if (!webhookSecret) {
		this.logger.warn(
			`Github Trigger "${this.getNode().name}" rejected a delivery because no webhook secret is stored. Activate the workflow again to re-register the webhook.`,
			{ workflowId: this.getWorkflow().id },
		);

		return false;
	}

	const req = this.getRequestObject();

	// Get the signature from GitHub's header
	const signature = req.header('x-hub-signature-256');
	if (!signature) {
		return false;
	}

	// Validate signature format (must start with "sha256=")
	if (!signature.startsWith('sha256=')) {
		return false;
	}

	// Extract just the hex digest part
	const providedSignature = signature.substring(7);

	try {
		// Get the raw request body
		if (!req.rawBody) {
			return false;
		}

		// Compute HMAC-SHA256 of the raw body using our secret
		const hmac = createHmac('sha256', webhookSecret);

		if (Buffer.isBuffer(req.rawBody)) {
			hmac.update(req.rawBody);
		} else {
			const rawBodyString =
				typeof req.rawBody === 'string' ? req.rawBody : JSON.stringify(req.rawBody);
			hmac.update(rawBodyString);
		}

		const computedSignature = hmac.digest('hex');

		const computedBuffer = Buffer.from(computedSignature, 'utf8');
		const providedBuffer = Buffer.from(providedSignature, 'utf8');

		// Buffers must be same length for timingSafeEqual
		if (computedBuffer.length !== providedBuffer.length) {
			return false;
		}

		return timingSafeEqual(computedBuffer, providedBuffer);
	} catch {
		return false;
	}
}
