'use strict'

const path = require('node:path')
const yaml = require('yaml')
const fastifyStatic = require('@fastify/static')
const rfdc = require('rfdc')()
const swaggerInitializer = require('./swagger-initializer')
const indexHtml = require('./index-html')

// URI prefix to separate static assets for swagger UI
const staticPrefix = '/static'

function getRedirectPathForTheRootRoute (url) {
  let redirectPath

  if (url.substr(-1) === '/') {
    redirectPath = `.${staticPrefix}/index.html`
  } else {
    const urlPathParts = url.split('/')
    redirectPath = `./${urlPathParts[urlPathParts.length - 1]}${staticPrefix}/index.html`
  }

  return redirectPath
}

function fastifySwagger (fastify, opts, done) {
  let staticCSP = false
  if (opts.staticCSP === true) {
    const csp = require('../static/csp.json')
    staticCSP = `default-src 'self'; base-uri 'self'; font-src 'self' https: data:; frame-ancestors 'self'; img-src 'self' data: validator.swagger.io; object-src 'none'; script-src 'self' ${csp.script.join(' ')}; script-src-attr 'none'; style-src 'self' https: ${csp.style.join(' ')}; upgrade-insecure-requests;`
  }
  if (typeof opts.staticCSP === 'string') {
    staticCSP = opts.staticCSP
  }
  if (typeof opts.staticCSP === 'object' && opts.staticCSP !== null) {
    staticCSP = ''
    Object.keys(opts.staticCSP).forEach(function (key) {
      const value = Array.isArray(opts.staticCSP[key]) ? opts.staticCSP[key].join(' ') : opts.staticCSP[key]
      staticCSP += `${key.toLowerCase()} ${value}; `
    })
  }

  if (typeof staticCSP === 'string' || typeof opts.transformStaticCSP === 'function') {
    fastify.addHook('onSend', function (request, reply, payload, done) {
      // set static csp when it is passed
      if (typeof staticCSP === 'string') {
        reply.header('content-security-policy', staticCSP.trim())
      }
      // mutate the header when it is passed
      const header = reply.getHeader('content-security-policy')
      if (header && typeof opts.transformStaticCSP === 'function') {
        reply.header('content-security-policy', opts.transformStaticCSP(header))
      }
      done()
    })
  }

  const hooks = Object.create(null)
  if (opts.hooks) {
    const additionalHooks = [
      'onRequest',
      'preHandler'
    ]
    for (const hook of additionalHooks) {
      hooks[hook] = opts.hooks[hook]
    }
  }

  fastify.route({
    url: '/',
    method: 'GET',
    schema: { hide: true },
    ...hooks,
    handler: (req, reply) => {
      reply.redirect(getRedirectPathForTheRootRoute(req.raw.url))
    }
  })

  if (opts.theme) {
    const themePrefix = `${staticPrefix}/theme`
    if (opts.theme.css) {
      for (const cssFile of opts.theme.css) {
        fastify.route({
          url: `${themePrefix}/${cssFile.filename}`,
          method: 'GET',
          schema: { hide: true },
          ...hooks,
          handler: (req, reply) => {
            reply
              .header('content-type', 'text/css; charset=UTF-8')
              .send(cssFile.content)
          }
        })
      }
    }

    if (opts.theme.js) {
      for (const jsFile of opts.theme.js) {
        fastify.route({
          url: `${themePrefix}/${jsFile.filename}`,
          method: 'GET',
          schema: { hide: true },
          ...hooks,
          handler: (req, reply) => {
            reply
              .header('content-type', 'application/javascript; charset=utf-8')
              .send(jsFile.content)
          }
        })
      }
    }

    if (opts.theme.favicon) {
      for (const favicon of opts.theme.favicon) {
        fastify.route({
          url: `${themePrefix}/${favicon.filename}`,
          method: 'GET',
          schema: { hide: true },
          ...hooks,
          handler: (req, reply) => {
            reply
              .header('content-type', favicon.type)
              .send(favicon.content)
          }
        })
      }
    }
  }

  const indexHtmlContent = indexHtml(opts)

  fastify.route({
    url: `${staticPrefix}/index.html`,
    method: 'GET',
    schema: { hide: true },
    ...hooks,
    handler: (req, reply) => {
      reply
        .header('content-type', 'text/html; charset=utf-8')
        .send(indexHtmlContent)
    }
  })

  const swaggerInitializerContent = swaggerInitializer(opts)

  fastify.route({
    url: `${staticPrefix}/swagger-initializer.js`,
    method: 'GET',
    schema: { hide: true },
    ...hooks,
    handler: (req, reply) => {
      reply
        .header('content-type', 'application/javascript; charset=utf-8')
        .send(swaggerInitializerContent)
    }
  })

  const hasTransformSpecificationFn = typeof opts.transformSpecification === 'function'
  const shouldCloneSwaggerObject = opts.transformSpecificationClone ?? true
  const transformSpecification = opts.transformSpecification
  fastify.route({
    url: '/json',
    method: 'GET',
    schema: { hide: true },
    ...hooks,
    handler: hasTransformSpecificationFn
      ? shouldCloneSwaggerObject
        ? function (req, reply) {
          reply.send(transformSpecification(rfdc(fastify.swagger()), req, reply))
        }
        : function (req, reply) {
          reply.send(transformSpecification(fastify.swagger(), req, reply))
        }
      : function (req, reply) {
        reply.send(fastify.swagger())
      }
  })

  fastify.route({
    url: '/yaml',
    method: 'GET',
    schema: { hide: true },
    ...hooks,
    handler: hasTransformSpecificationFn
      ? shouldCloneSwaggerObject
        ? function (req, reply) {
          reply
            .type('application/x-yaml')
            .send(yaml.stringify(transformSpecification(rfdc(fastify.swagger()), req, reply)))
        }
        : function (req, reply) {
          reply
            .type('application/x-yaml')
            .send(yaml.stringify(transformSpecification(fastify.swagger(), req, reply)))
        }
      : function (req, reply) {
        reply
          .type('application/x-yaml')
          .send(fastify.swagger({ yaml: true }))
      }
  })

  // serve swagger-ui with the help of @fastify/static
  fastify.register(fastifyStatic, {
    root: path.join(__dirname, '..', 'static'),
    prefix: staticPrefix,
    decorateReply: false
  })

  fastify.register(fastifyStatic, {
    root: opts.baseDir || path.join(__dirname, '..'),
    serve: false
  })

  // Handler for external documentation files passed via $ref
  fastify.route({
    url: '/*',
    method: 'GET',
    schema: { hide: true },
    ...hooks,
    handler: function (req, reply) {
      const file = req.params['*']
      reply.sendFile(file)
    }
  })

  done()
}

module.exports = fastifySwagger
