import { NestFactory } from '@nestjs/core';
import { NestExpressApplication, ExpressAdapter } from '@nestjs/platform-express';
import { AppModule } from './app.module';
// import { PrismaService } from './prisma/prisma.service';
import * as cookieParser from 'cookie-parser';
import { CsrfService } from './csrf/csrf.service';
import { cookieOptions } from './common/constants/cookieOptions.constant';
import { EmailService } from './email/email.service';
// TODO: remove the following packages express-session, @types/express-session, express-mysql-session, @types/express-mysql-session
// import * as session from 'express-session';
// import * as expressMySqlSession from 'express-mysql-session';
// import prismaUrlSplitter from 'src/common/prismaUrlSplitter';

// Adjust the environment interface with the custom variables
// https://stackoverflow.com/a/45195359/2110294
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: string;
      DOMAIN: string;
      ADAPTER_IP: string;
      PORT: number;
      DATABASE_URL: string;
      SESSION_SECRET: string;
      CSRF_SECRET: string;
      COOKIE_SECRET: string;
      JWT_ACCESS_TOKEN_SECRET: string;
      JWT_REFRESH_TOKEN_SECRET: string;
      HASHID_SALT: string;
      CLIENT_ORIGIN: string;
      SMTP_FROM: string;
      SMTP_HOST: string;
      SMTP_PORT: string;
      SMTP_USER: string;
      SMTP_PASS: string;
      UPLOAD_DIRECTORY: string;
      NUXT_API_LOCATION: string;
      NUXT_CLIENT_LOCATION: string;
    }
  }
}

// https://docs.nestjs.com/recipes/prisma#issues-with-enableshutdownhooks
async function bootstrap() {
  const { NODE_ENV, DATABASE_URL, DOMAIN, ADAPTER_IP, PORT, CLIENT_ORIGIN, SESSION_SECRET, CSRF_SECRET, COOKIE_SECRET } = process.env;
  // initialise the application
  const app = await NestFactory.create<NestExpressApplication>(
    AppModule,
    new ExpressAdapter(),
    // See warning
    // https://docs.nestjs.com/middleware
    // {
    //   bodyParser: false
    // }
  );

  // Set global route prefix
  app.setGlobalPrefix('api');

  // enable cors
  // https://docs.nestjs.com/security/cors
  const { origin } = JSON.parse(CLIENT_ORIGIN);
  app.enableCors({
    origin,
    methods: ['OPTIONS','GET','PUT','POST','PATCH','DELETE'],
    credentials: true,
  });

  // https://github.com/chill117/express-mysql-session
  // https://stackoverflow.com/a/44925725/2110294
  // const sessionStoreOptions = {
  //   ...prismaUrlSplitter(DATABASE_URL),
  //   schema: {
  //     tableName: 'sessions',
  //     columnNames: {
  //       session_id: 'session_id',
  //       expires: 'expires',
  //       data: 'data'
  //     }
  //   }
  // }
  // const MySQLStore = expressMySqlSession(session);
  // const sessionStore = new MySQLStore(sessionStoreOptions);

  // enable session usage
  // NB: must come before the csrf middleware
  // https://github.com/expressjs/session
  // app.use(
  //   session({
  //     name: '__'+DOMAIN+'-sid',
  //     // https://github.com/expressjs/session#secret
  //     secret: SESSION_SECRET!,
  //     // https://github.com/expressjs/session#unset
  //     store: sessionStore,
  //     // https://github.com/expressjs/session#resave
  //     resave: true,
  //     // https://github.com/expressjs/session#saveUninitialized
  //     saveUninitialized: false,
  //     // https://github.com/expressjs/session#rolling
  //     rolling: true,
  //     // https://github.com/expressjs/session#unset
  //     unset: 'destroy',
  //     // https://github.com/expressjs/session#cookie
  //     cookie: cookieOptions() as session.CookieOptions
  //   })
  // );

  // enable cookie usage during stateless csrf protection
  // https://github.com/expressjs/cookie-parser
  // see for cookie options:
  // https://github.com/jshttp/cookie#options
  app.use(cookieParser(COOKIE_SECRET, cookieOptions as cookieParser.CookieParseOptions));

  // Enable stateless CSRF protection
  // https://github.com/Psifi-Solutions/csrf-sync#getting-started
  const csrfService = app.get(CsrfService);
  // csrfService.initCsrfSync();
  // app.use(csrfService.csrfSynchronisedProtection);
  csrfService.initDoubleCsrf(DOMAIN, CSRF_SECRET);
  app.use(csrfService.doubleCsrfProtection, csrfService.csrfErrorHandler);

  // Express cannot handle BigInt data types when returning a JSON response
  // Workaround:
  (BigInt.prototype as any).toJSON = function () {
    // Reccommended as there is no character length
    //return this.toString();
    // We have however not achieved the length of a bigint
    // so casting to int would be fine
    return parseInt(this);
  }

  // address issues with shutdown hooks
  // https://docs.nestjs.com/recipes/prisma#issues-with-enableshutdownhooks
  // const prismaService = app.get(PrismaService);
  // await prismaService.enableShutdownHooks(app);
  // https://github.com/prisma/prisma/issues/20171#issuecomment-1632599621
  // https://docs.nestjs.com/fundamentals/lifecycle-events#application-shutdown
  app.enableShutdownHooks();

  // start processing the emails
  const emailService = app.get(EmailService);
  if (await emailService.isProcessing()) await emailService.toggleProcessing(false);
  emailService.startProcessing();

  // start the server
  await app.listen(PORT, ADAPTER_IP, function() {
    console.log(new Date()+' - Server started listening for traffic on port '+PORT);
    // take control of the event logic
    process.on('SIGTERM', code => {
      console.log(new Date()+' - Started shutdown routine');
      // TODO: check is email sending is processing, delay until complete
      // stop the scheduled events
      emailService.stopProcessing();
      // Exit the process with code 0
      // NB: if not called then the process with hang, would need to kill it with:
      // lsof -i tcp:8080
      // kill -9 <PID>
      console.log(new Date()+' - Server stopped listening for traffic');
      process.exit();
    })
  });
}

bootstrap();