import './instrument';

// BigInt cannot be serialized by JSON.stringify by default.
// Polyfill toJSON so Express/NestJS can serialize Prisma BigInt fields.
(BigInt.prototype as any).toJSON = function () {
  return this.toString();
};

import { RequestMethod, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import cookieParser from 'cookie-parser';
import helmet from 'helmet';
import { LoggerService, RedisIoAdapter, validateEnv } from '@modules/shared';
import { AppModule } from './app.module';

async function bootstrap() {
  validateEnv();
  const app = await NestFactory.create(AppModule, {
    bufferLogs: true,
    rawBody: true,
    bodyParser: true,
  });
  const logger = app.get(LoggerService);
  app.useLogger(logger);

  // ── API Versioning — global /api/v1/ prefix ──
  app.setGlobalPrefix('api/v1', {
    exclude: [
      { path: 'health', method: RequestMethod.GET },
      { path: 'health/(.*)', method: RequestMethod.GET },
    ],
  });

  // ── OpenAPI / Swagger ──
  const swaggerConfig = new DocumentBuilder()
    .setTitle('Goodgo Platform API')
    .setDescription('Real-estate platform API — listings, search, payments, subscriptions, analytics')
    .setVersion('1.0')
    .addBearerAuth(
      { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
      'JWT',
    )
    .addTag('auth', 'Authentication & user profile')
    .addTag('listings', 'Property listings CRUD & moderation')
    .addTag('search', 'Full-text & geo search')
    .addTag('payments', 'Payment processing & callbacks')
    .addTag('subscriptions', 'Plans, billing & usage metering')
    .addTag('admin', 'Admin panel operations')
    .addTag('notifications', 'Notification history & preferences')
    .addTag('analytics', 'Market reports & price analytics')
    .addTag('reviews', 'Property reviews & ratings')
    .addTag('mcp', 'Model Context Protocol server transport')
    .build();
  const document = SwaggerModule.createDocument(app, swaggerConfig);
  SwaggerModule.setup('api/v1/docs', app, document, {
    swaggerOptions: { persistAuthorization: true },
    jsonDocumentUrl: 'api/v1/docs-json',
  });

  // ── WebSocket Adapter (Socket.IO) ──
  // Redis pub/sub fan-out for multi-instance broadcasts; falls back to the
  // in-memory IoAdapter when Redis is unreachable (single-node / local dev).
  const wsAdapter = new RedisIoAdapter(app);
  await wsAdapter.connectToRedis();
  app.useWebSocketAdapter(wsAdapter);

  // ── Security Headers (Helmet) ──
  app.use(
    helmet({
      // CSP relaxed for API — responses are consumed cross-origin by the web frontend
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          scriptSrc: ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net'],
          styleSrc: ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net'],
          imgSrc: ["'self'", 'data:', 'https:', 'blob:'],
          connectSrc: ["'self'", 'https://cdn.jsdelivr.net', 'https://api.goodgo.vn', 'wss:', 'ws:'],
          fontSrc: ["'self'", 'data:'],
          objectSrc: ["'none'"],
          frameSrc: ["'none'"],
          baseUri: ["'self'"],
          formAction: ["'self'"],
        },
      },
      // Must allow cross-origin for API consumed by platform.goodgo.vn
      crossOriginEmbedderPolicy: false,
      crossOriginOpenerPolicy: false,
      crossOriginResourcePolicy: { policy: 'cross-origin' },
      frameguard: { action: 'deny' },
      hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
      referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
    }),
  );

  // ── Permissions-Policy Header ──
  app.use((_req: unknown, res: { setHeader: (name: string, value: string) => void }, next: () => void) => {
    res.setHeader(
      'Permissions-Policy',
      'camera=(), microphone=(), geolocation=(self), payment=(self)',
    );
    next();
  });

  // ── Cookie Parser (required for CSRF double-submit pattern) ──
  app.use(cookieParser());

  // ── CORS ──
  const corsOrigins = process.env['CORS_ORIGINS'];
  if (!corsOrigins && process.env['NODE_ENV'] === 'production') {
    throw new Error('CORS_ORIGINS must be set in production');
  }
  const allowedOrigins = (corsOrigins ?? 'http://localhost:3000')
    .split(',')
    .map((o) => o.trim());
  app.enableCors({
    origin: allowedOrigins,
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Correlation-Id', 'X-CSRF-Token'],
    exposedHeaders: ['X-Correlation-Id'],
    credentials: true,
    maxAge: 86400,
  });

  // ── Global Validation Pipe ──
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      transformOptions: { enableImplicitConversion: true },
      disableErrorMessages: process.env['NODE_ENV'] === 'production',
    }),
  );

  // ── Request Body Size Limit ──
  const expressApp = app.getHttpAdapter().getInstance();

  // ── Trust Proxy (for rate limiting behind reverse proxy) ──
  expressApp.set('trust proxy', 1);

  // ── Graceful Shutdown ──
  app.enableShutdownHooks();

  const port = process.env['PORT'] ?? 3001;
  await app.listen(port);
  logger.log(`API running on http://localhost:${port}/api/v1`, 'Bootstrap');
}

bootstrap();
