import { createHash } from 'node:crypto';
import { Hono } from 'hono';
import { z } from 'zod';
import { verifyHubSpotV3Signature } from '../integrations/hubspot/signature';

const MAX_WEBHOOK_BYTES = 1_048_576;
const webhookEventSchema = z.object({
  eventId: z.union([z.string(), z.number()]).transform(String),
  subscriptionId: z.union([z.string(), z.number()]).transform(String),
  portalId: z.union([z.string(), z.number()]).transform(String),
  appId: z.union([z.string(), z.number()]).transform(String),
  occurredAt: z.number().int().nonnegative(),
  objectId: z.union([z.string(), z.number()]).transform(String),
  subscriptionType: z.string().min(1).optional(),
  eventType: z.string().min(1).optional(),
  attemptNumber: z.number().int().nonnegative(),
}).passthrough().refine(
  (event) => Boolean(event.subscriptionType || event.eventType),
  'A HubSpot event type is required.',
);
const webhookBatchSchema = z.array(webhookEventSchema).min(1).max(100);

export type VerifiedHubSpotWebhookEvent = z.infer<typeof webhookEventSchema>;

export interface HubSpotWebhookStore {
  enqueueVerifiedBatch(input: {
    events: readonly VerifiedHubSpotWebhookEvent[];
    rawBodyHash: string;
    receivedAt: Date;
  }): Promise<void>;
}

export interface HubSpotWebhookOptions {
  clientSecret: string;
  publicUri: string;
  appId: string;
  store: HubSpotWebhookStore;
  now?: () => Date;
}

export function createHubSpotWebhookRoutes(options: HubSpotWebhookOptions): Hono {
  const routes = new Hono();
  const now = options.now ?? (() => new Date());

  routes.post('/api/webhooks/hubspot', async (context) => {
    const contentLength = context.req.header('content-length');
    if (contentLength && Number(contentLength) > MAX_WEBHOOK_BYTES) {
      return context.json({ error: 'HubSpot webhook body is too large.' }, 413);
    }
    const rawBody = await context.req.text();
    if (Buffer.byteLength(rawBody, 'utf8') > MAX_WEBHOOK_BYTES) {
      return context.json({ error: 'HubSpot webhook body is too large.' }, 413);
    }
    const signature = verifyHubSpotV3Signature({
      clientSecret: options.clientSecret,
      method: context.req.method,
      publicUri: options.publicUri,
      rawBody,
      signature: context.req.header('x-hubspot-signature-v3'),
      timestamp: context.req.header('x-hubspot-request-timestamp'),
      now: now(),
    });
    if (!signature.valid) return context.json({ error: 'Invalid HubSpot webhook signature.' }, 401);
    let json: unknown;
    try {
      json = JSON.parse(rawBody);
    } catch {
      return context.json({ error: 'Invalid HubSpot webhook payload.' }, 400);
    }
    const parsed = webhookBatchSchema.safeParse(json);
    if (!parsed.success || parsed.data.some((event) => event.appId !== options.appId)) {
      return context.json({ error: 'Invalid HubSpot webhook payload.' }, 400);
    }
    await options.store.enqueueVerifiedBatch({
      events: parsed.data,
      rawBodyHash: createHash('sha256').update(rawBody, 'utf8').digest('hex'),
      receivedAt: now(),
    });
    return context.body(null, 204);
  });

  return routes;
}
