/**
 * Custom server entry point.
 *
 * Boots Next.js, attaches Socket.io to the same HTTP server, and in
 * development mode also registers BullMQ recurring jobs and starts the
 * workers inline (so you don't need the separate crm-worker container).
 *
 * In production the workers run in the crm-worker container (Dockerfile.worker
 * → dist/worker.js) and this file only handles Next.js + Socket.io.
 */

// Must be first: sets globalThis.AsyncLocalStorage before Next's app-render
// modules load. See src/server-runtime-preamble.ts.
import './server-runtime-preamble';

import { createServer, type Server as HttpServer } from 'node:http';

import next from 'next';

import { initSocketServer, closeSocketServer } from '@/lib/socket/server';
import { logger } from '@/lib/logger';
import { env } from '@/lib/env';
import { redis } from '@/lib/redis';

const dev = env.NODE_ENV !== 'production';

async function gracefulShutdown(signal: string, httpServer: HttpServer): Promise<void> {
  logger.info({ signal }, 'Shutdown signal received; closing connections');

  // Order matters: close Socket.io first so it stops accepting new
  // sockets and emits disconnect events while the HTTP server is still
  // up to flush them. `httpServer.close` only stops new connections;
  // it waits for keep-alive HTTP and long-poll websockets to drain on
  // their own, so without an explicit io.close() upfront the polls hold
  // the server past the compose stop_grace_period and the process gets
  // SIGKILL'd mid-frame.
  await closeSocketServer().catch((err) => logger.warn({ err }, 'closeSocketServer error'));

  // Then drain the HTTP layer.
  await new Promise<void>((resolve) => {
    httpServer.close((err) => {
      if (err) logger.warn({ err }, 'httpServer.close emitted error');
      resolve();
    });
    // Hard timeout - 25s leaves headroom under a 30s compose grace
    // period before SIGKILL would arrive anyway.
    setTimeout(() => resolve(), 25_000).unref();
  });

  try {
    redis.disconnect();
  } catch (err) {
    logger.warn({ err }, 'redis.disconnect error');
  }

  logger.info({ signal }, 'Shutdown complete');
}

async function main(): Promise<void> {
  const app = next({ dev, port: env.PORT });
  const handle = app.getRequestHandler();

  await app.prepare();

  const httpServer = createServer((req, res) => {
    handle(req, res);
  });

  // Attach Socket.io to the HTTP server (uses Redis adapter for pub/sub)
  initSocketServer(httpServer);
  logger.info('Socket.io initialized');

  // In development, run BullMQ workers inline so a single `pnpm dev` is enough
  if (dev) {
    const { registerRecurringJobs } = await import('@/lib/queue/scheduler');
    const { emailWorker } = await import('@/lib/queue/workers/email');
    const { documentsWorker } = await import('@/lib/queue/workers/documents');
    const { notificationsWorker } = await import('@/lib/queue/workers/notifications');
    const { importWorker } = await import('@/lib/queue/workers/import');
    const { exportWorker } = await import('@/lib/queue/workers/export');
    const { aiWorker } = await import('@/lib/queue/workers/ai');
    const { bulkWorker } = await import('@/lib/queue/workers/bulk');
    const { maintenanceWorker } = await import('@/lib/queue/workers/maintenance');
    const { reportsWorker } = await import('@/lib/queue/workers/reports');
    const { webhooksWorker } = await import('@/lib/queue/workers/webhooks');

    await registerRecurringJobs();
    logger.info('BullMQ recurring jobs registered (dev mode)');

    // Keep a reference so workers aren't GC'd
    void [
      emailWorker,
      documentsWorker,
      notificationsWorker,
      importWorker,
      exportWorker,
      aiWorker,
      bulkWorker,
      maintenanceWorker,
      reportsWorker,
      webhooksWorker,
    ];
  }

  httpServer.listen(env.PORT, () => {
    logger.info({ port: env.PORT, env: env.NODE_ENV }, 'CRM server listening');
  });

  // Graceful stop on container restart / deploy. Without this, every
  // `docker compose up -d` rolling restart drops in-flight uploads, EOI
  // generation, Documenso requests, and Socket.io frames mid-statement.
  // Match docker-compose `stop_grace_period: 30s` (or longer) so the
  // 25s drain inside gracefulShutdown can complete before SIGKILL.
  for (const sig of ['SIGTERM', 'SIGINT'] as const) {
    process.once(sig, () => {
      void gracefulShutdown(sig, httpServer).finally(() => process.exit(0));
    });
  }
}

main().catch((err) => {
  logger.error(err, 'Failed to start server');
  process.exit(1);
});
