require("./globals")

const fs = require("fs")
const path = require("path")
const express = require("express")
const cors = require("cors")
const chalk = require("chalk")
const { spawn } = require("child_process")

const getInternalIp = require("./lib/getInternalIp")
const comtyAscii = require("./ascii")

const useLogger = require("./lib/useLogger")
const { createProxyMiddleware } = require("http-proxy-middleware")

const { setupLatestRelease } = require("./lib/setupDist")

const developmentServers = [
    {
        name: "WebAPP",
        color: "bgRed",
        cwd: path.resolve(global.__root, "../../app"),
    },
    {
        name: "MainAPI",
        color: "bgBlue",
        cwd: path.resolve(global.__root, "../../server"),
    },
    {
        name: "ChatAPI",
        color: "bgMagenta",
        cwd: path.resolve(global.__root, "../../chat_server"),
    },
    {
        name: "MarketplaceAPI",
        color: "bgCyan",
        cwd: path.resolve(global.__root, "../../marketplace_server"),
    },
    {
        name: "MusicAPI",
        color: "bgGreen",
        cwd: path.resolve(global.__root, "../../music_server")
    },
    {
        name: "FileAPI",
        color: "bgYellow",
        cwd: path.resolve(global.__root, "../../file_server"),
    },
    {
        name: "SyncAPI",
        color: "bgMagenta",
        cwd: path.resolve(global.__root, "../../sync_server"),
    }
]

const ApiServers = [
    {
        name: "main",
        remote: ({
            address,
            protocol,
            port
        } = {}) => `${protocol ?? "http"}://${address ?? process.env.LOCALHOST}:${port ?? 3010}`,
    },
    {
        name: "chat",
        remote: ({
            address,
            protocol,
            port
        } = {}) => `${protocol ?? "http"}://${address ?? process.env.LOCALHOST}:${port ?? 3020}`,
    },
    {
        name: "livestreaming",
        remote: ({
            address,
            protocol,
            port
        } = {}) => `${protocol ?? "http"}://${address ?? process.env.LOCALHOST}:${port ?? 3030}`,
    },
    {
        name: "marketplace",
        remote: ({
            address,
            protocol,
            port
        } = {}) => `${protocol ?? "http"}://${address ?? process.env.LOCALHOST}:${port ?? 3040}`
    },
    {
        name: "music",
        remote: ({
            address,
            protocol,
            port
        } = {}) => `${protocol ?? "http"}://${address ?? process.env.LOCALHOST}:${port ?? 3050}`
    },
    {
        name: "files",
        remote: ({
            address,
            protocol,
            port
        } = {}) => `${protocol ?? "http"}://${address ?? process.env.LOCALHOST}:${port ?? 3060}`
    },
    {
        name: "sync",
        remote: ({
            address,
            protocol,
            port
        } = {}) => `${protocol ?? "http"}://${address ?? process.env.LOCALHOST}:${port ?? 3070}`
    }
]

class Main {
    static checkDistIntegrity() {
        // check if dist folder exists
        if (!fs.existsSync(global.distPath)) {
            return false
        }

        // TODO: check the dist checksum with oficial server checksum

        return true
    }

    static fetchDistManifest() {
        if (!fs.existsSync(global.distPath)) {
            return null
        }

        const pkgPath = path.join(global.distPath, "manifest.json")

        if (!fs.existsSync(pkgPath)) {
            return null
        }

        const pkg = require(pkgPath)

        return pkg
    }

    constructor(process) {
        this.process = process
        this.args = this.getArgs()

        this.registerExitHandlers()
        this.initialize()
    }

    initialize = async () => {
        console.clear()
        console.log(comtyAscii)
        console.log(`${chalk.bgBlue(`Running Wrapper`)} ${chalk.bgMagenta(`[v${global.packagejson.version}]`)}`)

        this.internalIp = await getInternalIp()

        this.webapp_port = this.args.web_port ?? 9000
        this.api_port = this.args.api_proxy_port ?? 5000

        if (this.args.dev === true) {
            console.log(`🔧  Running in ${chalk.bgYellow("DEVELOPMENT")} mode \n\n`)

            //this.runDevelopmentServers()
            this.runDevelopmentScript()
            this.initializeAPIProxyServer()

            return this
        } else {
            if (!Main.checkDistIntegrity()) {
                await setupLatestRelease()
            }
        }

        if (!this.args["no-web"]) {
            this.initializeWebAppServer()
        }

        this.initializeAPIProxyServer()

        return this
    }

    runDevelopmentScript = async () => {
        const devScript = spawn("npm", ["run", "dev"], {
            cwd: path.resolve(global.__root, "../../../"),
            shell: true,
            stdio: "inherit"
        })

        devScript.on("exit", (code) => {
            console.log(`🔧  ${chalk.bgYellow("WebAPP")} exited with code ${code}`)
        })
    }

    runDevelopmentServers = async () => {
        this.dev_servers = []

        // start all development servers
        for (let i = 0; i < developmentServers.length; i++) {
            const server = developmentServers[i]

            console.log(`🔧  Starting ${chalk.bgYellow(server.name)}...`)

            const serverProcess = spawn("npm", ["run", "dev"], {
                cwd: server.cwd,
                shell: true
            })

            let chalkInstance = chalk[server.color]

            if (typeof chalkInstance === undefined) {
                chalkInstance = chalk.bgWhite
            }

            // log output of server
            serverProcess.stdout.on("data", (data) => {
                console.log(`${chalkInstance(`[${server.name}]`)} ${data.toString()}`)
            })

            serverProcess.on("exit", (code) => {
                console.log(`🔧  ${chalk.bgYellow(server.name)} exited with code ${code}`)
            })

            this.dev_servers.push({
                name: server.name,
                process: serverProcess
            })
        }
    }

    registerExitHandlers() {
        this.process.on("exit", this.onExit)
        this.process.on("SIGINT", this.onExit)
        this.process.on("SIGUSR1", this.onExit)
        this.process.on("SIGUSR2", this.onExit)
        this.process.on("uncaughtException", this.onExit)
    }

    onExit = async () => {
        console.clear()
        console.log(comtyAscii)
        console.log(`\n\nClosing wrapper... \n\n`)

        setTimeout(() => {
            console.log(`Wrapper did not close in time, forcefully closing...`)
            process.exit(0)
        }, 5000)

        if (Array.isArray(this.dev_servers)) {
            for await (const server of this.dev_servers) {
                console.log(`Killing ${chalk.bgYellow(server.name)}...`)

                server.process.kill()
            }
        }

        return process.exit(0)
    }

    getArgs = () => {
        let args = {}

        for (let i = 0; i < this.process.argv.length; i++) {
            const arg = this.process.argv[i]

            if (arg.startsWith("--")) {
                const argName = arg.replace("--", "")
                const argValue = this.process.argv[i + 1]

                args[argName] = argValue ?? true
            }
        }

        return args
    }

    initializeWebAppServer = async () => {
        this.webapp_server = express()

        this.webapp_server.use(cors({
            origin: "*",
            methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
            preflightContinue: true,
            optionsSuccessStatus: 204
        }))

        if (!this.forwardAppPort) {
            this.webapp_server.use(express.static(global.distPath))

            this.webapp_server.get("*", (req, res) => {
                return res.sendFile(path.join(global.distPath, "index.html"))
            })
        } else {
            this.webapp_server.use(createProxyMiddleware({
                target: `http://${this.internalIp}:${this.forwardAppPort}`,
                changeOrigin: true,
                ws: true,
                pathRewrite: {
                    "^/": ""
                }
            }))
        }

        this.webapp_server.listen(this.webapp_port)

        console.log(`🌐  WEB-APP Listening on > `, `${this.internalIp}:${this.webapp_port}`)

        return this.webapp_server
    }

    initializeAPIProxyServer = async () => {
        this.apiproxy_server = express()

        this.apiproxy_server.use(useLogger)

        ApiServers.forEach((server) => {
            const remote = server.remote({
                address: this.internalIp,
                protocol: this.forceApiHttps ? "https" : "http",
            })

            this.apiproxy_server.use(`/${server.name}`, createProxyMiddleware({
                target: `${remote}`,
                changeOrigin: true,
                ws: true,
                pathRewrite: {
                    [`^/${server.name}`]: ""
                }
            }))
        })

        this.apiproxy_server.listen(this.api_port)

        console.log(`🌐  API-PROXY Listening on >`, `${this.internalIp}:${this.api_port}`)

        return this.apiproxy_server
    }
}

new Main(process)