import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import helmet from 'helmet';
import * as cookieParserModule from 'cookie-parser';
import { json } from 'express';
import { join } from 'path';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule, { bodyParser: false });

  // Replace Express's JSON parser with one that stringifies BigInt.
  // Express's default `json()` throws "Do not know how to serialize a BigInt".
  app.use(
    json({
      limit: '2mb',
      reviver: (_key: string, value: unknown) => {
        // If a numeric string would overflow the JS number range we keep
        // it as a string; the JWT strategy / services expect bigints.
        if (typeof value === 'string' && /^-?\d{16,}$/.test(value)) {
          // Leave as string — services parse with BigInt().
          return value;
        }
        return value;
      },
    }),
  );

  // Security headers (X-Content-Type-Options, X-Frame-Options, HSTS, ...)
  // Static assets (/uploads, /assets) are embedded cross-origin by other
  // sites, so CORP must allow cross-origin reads. CSP 去掉默认的
  // upgrade-insecure-requests：Swagger UI 经纯 http 访问时该指令会把
  // 自身子资源升级为 https，导致 ERR_SSL_PROTOCOL_ERROR。
  app.use(
    helmet({
      crossOriginResourcePolicy: { policy: 'cross-origin' },
      contentSecurityPolicy: {
        useDefaults: true,
        directives: {
          'upgrade-insecure-requests': null,
        },
      },
    }),
  );

  // Parse auth cookies (HttpOnly access/refresh tokens). Resolve both the
  // namespace and its `default` interop shape so it works regardless of
  // the compiled module interop mode.
  const cookieParser = (
    cookieParserModule as unknown as {
      default?: typeof cookieParserModule;
    }
  ).default ?? cookieParserModule;
  app.use(cookieParser());

  // CORS: only origins listed in CORS_ORIGINS (comma-separated) are
  // allowed. Credentials are enabled because the session lives in
  // HttpOnly cookies. "*" disables the allowlist and reflects any origin
  // (reflected origins are required when credentials are enabled).
  const corsOrigins = (process.env.CORS_ORIGINS ?? '')
    .split(',')
    .map((o) => o.trim())
    .filter(Boolean);
  const origin = corsOrigins.includes('*') ? true : corsOrigins;
  app.enableCors(corsOrigins.length > 0 ? { origin, credentials: true } : undefined);

  // Serve uploaded files. nosniff prevents browsers from sniffing a
  // non-image content type out of an uploaded file.
  app.useStaticAssets(join(process.cwd(), 'uploads'), {
    prefix: '/uploads/',
    setHeaders: (res) => {
      res.setHeader('X-Content-Type-Options', 'nosniff');
    },
  });
  app.useStaticAssets(join(process.cwd(), 'public'), {
    prefix: '/assets/',
  });

  // Global pipes
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      forbidNonWhitelisted: true,
    }),
  );

  // Global filters and interceptors
  app.useGlobalFilters(new HttpExceptionFilter());
  app.useGlobalInterceptors(new TransformInterceptor());

  // Swagger is only exposed outside production to avoid leaking the
  // full admin API surface.
  if (process.env.NODE_ENV !== 'production') {
    const config = new DocumentBuilder()
      .setTitle('InkReach Product Center API')
      .setDescription('Backend API for InkReach Product Center')
      .setVersion('1.0')
      .addBearerAuth()
      .build();

    const document = SwaggerModule.createDocument(app, config);
    SwaggerModule.setup('api/docs', app, document);
  }

  const port = process.env.PORT ?? 3001;
  await app.listen(port, '0.0.0.0');
  console.log(`🚀 Application is running on: http://0.0.0.0:${port}`);
}

// Make JSON.stringify aware of BigInt so outgoing responses containing
// primary keys (`BigInt` columns) don't blow up. BigInts are serialized
// as their decimal string — clients should parse them with BigInt().
(BigInt.prototype as unknown as { toJSON: () => string }).toJSON = function () {
  return this.toString();
};

bootstrap();
