/* ----------------------------------------------------------------------------

 Question Server
 GitLab: <https://gitlab.com/MrFry/mrfrys-node-server>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <https://www.gnu.org/licenses/>.

 ------------------------------------------------------------------------- */

console.log('Node version: ' + process.version)
console.log('Current working directory: ' + process.cwd())

const port = process.env.PORT || 8080
const httpsport = 5001

// import os from 'os'
// os.setPriority(10)
// console.log(`Process priority set to ${os.getPriority()}`)

import express from 'express'
import http from 'http'
import https from 'https'
import cors from 'cors'
import cookieParser from 'cookie-parser'
import { v4 as uuidv4 } from 'uuid'
import type { Database } from 'better-sqlite3'

import logger from './utils/logger'
import utils from './utils/utils'
import dbtools from './utils/dbtools'
import reqlogger from './middlewares/reqlogger.middleware'
import idStats from './utils/ids'
import { paths, validateFiles } from './utils/files'

const logFile = paths.logDir + logger.logFileName
const vlogFile = paths.vlogDir + logger.logFileName

function moveLogIfNotFromToday(path: string, to: string) {
    if (utils.FileExists(path)) {
        const today = new Date()
        const stat = utils.statFile(path)
        if (
            today.getFullYear() !== stat.mtime.getFullYear() ||
            today.getMonth() !== stat.mtime.getMonth() ||
            today.getDate() !== stat.mtime.getDate()
        ) {
            utils.renameFile(path, to + utils.GetDateString(stat.mtime))
        }
    }
}
moveLogIfNotFromToday(logFile, paths.logDir)
moveLogIfNotFromToday(vlogFile, paths.vlogDir)

idStats.Load()
logger.Load()

interface Modules {
    [name: string]: Module
}

interface Module {
    path: string
    publicdirs: Array<string>
    name: string
    route: string
    nextdir?: string
    isNextJs?: boolean
    app: express.Application
    dailyAction: Function
    cleanup: Function
}

export interface SetupData {
    url: string
    publicdirs: Array<string>
    userDB?: Database
    nextdir?: string
    httpServer: http.Server
    httpsServer: https.Server
}

const filesValid = validateFiles()
if (!filesValid) {
    const msg =
        'Not all files are valid which are needed to run the server! Please resolve the above issues, and start again.'
    logger.Log(msg, 'red')
    throw new Error(msg)
}

const userDB = dbtools.GetDB(paths.usersDBPath)
let modules: Modules = utils.ReadJSON(paths.modulesFile)

const debugLevel = parseInt(process.env.NS_LOGLEVEL) || 0
logger.Log('Loglevel is: ' + debugLevel)
logger.Log(`Log path: ${logFile}`)
logger.Log(`vLog path: ${vlogFile}`)

try {
    if (utils.FileExists(paths.extraModulesFile)) {
        const extraModules = JSON.parse(utils.ReadFile(paths.extraModulesFile))
        modules = {
            ...extraModules,
            ...modules,
        }
    }
} catch (err) {
    logger.Log('Failed to read extra modules file')
    console.error(err)
}

process.on('SIGINT', () => exit('SIGINT'))
process.on('SIGTERM', () => exit('SIGTERM'))

function exit(reason: string) {
    console.log()
    logger.Log(`Exiting, reason: ${reason}`)
    Object.keys(modules).forEach((key) => {
        const module = modules[key]
        if (module.cleanup) {
            try {
                module.cleanup()
            } catch (err) {
                logger.Log(
                    `Error in ${key} cleanup! Details in STDERR`,
                    logger.GetColor('redbg')
                )
                console.error(err)
            }
        }
    })

    logger.Log('Closing Auth DB')
    userDB.close()

    process.exit()
}

let certsLoaded = false
let certs: { key: string; cert: string; ca: string }

// https://certbot.eff.org/
if (
    !process.env.NS_NO_HTTPS_FORCE &&
    utils.FileExists(paths.privkeyFile) &&
    utils.FileExists(paths.fullchainFile) &&
    utils.FileExists(paths.chainFile)
) {
    try {
        certs = {
            key: utils.ReadFile(paths.privkeyFile),
            cert: utils.ReadFile(paths.fullchainFile),
            ca: utils.ReadFile(paths.chainFile),
        }
        certsLoaded = true
    } catch (err) {
        logger.Log('Error loading cert files!', logger.GetColor('redbg'))
        console.error(err)
    }
}

const app = express()
const httpServer = http.createServer(app)
let httpsServer: https.Server
if (!process.env.NS_NO_HTTPS_FORCE && certsLoaded) {
    httpsServer = https.createServer(certs, app)
    logger.Log('Listening on port: ' + httpsport + ' (https)')
} else {
    logger.Log('Https not avaible', 'yellowbg')
}

if (!process.env.NS_NO_HTTPS_FORCE) {
    app.use(function (req, res, next) {
        if (req.secure) {
            next()
        } else {
            logger.DebugLog(
                `HTTPS ${req.method} redirect to: ${
                    'https://' + req.headers.host + req.url
                }`,
                'https',
                1
            )
            if (req.method === 'POST') {
                res.redirect(307, 'https://' + req.headers.host + req.url)
            } else {
                res.redirect('https://' + req.headers.host + req.url)
            }
        }
    })
}
// https://github.com/expressjs/cors#configuration-options
app.use(
    cors({
        credentials: true,
        origin: true,
    })
)

const cookieSecret = uuidv4()
app.use(cookieParser(cookieSecret))
app.set('view engine', 'ejs')
app.set('views', ['./src/modules/api/views', './src/sharedViews'])

if (!utils.FileExists(paths.statExcludeFile)) {
    utils.WriteFile('[]', paths.statExcludeFile)
}
const excludeFromStats = utils.ReadJSON(paths.statExcludeFile)

app.use(
    reqlogger({
        loggableKeywords: ['news.json'],
        loggableModules: [],
        exceptions: ['_next/static'],
        excludeFromStats: excludeFromStats,
    })
)

const domain = utils.ReadFile(paths.domainFile).trim()

Object.keys(modules).forEach(function (key) {
    const module = modules[key]
    try {
        const mod = require(module.path).default // eslint-disable-line
        // const mod = require(module.path)

        module.publicdirs.forEach((pdir) => {
            utils.CreatePath(pdir)
        })

        if (mod.setup) {
            mod.setup({
                url: domain,
                userDB: userDB,
                publicdirs: module.publicdirs,
                nextdir: module.nextdir,
                httpServer: httpServer,
                httpsServer: httpsServer,
            })
        }

        const modApp = mod.getApp()
        module.app = modApp.app
        module.dailyAction = modApp.dailyAction
        module.cleanup = modApp.cleanup
        logger.Log(
            `Module "${mod.name}" loaded at "${module.route}"`,
            logger.GetColor('yellow')
        )
        app.use(module.route, module.app)
    } catch (err) {
        console.error(err)
    }
})

app.get('*', (req, res) => {
    if (req.is('application/json')) {
        res.status(404).end()
    } else {
        res.status(404).render('404', { domain: domain })
    }
})

app.post('*', (req, res) => {
    if (req.is('application/json')) {
        res.status(404).end()
    } else {
        res.status(404).render('404')
    }
})

setLogTimer()
function setLogTimer() {
    const now = new Date()
    const night = new Date(
        now.getFullYear(),
        now.getMonth(),
        now.getDate() + 1,
        0,
        0,
        1
    )
    logger.DebugLog(`Next daily action: ${night}`, 'daily', 1)
    const msToMidnight = night.getTime() - now.getTime() + 10000
    logger.DebugLog(`msToMidnight: ${msToMidnight}`, 'daily', 1)
    logger.DebugLog(`Seconds To Midnight: ${msToMidnight / 1000}`, 'daily', 1)

    if (msToMidnight < 0) {
        logger.Log(
            `Error setting up Log Timer, msToMidnight is negative! (${msToMidnight})`,
            logger.GetColor('redbg')
        )
        return
    }

    setTimeout(function () {
        LogTimerAction()
        rotateLog()
        setLogTimer()
    }, msToMidnight)
}

function rotateLog() {
    const date = new Date()
    date.setDate(date.getDate() - 1)
    const fname =
        date.getFullYear() +
        '-' +
        ('0' + (date.getMonth() + 1)).slice(-2) +
        '-' +
        ('0' + date.getDate()).slice(-2)

    if (utils.FileExists(logFile)) {
        utils.CopyFile(logFile, paths.logDir + fname)
    }
    if (utils.FileExists(vlogFile)) {
        utils.CopyFile(vlogFile, paths.vlogDir + fname)
    }

    utils.WriteFile(fname, logFile)
    utils.WriteFile(fname, vlogFile)
}

function LogTimerAction() {
    logger.DebugLog(`Running Log Timer Action`, 'daily', 1)
    Object.keys(modules).forEach((key) => {
        const module = modules[key]
        logger.DebugLog(`Ckecking ${key}`, 'daily', 1)
        if (module.dailyAction) {
            try {
                logger.Log(`Running daily action of ${key}`)
                module.dailyAction()
            } catch (err) {
                logger.Log(
                    `Error in ${key} daily action! Details in STDERR`,
                    logger.GetColor('redbg')
                )
                console.error(err)
            }
        }
    })

    const line =
        '==================================================================================================================================================='
    logger.Log(line)
}

logger.Log('Node version: ' + process.version)
logger.Log('Current working directory: ' + process.cwd())
logger.Log('Listening on port: ' + logger.C('blue') + port + logger.C())
if (process.getuid && process.getuid() === 0) {
    logger.Log('Running as root', 'redbg')
}

httpServer.listen(port)
if (httpsServer) {
    httpsServer.listen(httpsport)
}
