import 'reflect-metadata';
import dotenv from 'dotenv';
import path from 'path';
import fs from 'fs';
import { createServer as createHttpServer } from 'http';
import { createServer as createHttpsServer } from 'https';
import { resolveCertificateDirectory, resolveEnvFilePath } from './runtime-paths';

// Load .env from project root (one level up from server/)
dotenv.config({ path: resolveEnvFilePath() });

import { initDatabase, destroyDatabase } from './db/database';
import { deleteStaleJoinRequests } from './cqrs';
import { createApp } from './app';
import {
  ensureVariablesConfig,
  getServerHost,
  getVariablesConfigPath,
  getServerPort,
  getServerProtocol,
  ServerHttpProtocol
} from './config/variables';
import { setupWebSocket } from './websocket';

function formatHostForUrl(host: string): string {
  if (host.startsWith('[') || !host.includes(':')) {
    return host;
  }

  return `[${host}]`;
}

function getDisplayHost(serverHost: string | undefined): string {
  if (!serverHost || serverHost === '0.0.0.0' || serverHost === '::') {
    return 'localhost';
  }

  return serverHost;
}

function buildServer(app: ReturnType<typeof createApp>, serverProtocol: ServerHttpProtocol) {
  if (serverProtocol === 'https') {
    const certDir = resolveCertificateDirectory();
    const certFile = path.join(certDir, 'localhost.crt');
    const keyFile = path.join(certDir, 'localhost.key');

    if (!fs.existsSync(certFile) || !fs.existsSync(keyFile)) {
      console.error(`HTTPS is enabled but certs were not found in ${certDir}`);
      console.error('Add localhost.crt and localhost.key there, or switch serverProtocol to "http".');
      process.exit(1);
    }

    return createHttpsServer(
      { cert: fs.readFileSync(certFile), key: fs.readFileSync(keyFile) },
      app
    );
  }

  return createHttpServer(app);
}

let listeningServer: ReturnType<typeof buildServer> | null = null;
let staleJoinRequestInterval: ReturnType<typeof setInterval> | null = null;

async function bootstrap(): Promise<void> {
  const variablesConfig = ensureVariablesConfig();
  const serverProtocol = getServerProtocol();
  const serverPort = getServerPort();
  const serverHost = getServerHost();
  const bindHostLabel = serverHost || 'default interface';

  console.log('[Config] Variables loaded from:', getVariablesConfigPath());

  if (
    variablesConfig.serverProtocol !== serverProtocol
    || variablesConfig.serverPort !== serverPort
  ) {
    console.log(`[Config] Server runtime override active: protocol=${serverProtocol}, host=${bindHostLabel}, port=${serverPort}`);
  } else {
    console.log(`[Config] Server runtime config: protocol=${serverProtocol}, host=${bindHostLabel}, port=${serverPort}`);
  }

  if (!variablesConfig.klipyApiKey) {
    console.log('[KLIPY] API key not configured. GIF search is disabled.');
  }

  await initDatabase();

  const app = createApp();
  const server = buildServer(app, serverProtocol);

  listeningServer = server;

  setupWebSocket(server);

  // Periodically clean up stale join requests (older than 24 h)
  staleJoinRequestInterval = setInterval(() => {
    deleteStaleJoinRequests(24 * 60 * 60 * 1000)
      .catch(err => console.error('Failed to clean up stale join requests:', err));
  }, 60 * 1000);

  const onListening = () => {
    const displayHost = formatHostForUrl(getDisplayHost(serverHost));
    const wsProto = serverProtocol === 'https' ? 'wss' : 'ws';
    const localHostNames = [
      'localhost',
      '127.0.0.1',
      '::1'
    ];

    console.log(`MetoYou signaling server running on port ${serverPort} (${serverProtocol.toUpperCase()}, bind host=${bindHostLabel})`);
    console.log(`   REST API: ${serverProtocol}://${displayHost}:${serverPort}/api`);
    console.log(`   WebSocket: ${wsProto}://${displayHost}:${serverPort}`);

    if (serverProtocol === 'https' && serverHost && !localHostNames.includes(serverHost)) {
      console.warn('[Config] HTTPS certificates must match the configured serverHost/server IP.');
    }
  };

  if (serverHost) {
    server.listen(serverPort, serverHost, onListening);
  } else {
    server.listen(serverPort, onListening);
  }
}

let shuttingDown = false;

async function gracefulShutdown(signal: string): Promise<void> {
  if (shuttingDown)
    return;

  shuttingDown = true;

  if (staleJoinRequestInterval) {
    clearInterval(staleJoinRequestInterval);
    staleJoinRequestInterval = null;
  }

  console.log(`\n[Shutdown] ${signal} received - closing database...`);

  if (listeningServer?.listening) {
    try {
      await new Promise<void>((resolve) => {
        listeningServer?.close(() => resolve());
      });
    } catch (err) {
      console.error('[Shutdown] Error closing server:', err);
    }
  }

  listeningServer = null;

  try {
    await destroyDatabase();
  } catch (err) {
    console.error('[Shutdown] Error closing database:', err);
  }

  process.exit(0);
}

process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));

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