import type { FastifyInstance } from 'fastify';
import type { PlatformEventName } from '@bytelyst/events';
import { BadRequestError, ForbiddenError, NotFoundError } from '../../lib/errors.js';
import { bus } from '../../lib/event-bus.js';
import { listEvents } from '../../lib/event-store-bridge.js';
import {
  CreateEventSubscriptionSchema,
  UpdateEventSubscriptionSchema,
  ListEventSubscriptionsQuerySchema,
  ListDlqQuerySchema,
} from './types.js';
import * as repo from './repository.js';

function requireAdmin(req: { jwtPayload?: { sub?: string; role?: string; productId?: string } }): {
  userId: string;
  productId: string;
} {
  const payload = req.jwtPayload;
  if (!payload?.sub) throw new ForbiddenError('Authentication required');
  if (!payload.role || !['super_admin', 'admin'].includes(payload.role)) {
    throw new ForbiddenError('Admin access required');
  }
  return {
    userId: payload.sub,
    productId: payload.productId ?? process.env.DEFAULT_PRODUCT_ID ?? 'lysnrai',
  };
}

export async function eventSubscriptionRoutes(app: FastifyInstance) {
  // ── Subscription CRUD ─────────────────────────────────────

  app.get('/event-subscriptions', async req => {
    const access = requireAdmin(req);
    const parsed = ListEventSubscriptionsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    return repo.list(access.productId, parsed.data);
  });

  app.post('/event-subscriptions', async req => {
    const access = requireAdmin(req);
    const parsed = CreateEventSubscriptionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    return repo.create(access.productId, parsed.data, access.userId);
  });

  app.get('/event-subscriptions/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const sub = await repo.getById(id, access.productId);
    if (!sub) throw new NotFoundError(`Event subscription '${id}' not found`);
    return sub;
  });

  app.patch('/event-subscriptions/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = UpdateEventSubscriptionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const updated = await repo.update(id, access.productId, parsed.data);
    if (!updated) throw new NotFoundError(`Event subscription '${id}' not found`);
    return updated;
  });

  app.delete('/event-subscriptions/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const deleted = await repo.remove(id, access.productId);
    if (!deleted) throw new NotFoundError(`Event subscription '${id}' not found`);
    return { deleted: true };
  });

  // ── Dead-Letter Queue ─────────────────────────────────────

  app.get('/event-dlq', async req => {
    const access = requireAdmin(req);
    const parsed = ListDlqQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    return repo.listDlq(access.productId, parsed.data);
  });

  app.post('/event-dlq/:id/retry', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const entry = await repo.getDlqEntry(id, access.productId);
    if (!entry) throw new NotFoundError(`DLQ entry '${id}' not found`);

    // Re-emit via bus — cast topic since stored events are already validated
    await bus.emit(
      entry.topic as PlatformEventName,
      entry.payload as Parameters<typeof bus.emit>[1]
    );

    await repo.deleteDlqEntry(id, access.productId);
    return { retried: true, topic: entry.topic };
  });

  app.delete('/event-dlq/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const deleted = await repo.deleteDlqEntry(id, access.productId);
    if (!deleted) throw new NotFoundError(`DLQ entry '${id}' not found`);
    return { deleted: true };
  });

  app.post('/event-dlq/purge', async req => {
    const access = requireAdmin(req);
    const count = await repo.purgeDlq(access.productId);
    return { purged: count };
  });

  // ── Event Replay ──────────────────────────────────────────

  app.post('/events/replay', async req => {
    const access = requireAdmin(req);
    const body = req.body as { topic?: string; since?: string; until?: string };
    if (!body.topic) throw new BadRequestError('topic is required');

    const events = await listEvents(access.productId, {
      topic: body.topic,
      since: body.since,
      until: body.until,
      limit: 100,
    });

    let replayed = 0;
    for (const evt of events) {
      await bus.emit(evt.topic as PlatformEventName, evt.payload as Parameters<typeof bus.emit>[1]);
      replayed++;
    }

    return { replayed, topic: body.topic };
  });
}
