import { Job, Worker, type ConnectionOptions } from 'bullmq';

import { validateConfig } from '@hexahost/config';
import { prisma } from '@hexahost/database';

import { processNotificationJob } from './handlers/notifications';
import { processLifecycleJob } from './handlers/server-lifecycle';
import { processProvisionServerJob } from './handlers/server-provisioning';
import { startHealthServer } from './health';
import { logger } from './logger';
import { closeRedisConnections } from './redis';
import {
  QUEUE_NOTIFICATIONS,
  QUEUE_SERVER_LIFECYCLE,
  QUEUE_SERVER_PROVISIONING,
  WORKER_QUEUES,
  type WorkerQueueName,
} from './queues';

function createQueueWorker(
  queueName: WorkerQueueName,
  connection: ConnectionOptions,
): Worker {
  return new Worker(
    queueName,
    async (job: Job) => {
      logger.info(
        { queue: queueName, jobId: job.id, jobName: job.name },
        'Processing job',
      );

      if (queueName === QUEUE_NOTIFICATIONS) {
        return processNotificationJob(job);
      }

      if (queueName === QUEUE_SERVER_PROVISIONING) {
        return processProvisionServerJob(job);
      }

      if (queueName === QUEUE_SERVER_LIFECYCLE) {
        return processLifecycleJob(job);
      }

      logger.warn({ queue: queueName, jobName: job.name }, 'Unhandled job');
    },
    { connection },
  );
}

async function bootstrap(): Promise<void> {
  const config = validateConfig();

  const healthServer = startHealthServer();
  const connection: ConnectionOptions = {
    url: config.REDIS_URL,
    maxRetriesPerRequest: null,
  };

  const workers = WORKER_QUEUES.map((queueName) =>
    createQueueWorker(queueName, connection),
  );

  for (const worker of workers) {
    worker.on('completed', (job: Job | undefined) => {
      logger.info({ queue: worker.name, jobId: job?.id }, 'Job completed');
    });

    worker.on('failed', (job: Job | undefined, error: Error) => {
      logger.error(
        { queue: worker.name, jobId: job?.id, err: error },
        'Job failed',
      );
    });
  }

  logger.info({ queues: WORKER_QUEUES }, 'Worker started');

  const shutdown = async (signal: string): Promise<void> => {
    logger.info({ signal }, 'Shutting down worker');

    await Promise.all(workers.map((worker) => worker.close()));
    await closeRedisConnections();
    await prisma.$disconnect();

    healthServer.close(() => {
      process.exit(0);
    });
  };

  process.on('SIGINT', () => {
    void shutdown('SIGINT');
  });

  process.on('SIGTERM', () => {
    void shutdown('SIGTERM');
  });
}

void bootstrap();
