import { randomBytes } from 'node:crypto';
import type { FastifyInstance } from 'fastify';
import { requireJwtOrApiKey } from '../../lib/api-key-auth.js';
import { BadRequestError, NotFoundError } from '../../lib/errors.js';
import { CreateWebhookSubscriptionSchema, UpdateWebhookSubscriptionSchema } from './types.js';
import * as repo from './repository.js';
import { dispatchEvent } from './dispatcher.js';

export async function webhookRoutes(app: FastifyInstance) {
  async function requireWebhooksRead(req: import('fastify').FastifyRequest): Promise<string> {
    const access = await requireJwtOrApiKey(req, {
      jwtRoles: ['super_admin', 'admin'],
      apiKeyScopes: ['webhooks:read'],
      rateLimitKey: 'webhooks:read',
    });
    return access.productId;
  }

  async function requireWebhooksWrite(req: import('fastify').FastifyRequest): Promise<{
    actorId: string;
    productId: string;
  }> {
    const access = await requireJwtOrApiKey(req, {
      jwtRoles: ['super_admin', 'admin'],
      apiKeyScopes: ['webhooks:write'],
      rateLimitKey: 'webhooks:write',
    });
    return {
      actorId: access.actorId,
      productId: access.productId,
    };
  }

  // List webhook subscriptions
  app.get('/webhooks/subscriptions', async req => {
    const productId = await requireWebhooksRead(req);
    return repo.listSubscriptions(productId);
  });

  // Create a webhook subscription
  app.post('/webhooks/subscriptions', async req => {
    const access = await requireWebhooksWrite(req);
    const parsed = CreateWebhookSubscriptionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const secret = randomBytes(32).toString('hex');
    const sub = await repo.createSubscription(
      { ...parsed.data, productId: access.productId },
      secret,
      access.actorId
    );

    // Return secret only on creation (shown once)
    return { ...sub, secret };
  });

  // Get a specific subscription
  app.get('/webhooks/subscriptions/:id', async req => {
    const productId = await requireWebhooksRead(req);
    const { id } = req.params as { id: string };

    const sub = await repo.getSubscription(id, productId);
    if (!sub) throw new NotFoundError('Webhook subscription not found');

    // Redact secret (only shown on creation)
    return { ...sub, secret: `${sub.secret.slice(0, 8)}...` };
  });

  // Update a subscription
  app.patch('/webhooks/subscriptions/:id', async req => {
    const access = await requireWebhooksWrite(req);
    const { id } = req.params as { id: string };

    const parsed = UpdateWebhookSubscriptionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const updated = await repo.updateSubscription(id, access.productId, parsed.data);
    if (!updated) throw new NotFoundError('Webhook subscription not found');
    return { ...updated, secret: `${updated.secret.slice(0, 8)}...` };
  });

  // Delete a subscription
  app.delete('/webhooks/subscriptions/:id', async req => {
    const productId = (await requireWebhooksWrite(req)).productId;
    const { id } = req.params as { id: string };

    const deleted = await repo.deleteSubscription(id, productId);
    if (!deleted) throw new NotFoundError('Webhook subscription not found');
    return { success: true };
  });

  // List deliveries for a subscription
  app.get('/webhooks/subscriptions/:id/deliveries', async req => {
    await requireWebhooksRead(req);
    const { id } = req.params as { id: string };
    const query = req.query as Record<string, string>;
    return repo.listDeliveries(id, {
      limit: query.limit ? parseInt(query.limit, 10) : undefined,
    });
  });

  // Test delivery — send a test event to a specific subscription
  app.post('/webhooks/subscriptions/:id/test', async req => {
    const productId = (await requireWebhooksWrite(req)).productId;
    const { id } = req.params as { id: string };

    const sub = await repo.getSubscription(id, productId);
    if (!sub) throw new NotFoundError('Webhook subscription not found');

    await dispatchEvent(
      productId,
      'user.created',
      { test: true, message: 'This is a test webhook delivery' },
      req.log
    );

    return { success: true, message: 'Test event dispatched' };
  });

  // Rotate subscription secret
  app.post('/webhooks/subscriptions/:id/rotate-secret', async req => {
    const productId = (await requireWebhooksWrite(req)).productId;
    const { id } = req.params as { id: string };

    const sub = await repo.getSubscription(id, productId);
    if (!sub) throw new NotFoundError('Webhook subscription not found');

    const newSecret = randomBytes(32).toString('hex');
    sub.secret = newSecret;
    sub.updatedAt = new Date().toISOString();

    // upsert the full doc to persist the new secret (not exposed via UpdateSchema)
    const { getCollection } = await import('../../lib/datastore.js');
    await getCollection('webhook_subscriptions', '/productId').upsert(sub);

    return { secret: newSecret, message: 'Secret rotated. Update your webhook consumer.' };
  });
}
