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

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

import { processDnsSyncTick } from './handlers/dns-sync';
import { processIdleShutdownTick, processUsageMeteringTick } from './handlers/idle-billing';
import { processNodeHealthTick } from './handlers/node-health';
import { processNotificationJob } from './handlers/notifications';
import { processAddonJob } from './handlers/server-addons';
import { processBackupJob, processScheduledBackupsTick } from './handlers/server-backups';
import { processLifecycleJob } from './handlers/server-lifecycle';
import { processProvisionServerJob } from './handlers/server-provisioning';
import { processStartQueueTick } from './handlers/start-queue';
import { processWhmcsReconciliationTick, processWhmcsUsagePeriodTick } from './handlers/whmcs-sync';
import { startHealthServer } from './health';
import { logger } from './logger';
import { closeRedisConnections } from './redis';
import {
  QUEUE_NOTIFICATIONS,
  QUEUE_SERVER_ADDONS,
  QUEUE_SERVER_BACKUPS,
  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_BACKUPS) {
        return processBackupJob(job);
      }

      if (queueName === QUEUE_SERVER_ADDONS) {
        return processAddonJob(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 scheduleTimer = setInterval(() => {
    void processScheduledBackupsTick().catch((error: Error) => {
      logger.error({ err: error }, 'Scheduled backup tick failed');
    });
    void processStartQueueTick().catch((error: Error) => {
      logger.error({ err: error }, 'Start queue tick failed');
    });
    void processNodeHealthTick().catch((error: Error) => {
      logger.error({ err: error }, 'Node health tick failed');
    });
    void processIdleShutdownTick().catch((error: Error) => {
      logger.error({ err: error }, 'Idle shutdown tick failed');
    });
    void processUsageMeteringTick().catch((error: Error) => {
      logger.error({ err: error }, 'Usage metering tick failed');
    });
    void processDnsSyncTick().catch((error: Error) => {
      logger.error({ err: error }, 'DNS sync tick failed');
    });
    void processWhmcsReconciliationTick().catch((error: Error) => {
      logger.error({ err: error }, 'WHMCS reconciliation tick failed');
    });
    void processWhmcsUsagePeriodTick().catch((error: Error) => {
      logger.error({ err: error }, 'WHMCS usage period tick failed');
    });
  }, 60_000);

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

    clearInterval(scheduleTimer);

    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();
