import { DateTime } from "luxon";
import http from "node:http";
import os from "node:os";
import { z } from "zod/v4";
import { createApp } from "./app";
import { ENV } from "./config";
import { tryConnectToDatabase } from "./config/database";
import { registerHealthRoutes } from "./health";
import { listRoutes, logger } from "./lib";
import { initModules } from "./lib/modules";
import { registerModules } from "./register-modules";

const API_BASE_PATH = "/api/v1";

z.config(z.locales.es());

// Guardamos información del estado del servidor
export const currentState = {
  launchedAt: DateTime.now(),
  appPath: process.cwd(),
  host: ENV.HOST,
  port: ENV.PORT,
  environment: ENV.NODE_ENV,
  connections: {} as Record<string, unknown>,
};

// ─────────────────────────────────────────────────────────────────────────────
// 🔒 Bandera de apagado y promesa para idempotencia
let isShuttingDown = false;
let shutdownPromise: Promise<void> | null = null;
// 🔒 Bandera de readiness (se actualiza durante arranque y apagado)
let isReady = false;

// ─────────────────────────────────────────────────────────────────────────────
// Manejo de cierre forzado del servidor (graceful shutdown + sockets)
const serverStop = (server: http.Server) => {
  const forceTimeout = 30000;

  // Devolvemos siempre la misma promesa para evitar dobles cierres
  if (shutdownPromise) return shutdownPromise;

  shutdownPromise = new Promise<void>((resolve, reject) => {
    logger.warn("⚡️ Shutting down server");

    // Tras el timeout, destruimos sockets vivos y resolvemos
    const killer = setTimeout(() => {
      try {
        const sockets = Object.values(currentState.connections) as any[];
        let destroyed = 0;
        for (const s of sockets) {
          try {
            // @ts-ignore - algunos runtimes exponen destroy() / end()
            if (s.destroy && !s.destroyed) {
              s.destroy();
              destroyed++;
            } else if (s.end) {
              s.end();
            }
          } catch {
            /* noop */
          }
        }
        logger.error(`Force timeout reached. Destroyed ${destroyed} remaining connection(s).`);
      } finally {
        resolve();
      }
    }, forceTimeout);
    // Evita mantener el event-loop vivo por el timeout
    killer.unref();

    // Intentamos cerrar el servidor de forma limpia
    server.close((err) => {
      if (err) {
        logger.error("Error while closing server", { err, label: "serverStop.close" });
        return reject(err);
      }
      logger.info("Closed out remaining connections.");
      logger.info("❎ Bye!");
      clearTimeout(killer);
      resolve();
    });
  });

  return shutdownPromise;
};

// ─────────────────────────────────────────────────────────────────────────────
// Manejo de errores al iniciar el servidor
const serverError = (error: NodeJS.ErrnoException) => {
  logger.error("⛔️ Server wasn't able to start properly.", {
    label: "serverError0",
    error,
    host: ENV.HOST,
    port: ENV.PORT,
  });

  if (error.code === "EADDRINUSE") {
    logger.error(`Port ${ENV.PORT} already in use`, { error, label: "serverError1" });
  } else {
    logger.error(error.message, { error, label: "serverError2" });
  }

  // Fallo crítico de arranque → salida con código 1
  process.exit(1);
};

// Almacena en "connections" cada nueva conexión
const serverConnection = (conn: any) => {
  const key = `${conn.remoteAddress}:${conn.remotePort}`;
  currentState.connections[key] = conn;

  conn.on("close", () => {
    delete currentState.connections[key];
  });
};

// Cargar paquetes de la aplicación: customers, invoices, routes...
registerModules();

const app = createApp();

// ➕ Rutas de salud disponibles desde el inicio del proceso
registerHealthRoutes(app, () => ({ ready: isReady }));

// Crea el servidor HTTP
const server = http.createServer(app);

// ⚙️ Ajustes explícitos de timeouts HTTP (evita cuelgues en keep-alive)
server.keepAliveTimeout = 70000; // el socket puede quedar vivo 70s
server.headersTimeout = 75000; // debe ser > keepAliveTimeout

// ─────────────────────────────────────────────────────────────────────────────
// 🛎️ Señales del SO (registradas antes de listen) + apagado controlado
// En los manejadores de señales/errores, marcar no-ready al iniciar shutdown
const handleSignal = (signal: NodeJS.Signals) => async () => {
  if (isShuttingDown) {
    logger.warn(`Shutdown already in progress. Ignoring signal ${signal}.`);
    return;
  }
  isShuttingDown = true;
  // ⬇️ el servidor deja de estar listo inmediatamente
  isReady = false;

  logger.warn(`Received ${signal}. Starting graceful shutdown...`);
  try {
    await serverStop(server);
    process.exit(0);
  } catch (err) {
    logger.error("Error during shutdown, forcing exit(1)", { err, label: "handleSignal" });
    process.exit(1);
  }
};

process.on("SIGINT", handleSignal("SIGINT"));
process.on("SIGTERM", handleSignal("SIGTERM"));

// ─────────────────────────────────────────────────────────────────────────────
// Eventos del servidor
server
  .once("listening", () => {
    logger.info(`HTTP server is listening on port ${currentState.port}`);
  })
  .on("close", () =>
    logger.info(`Shutdown at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`)
  )
  .on("connection", serverConnection)
  .on("error", serverError);

// ─────────────────────────────────────────────────────────────────────────────
// Manejo de promesas no capturadas: apagado controlado
// ... unhandledRejection/uncaughtException: también set isReady = false;
process.on("unhandledRejection", async (reason: any, promise: Promise<any>) => {
  logger.error("❌ Unhandled rejection", {
    label: "unhandledRejection",
    reason,
    promise: String(promise),
  });
  if (!isShuttingDown) {
    isShuttingDown = true;
    isReady = false;
    try {
      await serverStop(server);
    } finally {
      process.exit(1);
    }
  }
});

process.on("uncaughtException", async (error: Error) => {
  logger.error(`❌ Uncaught exception: ${error.message}`, {
    stack: error.stack,
    label: "uncaughtException",
  });
  if (!isShuttingDown) {
    isShuttingDown = true;
    isReady = false;
    try {
      await serverStop(server);
    } finally {
      process.exit(1);
    }
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// Arranca el servidor si la conexión a la base de datos va bien
(async () => {
  try {
    const now = DateTime.now();
    logger.info(`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`);
    logger.info(`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`);
    logger.info(`Environment: ${currentState.environment}`);
    logger.info(`Process PID: ${process.pid}`);

    const database = await tryConnectToDatabase();

    // Lógica de inicialización de DB, si procede:
    // initStructure(sequelizeConn.connection);
    // insertUsers();

    await initModules({ app, database, baseRoutePath: API_BASE_PATH, logger });

    // ✅ El servidor ya está listo para recibir tráfico
    isReady = true;
    logger.info("✅ Server is READY (readiness=true)");
    logger.info(`startup_duration_ms=${DateTime.now().diff(currentState.launchedAt).toMillis()}`);

    server.listen(currentState.port, () => {
      server.emit("listening");

      const networkInterfaces = os.networkInterfaces();
      const addresses: string[] = [];

      // Obtiene todas las direcciones IPv4
      for (const interfaceName in networkInterfaces) {
        const networkInterface = networkInterfaces[interfaceName];
        if (networkInterface) {
          for (const iface of networkInterface) {
            if (iface.family === "IPv4" && !iface.internal) {
              addresses.push(iface.address);
            }
          }
        }
      }

      // URLs de acceso útiles
      logger.info(`⚡️ Server accessible at: http://localhost:${currentState.port}`);
      logger.info(`⚡️ Server accessible at: http://${currentState.host}:${currentState.port}`);
      for (const address of addresses) {
        logger.info(`⚡️ Server accessible at: http://${address}:${currentState.port}`);
      }

      logger.info(`Server started at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`);
      logger.info(`Server PID: ${process.pid}`);
      logger.info(
        `Server launched in: ${DateTime.now().diff(currentState.launchedAt).toMillis()} ms`
      );
      logger.info(`Server path: ${currentState.appPath}`);
      logger.info(`Server environment: ${currentState.environment}`);
      logger.info("To shut down your server, press <CTRL> + C at any time");

      // En entornos de desarrollo puede ser muy verboso
      logger.info(JSON.stringify(listRoutes((app as any)._router, API_BASE_PATH), null, 3));
    });
  } catch (error) {
    // Arranque fallido → readiness sigue false
    isReady = false;
    serverError(error as NodeJS.ErrnoException);
  }
})();
