// Ignore paths for logging, metrics and docs
// Fast, minimalist web framework for node.
import express from 'express'

// Helmet helps you secure your Express app by setting various HTTP headers
import helmet from 'helmet'

// Fastest HTTP logger for Node.js in town
import { httpLogger, logger } from './logger.js'
// Readiness and liveness checks middleware
import health from '@cloudnative/health-connect'

// Prometheus middleware for standard api metrics
import { metricsMiddleware } from '@open-xchange/metrics'
import promClient from 'prom-client'

// Swagger UI for api-docs
import swaggerUi from 'swagger-ui-express'
import yaml from 'js-yaml'
import fs from 'fs'
import { getCSSDependenciesFor, getDependencies, getOxManifests, getVersion, loadViteManifests } from './manifests.js'
import { fileCache } from './files.js'
import { getMergedMetadata } from './meta.js'
import { viteManifestToDeps } from './util.js'

const swaggerDocument = yaml.load(fs.readFileSync('./src/swagger.yaml', 'utf8'))

const startUpTimeGauge = new promClient.Gauge({
  name: 'manifest_service_startup_time',
  help: 'Time to warm up cache'
})

const metricsMiddlewareInstance = metricsMiddleware()

export function createApp () {
  // The app returned by express() is in fact a JavaScript Function, designed to be passed to Node’s HTTP servers as a callback to handle requests.

  const app = express()
  app.use(express.urlencoded({ extended: true }))

  const healthCheck = new health.HealthChecker()
  const startupCheck = new health.StartupCheck('warmup cache', async function () {
    const stopTimer = startUpTimeGauge.startTimer()
    try {
      const viteManifests = await loadViteManifests({ warmUp: false })
      // also need to load ox manifests here, to make sure the cache is warm
      await getOxManifests()
      const deps = viteManifestToDeps(viteManifests)
      await fileCache.warmUp(viteManifests, deps)
    } catch (e) {
      logger.error(`Failed to get dependencies: ${e.message}`)
      throw e
    } finally {
      stopTimer()
    }
  })
  healthCheck.registerStartupCheck(startupCheck)

  // Application-level middleware
  app.use(httpLogger)
  app.use((req, res, next) => {
    const { sha256Sum } = fileCache.get(req.path)
    res.locals.sha256Sum = sha256Sum
    next()
  })
  app.use(helmet({
    contentSecurityPolicy: false,
    crossOriginEmbedderPolicy: false,
    originAgentCluster: false,
    crossOriginOpenerPolicy: { policy: 'same-origin-allow-popups' }
  }))
  app.use('/healthy', health.LivenessEndpoint(healthCheck))
  app.use('/ready', health.ReadinessEndpoint(healthCheck))
  app.use(metricsMiddlewareInstance)
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument))
  app.use('/swagger.json', (req, res) => res.json(swaggerDocument))
  app.timeout = 30000

  app.use(async (req, res, next) => {
    const version = await getVersion()
    if (version) res.setHeader('version', version)
    next()
  })

  app.get('/manifests', async (req, res, next) => {
    try {
      res.json(await getOxManifests())
    } catch (err) {
      next(err)
    }
  })

  app.get('/dependencies', async (req, res, next) => {
    try {
      res.json(Object.assign({ '/': [] }, await getDependencies()))
    } catch (err) {
      next(err)
    }
  })

  app.get('/meta', async (req, res, next) => {
    try {
      res.json(await getMergedMetadata())
    } catch (err) {
      next(err)
    }
  })

  app.get('/', async (req, res, next) => {
    const { 'content-type': contentType, content } = fileCache.get('/index.html')
    if (content) return res.setHeader('content-type', contentType).status(200).send(content)
    next()
  })

  // backwards compatibility for 7.10.x
  // this should hopefully be resolved with an ingress
  // or proper config. But is used to be safe on all ends
  app.get('/ui', async (req, res, next) => {
    res.redirect(process.env.APP_ROOT)
  })

  app.post('/redirect', (req, res, next) => {
    const location = req.body.location || '../busy.html'
    res.redirect(location)
  })

  app.use(async (req, res, next) => {
    const { 'content-type': contentType, content } = fileCache.get(req.path)
    if (content) {
      const dependencies = await getCSSDependenciesFor(req.path.substr(1))
      return res
        .setHeader('content-type', contentType)
        .setHeader('dependencies', dependencies.join(','))
        .status(200).send(content)
    }
    next()
  })

  app.use(async (req, res, next) => {
    try {
      const { 'content-type': contentType, content } = await fileCache.fetchAndStore(req.path)
      if (content) {
        const dependencies = await getCSSDependenciesFor(req.path.substr(1))
        return res
          .setHeader('content-type', contentType)
          .setHeader('dependencies', dependencies.join(','))
          .status(200).send(content)
      }
    } catch (e) {}
    next()
  })

  app.use(function (err, req, res, next) {
    logger.error(err)
    res.status(500).end()
  })

  return app
}