import { INestApplication, ValidationPipe } from '@nestjs/common';
import { HttpAdapterHost, NestFactory } from '@nestjs/core';

import { ConfigService } from '@nestjs/config';
import * as bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import { Logger, LoggerErrorInterceptor } from 'nestjs-pino';
import { AppModule } from './app.module';
import { AllExceptionFilter } from './fliters/all-exception.filter';
import { applyHttpServerTimeouts } from './helpers/http-server.helper';
import { AdminAuditInterceptor } from './interceptors/admin-audit.interceptor';
import { RequestCountInterceptor } from './interceptors/request-count.interceptor';
import { setupKernel } from './kernel';
import { runSeeds } from './seeds/run-seeds';
import { AlertSignalService } from './modules/alert/alert-signal.service';
import { AuditService } from './modules/audit/audit.service';
import { setupSwagger } from './swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bufferLogs: true });
  const httpAdapterHost = app.get(HttpAdapterHost);
  //* A4 신호 수집(M2 §D3) — 필터(분자)와 인터셉터(분모)가 같은 인스턴스를 쓴다.
  const alertSignals = app.get(AlertSignalService);
  //* 관리자 행위 감사(T2·DENIED) — 인터셉터가 통과 요청을, 예외 필터가 가드 거부를 남긴다.
  //*  두 층이 같은 인스턴스를 쓰므로 줄 형태와 버전이 갈리지 않는다.
  const audit = app.get(AuditService);

  setupCors(app);
  app.use(bodyParser.json({ limit: '10mb' }));
  app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }));
  app.use(cookieParser());
  // app.useGlobalPipes(new ValidationPipe({ transform: true }));
  app.useGlobalFilters(new AllExceptionFilter(httpAdapterHost, alertSignals, audit));

  app.useLogger(app.get(Logger));
  app.useGlobalInterceptors(
    new LoggerErrorInterceptor(),
    new RequestCountInterceptor(alertSignals),
    new AdminAuditInterceptor(audit),
  );

  process.env.NODE_ENV !== 'production' && setupSwagger(app);
  if (process.platform === 'win32') {
    await setupKernel();
  }

  applyHttpServerTimeouts(app.getHttpServer());

  //* 기본값은 7200 그대로. PORT 가 있을 때만 그 값을 쓴다 — 같은 머신에서 worktree별
  //* 인스턴스를 동시에 띄우려면 포트가 하드코딩돼 있으면 안 된다. 미설정/비수치면 7200.
  const port = Number(process.env.PORT) || 7200;
  await app.listen(port);

  //* 기동 seed — **listen 뒤**다(DD2). 앞에 두면 seed 가 늦거나 멈출 때 기동·헬스체크가 함께 기다린다.
  //*   모듈 수명주기 훅에 걸지 않는 이유도 같다 — 스케줄러와 invoice.seeder 도 같은 AppModule 을 띄운다.
  //*   `runSeeds` 는 던지지 않는다. 끝나기 전에 온 요청은 최고관리자 판정이 거짓(닫힘)이다.
  void runSeeds(app);
}

bootstrap();

function setupCors(app: INestApplication) {
  const configService = app.get(ConfigService);
  const rawOrigins = configService.get<string>('allowOrigins');
  if (!rawOrigins) {
    return;
  }

  const origins = rawOrigins.split(',');
  app.enableCors({
    origin: origins,
    credentials: true,
    exposedHeaders: ['Content-Disposition'],
  });
}
