import { Hono } from "hono"
import { upgradeWebSocket, websocket } from "hono/bun"
import { uniqueNamesGenerator, adjectives, animals } 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
    isBinary?: boolean
}

type Success = {
    subdomain: string
}

export const GIT_SHA = process.env.RENDER_GIT_COMMIT ?? (await Bun.$`git rev-parse HEAD`.text()).trim()

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

const app = new Hono

app.get("/health", c => c.text(GIT_SHA))

app.get("/tunnels", c => {
    return c.html(<ul>
        {Object.keys(connections).map(key =>
            <li><a href={`/tunnel/${key}`}>{key}</a> ({connections[key]?.app})</li>
        )}
    </ul>)
})

app.get("/tunnel/:app", c => {
    const url = new URL(c.req.url)
    const port = url.port === "80" ? "" : `:${url.port}`
    const hostname = url.hostname
    return c.redirect(`http://${c.req.param("app")}.${hostname}${port}`)
})

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

    const subdomain = c.req.query("subdomain") || ""
    if (subdomain && connections[subdomain])
        return c.text("subdomain taken", 502)

    const name = subdomain || randomName()

    return upgradeWebSocket(c, {
        async onOpen(_event, ws) {
            connections[name] = { app, ws }
            console.log(`connection opened: ${name} -> ${app}`)
            send(ws, { subdomain: name })
        },
        onClose: (_event, ws) => {
            console.log("connection closed:", name)
            delete connections[name]
        },
        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.all("*", 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("Bad Gateway", 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
        })
    })

    if (result.isBinary) {
        const buffer = Buffer.from(result.body, 'base64')
        return new Response(buffer, {
            status: result.status,
            headers: result.headers,
        })
    }

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

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


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,
}