import { Hono } from "hono"
import { upgradeWebSocket, websocket } from "hono/bun"
import { uniqueNamesGenerator, adjectives, animals, colors } from "unique-names-generator"

type Request = {
    id: string
    app: string
    method: string
    path: string
    headers: Record<string, string>,
    body: string
}

type Response = {
    id: string
    status: number
    headers: Record<string, string>,
    body: string
}

type Connection = { app: string, ws: any }
let connections: Record<string, Connection> = {}
const pending = new Map<string, (res: any) => void>

function send(connection: any, msg: Request) {
    console.log("sending", msg)
    connection.send(JSON.stringify(msg))
}

const app = new Hono

app.get("/tunnel", c => {
    const app = c.req.query("app")
    if (!app) {
        return c.text("need ?app name", 502)
    }

    return upgradeWebSocket(c, {
        async onOpen(_event, ws) {
            const name = randomName()
            connections[name] = { app, ws }
            console.log(`connection opened: ${name} -> ${app}`)
        },
        onClose: (_event, ws) => {
            for (const name of Object.keys(connections))
                if (connections[name]?.ws === ws) {
                    console.log("connection closed:", name)
                    delete connections[name]
                    break
                }
        },
        async onMessage(event, _ws) {
            const msg = JSON.parse(event.data.toString())
            const resolve = pending.get(msg.id)
            if (resolve) {
                resolve(msg)
                pending.delete(msg.id)
            }
        },
    })
})

app.get("*", async c => {
    const url = new URL(c.req.url)
    const localhost = url.hostname.endsWith("localhost")
    const domains = url.hostname.split(".")
    let subdomain = ""

    if (domains.length > (localhost ? 1 : 2))
        subdomain = domains[0]!

    const connection = connections[subdomain]
    if (!connection)
        return c.text("No tunnel", 502)

    const id = randomID()
    const headers = Object.fromEntries(c.req.raw.headers)
    const body = await c.req.text()
    const app = connection.app

    const result = await new Promise<Response>(resolve => {
        pending.set(id, resolve)
        send(connection.ws, {
            id,
            app,
            method: c.req.method,
            path: c.req.path,
            headers,
            body
        })
    })

    return new Response(result.body, {
        status: result.status,
        headers: result.headers,
    })
})

function randomID(): string {
    return Math.random().toString(36).slice(2, 10)
}

function randomName(): string {
    return uniqueNamesGenerator({
        dictionaries: [adjectives, animals],
        separator: "-",
        style: "lowerCase",
    })
}

export default {
    port: process.env.PORT || 3100,
    websocket,
    fetch: app.fetch,
}