import http from "node:http"
import httpProxy from "http-proxy"
import defaults from "linebridge/src/server/defaults"

import pkg from "../package.json"

export default class Proxy {
    constructor() {
        this.proxys = new Map()
        this.wsProxys = new Map()

        this.http = http.createServer(this.handleHttpRequest)
        this.http.on("upgrade", this.handleHttpUpgrade)
    }

    http = null

    register = ({ serviceId, path, target, pathRewrite, ws } = {}) => {
        if (!path) {
            throw new Error("Path is required")
        }

        if (!target) {
            throw new Error("Target is required")
        }

        if (this.proxys.has(path)) {
            console.warn(`Proxy already registered [${path}], skipping...`)
            return false
        }

        const proxy = httpProxy.createProxyServer({
            target: target,
        })

        proxy.on("proxyReq", (proxyReq, req, res, options) => {
            proxyReq.setHeader("x-linebridge-version", pkg.version)
            proxyReq.setHeader("x-forwarded-for", req.socket.remoteAddress)
        })

        proxy.on("error", (e) => {
            console.error(e)
        })

        const proxyObj = {
            serviceId: serviceId ?? "default_service",
            path: path,
            target: target,
            pathRewrite: pathRewrite,
            proxy: proxy,
        }

        if (ws) {
            console.log(`🔗 Registering websocket proxy [${path}] -> [${target}]`)
            this.wsProxys.set(path, proxyObj)
        } else {
            console.log(`🔗 Registering path proxy [${path}] -> [${target}]`)
            this.proxys.set(path, proxyObj)
        }

        return true
    }

    unregister = (path) => {
        if (!this.proxys.has(path)) {
            console.warn(`Proxy not registered [${path}], skipping...`)
            return false
        }

        console.log(`🔗 Unregistering path proxy [${path}]`)

        this.proxys.get(path).proxy.close()
        this.proxys.delete(path)
    }

    unregisterAllFromService = (serviceId) => {
        this.proxys.forEach((value, key) => {
            if (value.serviceId === serviceId) {
                this.unregister(value.path)
            }
        })
    }

    listen = async (port = 9000, host = "0.0.0.0", cb) => {
        return await new Promise((resolve, reject) => {
            this.http.listen(port, host, () => {
                console.log(`🔗 Proxy listening on ${host}:${port}`)

                if (cb) {
                    cb(this)
                }

                resolve(this)
            })
        })
    }

    rewritePath = (rewriteConfig, path) => {
        let result = path
        const rules = []

        for (const [key, value] of Object.entries(rewriteConfig)) {
            rules.push({
                regex: new RegExp(key),
                value: value,
            })
        }

        for (const rule of rules) {
            if (rule.regex.test(path)) {
                result = result.replace(rule.regex, rule.value)
                break
            }
        }

        return result
    }

    setCorsHeaders = (res) => {
        res.setHeader("Access-Control-Allow-Origin", "*")
        res.setHeader("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
        res.setHeader("Access-Control-Allow-Headers", "*")

        return res
    }

    handleHttpRequest = (req, res) => {
        res = this.setCorsHeaders(res)

        const sanitizedUrl = req.url.split("?")[0]

        // preflight continue with code 204
        if (req.method === "OPTIONS") {
            res.statusCode = 204
            res.end()
            return
        }

        if (sanitizedUrl === "/") {
            return res.end(`
                {
                    "name": "${pkg.name}",
                    "version": "${pkg.version}",
                    "lb_version": "${defaults.version}"
                }
            `)
        }

        const namespace = `/${sanitizedUrl.split("/")[1]}`
        const route = this.proxys.get(namespace)

        if (!route) {
            res.statusCode = 404
            res.end(`
                {
                    "error": "404 Not found"
                }
            `)
            return
        }

        if (route.pathRewrite) {
            req.url = this.rewritePath(route.pathRewrite, req.url)
        }

        route.proxy.web(req, res)
    }

    handleHttpUpgrade = (req, socket, head) => {
        const namespace = `/${req.url.split("/")[1]}`
        const route = this.wsProxys.get(namespace)

        if (!route) {
            // destroy socket
            socket.destroy()
            return false
        }

        if (route.pathRewrite) {
            req.url = this.rewritePath(route.pathRewrite, req.url)
        }

        route.proxy.ws(req, socket, head)
    }

    close = () => {
        this.http.close()
    }
}