import "./ddApm"
// need to load environment first
import {
  auth,
  cache,
  env as coreEnv,
  events,
  features,
  logging,
  middleware,
  queue,
  redis,
  timers,
} from "@budibase/backend-core"
import { sdk as proSdk } from "@budibase/pro"
import { bootstrap } from "global-agent"
import http from "http"
import gracefulShutdown from "http-graceful-shutdown"
import Application, { Middleware } from "koa"
import koaBody, { HttpMethodEnum } from "koa-body"
import RedisStore from "koa-redis"
import api from "./api"
import { loadTemplateConfig } from "./constants/templates"
import * as db from "./db"
import env from "./environment"

db.init()

const koaSession = require("koa-session")

import { userAgent } from "koa-useragent"

import { initPro } from "./initPro"
import { handleScimBody } from "./middleware/handleScimBody"

if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE) {
  console.warn(
    "Warning: ENABLE_SSO_MAINTENANCE_MODE is set. It is recommended this flag is disabled if maintenance is not in progress"
  )
}

// this will setup http and https proxies form env variables
bootstrap()

const app: Application = new Application()

app.keys = ["secret", "key"]
app.proxy = true

// set up top level koa middleware
app.use(handleScimBody)
app.use(
  koaBody({
    multipart: true,
    parsedMethods: [
      HttpMethodEnum.POST,
      HttpMethodEnum.PUT,
      HttpMethodEnum.PATCH,
      HttpMethodEnum.DELETE,
    ],
  })
)

let store: any

const sessionMiddleware: Middleware = async (ctx: any, next: any) => {
  if (!store) {
    const redisClient = await redis.clients.getSessionClient()
    // @ts-expect-error - koa-redis types are weird
    store = RedisStore({ client: redisClient.client })
  }

  return koaSession(
    {
      store,
      key: "koa:sess",
      maxAge: 86400000, // one day
    },
    app
  )(ctx, next)
}

app.use(sessionMiddleware)

app.use(middleware.correlation)
app.use(middleware.pino)
app.use(middleware.ip)
if (!coreEnv.DISABLE_CONTENT_SECURITY_POLICY) {
  app.use(middleware.csp)
}
app.use(userAgent)

// authentication
app.use(auth.passport.initialize())
app.use(auth.passport.session())

// api routes
app.use(api.routes())

const server = http.createServer(app.callback())

server.timeout = parseInt(env.HTTP_SERVER_TIMEOUT_MS || "60000")
server.headersTimeout = parseInt(env.HTTP_HEADERS_TIMEOUT_MS || "12000")
server.requestTimeout = parseInt(env.HTTP_REQUEST_TIMEOUT_MS || "120000")
server.keepAliveTimeout = parseInt(env.HTTP_KEEPALIVE_TIMEOUT_MS || "15000")

const shutdown = async (signal?: string) => {
  console.log(
    `Worker service shutting down gracefully... ${signal ? `Signal: ${signal}` : ""}`
  )
  timers.cleanup()
  await events.shutdown()
  await redis.clients.shutdown()
  await queue.shutdown()
}

gracefulShutdown(server, {
  signals: "SIGINT SIGTERM",
  timeout: 30000,
  onShutdown: shutdown,
  forceExit: !env.isTest(),
  finally: () => {
    console.log("Worker service shutdown complete")
  },
})

process.on("uncaughtException", async err => {
  logging.logAlert("Uncaught exception.", err)
  await shutdown()
  if (!env.isTest()) {
    process.exit(1)
  }
})

process.on("unhandledRejection", async reason => {
  logging.logAlert("Unhandled Promise Rejection", reason as Error)
  await shutdown()
  if (!env.isTest()) {
    process.exit(1)
  }
})

export default server.listen(parseInt(env.PORT || "4002"), async () => {
  let startupLog = `Worker running on ${JSON.stringify(server.address())}`
  if (env.BUDIBASE_ENVIRONMENT) {
    startupLog = `${startupLog} - environment: "${env.BUDIBASE_ENVIRONMENT}"`
  }
  console.log(startupLog)

  await initPro()
  await redis.clients.init()
  features.init()

  if (env.EMAIL_TEMPLATE_PATH) {
    await loadTemplateConfig(env.EMAIL_TEMPLATE_PATH)
  }

  cache.docWritethrough.init()
  // configure events processors with pro dependencies
  // can't integrate directly into backend-core due to cyclic issues
  await events.processors.init(
    proSdk.auditLogs.write,
    proSdk.licensing.client.getLicenseKey
  )
})
