import 'dotenv/config';
import { mkdirSync } from 'node:fs';
import { basename, extname } from 'node:path';
import { BadRequestException, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import helmet from 'helmet';
import { AppModule } from './app.module.js';
import { HttpExceptionFilter } from './common/http-exception.filter.js';
import { MESSAGES } from './common/messages.js';
import { ResponseEnvelopeInterceptor } from './common/response-envelope.interceptor.js';
import { resolveUploadRootDir } from './uploads/upload-path.util.js';

const INLINE_UPLOAD_EXTENSIONS = new Set([
  '.png',
  '.jpg',
  '.jpeg',
  '.gif',
  '.bmp',
  '.webp',
  '.mp4',
  '.mov',
  '.avi',
  '.mkv',
  '.webm',
]);

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  app.use(
    helmet({
      contentSecurityPolicy: false,
      crossOriginResourcePolicy: false,
    }),
  );
  app.enableCors(buildCorsOptions());

  mkdirSync(resolveUploadRootDir(), { recursive: true });
  app.useStaticAssets(resolveUploadRootDir(), {
    prefix: '/uploads/',
    setHeaders: (res, filePath) => {
      const extension = extname(filePath).toLowerCase();
      res.setHeader('X-Content-Type-Options', 'nosniff');

      if (!INLINE_UPLOAD_EXTENSIONS.has(extension)) {
        const fileName = basename(filePath);
        res.setHeader(
          'Content-Disposition',
          `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`,
        );
        res.setHeader('Content-Type', 'application/octet-stream');
      }
    },
  });

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      transform: true,
      forbidNonWhitelisted: true,
      exceptionFactory: (errors) => {
        const messages = errors
          .flatMap((error) => Object.values(error.constraints ?? {}))
          .filter((item): item is string => Boolean(item));
        return new BadRequestException(
          messages.length > 0
            ? messages.join('; ')
            : MESSAGES.DEFAULT_BAD_REQUEST,
        );
      },
    }),
  );

  app.useGlobalFilters(new HttpExceptionFilter());
  app.useGlobalInterceptors(new ResponseEnvelopeInterceptor());

  if (shouldEnableSwagger()) {
    const swaggerBuilder = new DocumentBuilder()
      .setTitle('调压通医疗平台接口文档')
      .setDescription('包含认证、权限、患者、手术、任务和上传等后端接口。')
      .setVersion('1.0.0')
      .addBearerAuth(
        {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
          description: '请填写登录接口返回的访问令牌。',
        },
        'bearer',
      );

    const publicBaseUrl = normalizeOrigin(process.env.PUBLIC_BASE_URL);
    if (publicBaseUrl) {
      swaggerBuilder.addServer(publicBaseUrl, '公开地址');
    }

    const swaggerDocument = SwaggerModule.createDocument(
      app,
      swaggerBuilder.build(),
    );
    SwaggerModule.setup('api/docs', app, swaggerDocument, {
      jsonDocumentUrl: 'api/docs-json',
    });
  }

  await app.listen(process.env.PORT ?? 3000);
}

bootstrap();

function buildCorsOptions() {
  const allowedOrigins = parseAllowedOrigins(process.env.CORS_ALLOWED_ORIGINS);

  if (allowedOrigins.length === 0) {
    return {
      origin: process.env.NODE_ENV === 'production' ? false : true,
      credentials: true,
    };
  }

  return {
    credentials: true,
    origin: (
      origin: string | undefined,
      callback: (error: Error | null, allow?: boolean) => void,
    ) => {
      if (!origin) {
        callback(null, true);
        return;
      }

      callback(null, allowedOrigins.includes(normalizeOrigin(origin)));
    },
  };
}

function parseAllowedOrigins(value: string | undefined) {
  if (!value) {
    return [];
  }

  return Array.from(
    new Set(
      value
        .split(',')
        .map((origin) => normalizeOrigin(origin))
        .filter(Boolean),
    ),
  );
}

function normalizeOrigin(origin: string | undefined) {
  return String(origin ?? '').trim().replace(/\/+$/, '');
}

function shouldEnableSwagger() {
  return (
    process.env.ENABLE_SWAGGER === 'true' ||
    process.env.NODE_ENV !== 'production'
  );
}
