import 'dotenv/config';
import 'reflect-metadata';
import os from 'os';
import http from 'http';
import { Socket } from 'net';
import { redisClient, closeRedisClient } from '../infrastructure/cache/redis-client';
import { initializeDatabase, closeDatabase } from '../infrastructure/persistence/data-source';
import { createWebSocketServer } from '../infrastructure/websocket/websocket-server';
import { TransactionNotificationService } from '../application/services/transaction-notification.service';
import { OneSignalPushGateway } from '../infrastructure/push/onesignal-push-gateway';
import { createRepositories } from './factories/repositories.factory';
import { createServices } from './factories/services.factory';
import { createUseCases } from './factories/use-cases.factory';
import { createControllers } from './factories/controllers.factory';
import { createMiddlewares } from './factories/middleware.factory';
import { createRoutes } from './factories/routes.factory';
import { createApp } from './factories/app.factory';
import { env } from './config/env';

const getLanAddresses = (): string[] => {
  const interfaces = os.networkInterfaces();
  const addresses: string[] = [];

  for (const name of Object.keys(interfaces)) {
    const iface = interfaces[name];
    if (!iface) continue;
    for (const entry of iface) {
      if (entry.family === 'IPv4' && !entry.internal) {
        addresses.push(entry.address);
      }
    }
  }

  return addresses;
};

const startServer = async (): Promise<void> => {
  await initializeDatabase();

  const repositories = createRepositories();
  const services = createServices(redisClient);
  const useCases = createUseCases(repositories, services);
  const controllers = createControllers(useCases);
  const middlewares = createMiddlewares(services.logger, redisClient);
  const routes = createRoutes(controllers, middlewares);
  const app = createApp(services.logger, redisClient, routes, middlewares);

  const httpServer = http.createServer(app);

  const sockets = new Set<Socket>();
  httpServer.on('connection', (socket) => {
    sockets.add(socket);
    socket.on('close', () => sockets.delete(socket));
  });

  let closeWebSocketServer: (() => Promise<void>) | undefined;

  if (env.WEBSOCKET_ENABLED === 'true') {
    const websocketResult = createWebSocketServer({
      httpServer,
      path: env.WEBSOCKET_PATH,
      logger: services.logger,
    });

    closeWebSocketServer = websocketResult.close;

    const pushGateway =
      env.ONESIGNAL_APP_ID && env.ONESIGNAL_REST_API_KEY
        ? new OneSignalPushGateway(
            env.ONESIGNAL_APP_ID,
            env.ONESIGNAL_REST_API_KEY,
            repositories.deviceRepository,
            services.logger,
          )
        : undefined;

    if (pushGateway) {
      services.logger.info('[OneSignal] Push gateway enabled', {
        appId: env.ONESIGNAL_APP_ID,
      });
    } else {
      services.logger.info('[OneSignal] Push gateway disabled: missing app ID or REST API key');
    }

    const transactionNotificationService = new TransactionNotificationService(
      services.eventPublisher,
      repositories.addressRepository,
      repositories.walletRepository,
      repositories.deviceRepository,
      repositories.tokenRepository,
      repositories.networkRepository,
      websocketResult.gateway,
      pushGateway,
    );

    transactionNotificationService.start();
  }

  const server = httpServer.listen(env.PORT, env.HOST, () => {
    const lanAddresses = getLanAddresses();
    services.logger.info(`Server running on ${env.HOST}:${env.PORT}`, { port: env.PORT, host: env.HOST, env: env.NODE_ENV });
    if (lanAddresses.length > 0) {
      services.logger.info('Server reachable on the local network at:', {
        addresses: lanAddresses.map((address) => `http://${address}:${env.PORT}`),
      });
    }
  });

  let isShuttingDown = false;

  const gracefulShutdown = async (signal: string): Promise<void> => {
    if (isShuttingDown) {
      services.logger.info(`Shutdown already in progress; ignoring ${signal}`);
      return;
    }
    isShuttingDown = true;

    services.logger.info(`Received ${signal}. Shutting down gracefully...`);

    if (closeWebSocketServer) {
      await closeWebSocketServer();
    }

    for (const socket of sockets) {
      socket.destroy();
    }

    if (typeof server.closeAllConnections === 'function') {
      server.closeAllConnections();
    }

    const shutdownTimeout = setTimeout(() => {
      services.logger.warn('Graceful shutdown timed out; forcing exit');
      process.exit(1);
    }, 10_000);

    server.close(async () => {
      try {
        await closeDatabase();
        await closeRedisClient();
        services.logger.info('Server closed');
      } catch (error) {
        services.logger.error('Error during shutdown cleanup', { error });
      } finally {
        clearTimeout(shutdownTimeout);
        process.exit(0);
      }
    });
  };

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

startServer().catch((error) => {
  // eslint-disable-next-line no-console
  console.error('Failed to start server', error);
  process.exit(1);
});
