import { allApps, initApps, TOES_URL } from '$apps'
import { buildAppUrl } from '@urls'
import appsRouter from './api/apps'
import eventsRouter from './api/events'
import syncRouter from './api/sync'
import systemRouter from './api/system'
import { Hype } from '@because/hype'
import { cleanupStalePublishers } from './mdns'
import { extractSubdomain, proxySubdomain, proxyWebSocket, websocket } from './proxy'
import { Shell } from './shell'
import type { Server } from 'bun'
import type { WsData } from './proxy'

const app = new Hype({ layout: false, logging: !!process.env.DEBUG })

app.route('/api/apps', appsRouter)
app.route('/api/events', eventsRouter)
app.route('/api/sync', syncRouter)
app.route('/api/system', systemRouter)

// Tool URLs: /tool/code?app=todo&file=README.md -> redirect to tool subdomain
app.get('/tool/:tool', c => {
  const toolName = c.req.param('tool')
  const tool = allApps().find(a => a.tool && a.name === toolName)
  if (!tool || tool.state !== 'running' || !tool.port) {
    return c.text(`Tool "${toolName}" not found or not running`, 404)
  }
  const params = new URLSearchParams(c.req.query()).toString()
  const base = buildAppUrl(toolName, TOES_URL)
  const url = params ? `${base}?${params}` : base
  return c.redirect(url)
})

// Tool API proxy: /api/tools/:tool/* -> proxy to tool port
app.all('/api/tools/:tool/:path{.+}', async c => {
  const toolName = c.req.param('tool')
  const tool = allApps().find(a => a.tool && a.name === toolName)
  if (!tool || tool.state !== 'running' || !tool.port) {
    return c.json({ error: `Tool "${toolName}" not found or not running` }, 404)
  }

  const subPath = '/' + c.req.param('path')

  // Build target URL
  const params = new URLSearchParams(c.req.query()).toString()
  const targetUrl = params
    ? `http://localhost:${tool.port}${subPath}?${params}`
    : `http://localhost:${tool.port}${subPath}`

  // Proxy the request
  const response = await fetch(targetUrl, {
    method: c.req.method,
    headers: c.req.raw.headers,
    body: c.req.method !== 'GET' && c.req.method !== 'HEAD' ? c.req.raw.body : undefined,
  })

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

const BUILD_SCRIPT = import.meta.dir + '/../../scripts/build.ts'
const DIST_DIR = import.meta.dir + '/../../dist'
const INSTALL_SCRIPT = await Bun.file(import.meta.dir + '/install.sh').text()

const BUILD_TARGETS = [
  'toes-macos-arm64',
  'toes-macos-x64',
  'toes-linux-arm64',
  'toes-linux-x64',
]

const buildInFlight = new Map<string, Promise<boolean>>()

async function buildBinary(name: string): Promise<boolean> {
  const existing = buildInFlight.get(name)
  if (existing) return existing

  const promise = (async () => {
    const proc = Bun.spawn(
      ['bun', 'run', BUILD_SCRIPT, `--target=${name}`],
      { stdout: 'inherit', stderr: 'inherit' },
    )
    return (await proc.exited) === 0
  })()

  buildInFlight.set(name, promise)
  promise.finally(() => buildInFlight.delete(name))
  return promise
}

// Install script: curl -fsSL http://<hostname>.local/install | bash
app.get('/install', c => {
  if (!TOES_URL) return c.text('TOES_URL is not configured', 500)
  const script = INSTALL_SCRIPT.replace('__TOES_URL__', TOES_URL)
  return c.text(script, 200, { 'content-type': 'text/plain' })
})

// Serve built CLI binaries from dist/, building on-demand if needed
app.get('/dist/:file', async c => {
  const file = c.req.param('file')
  if (!file || file.includes('/') || file.includes('..')) {
    return c.text('Not found', 404)
  }
  const bunFile = Bun.file(`${DIST_DIR}/${file}`)
  if (!(await bunFile.exists())) {
    if (!BUILD_TARGETS.includes(file)) return c.text('Not found', 404)
    const ok = await buildBinary(file)
    if (!ok) return c.text(`Failed to build "${file}"`, 500)
  }
  return new Response(Bun.file(`${DIST_DIR}/${file}`), {
    headers: { 'content-type': 'application/octet-stream' },
  })
})

// SPA routes — serve the shell for all client-side paths
app.get('/app/:name/:tab', c => c.html(<Shell />))
app.get('/app/:name', c => c.html(<Shell />))
app.get('/:path', c => c.html(<Shell />))

cleanupStalePublishers()
await initApps()

const defaults = app.defaults

export default {
  ...defaults,
  maxRequestBodySize: 1024 * 1024 * 50, // 50MB
  fetch(req: Request, server: Server<WsData>) {
    const subdomain = extractSubdomain(req.headers.get('host') ?? '')
    if (subdomain) {
      if (req.headers.get('upgrade')?.toLowerCase() === 'websocket') {
        return proxyWebSocket(subdomain, req, server)
      }
      return proxySubdomain(subdomain, req)
    }
    return defaults.fetch.call(app, req, server)
  },
  websocket,
}
