import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { createServer } from 'node:http'
import os from 'node:os'
import path from 'node:path'
import express from 'express'
import { afterEach, describe, expect, it } from 'vitest'
import { registerStaticWebsiteRoutes } from '../static-site-routes'
import type { StaticWebsiteConfig } from '../static-site'

const tempRoots: string[] = []

function makeTempRoot() {
  const root = mkdtempSync(path.join(os.tmpdir(), 'hypertwist-static-routes-'))
  tempRoots.push(root)
  return root
}

function createStaticWebsiteConfig(root: string): StaticWebsiteConfig {
  const distPath = path.join(root, 'dist')
  mkdirSync(path.join(distPath, 'assets'), { recursive: true })
  writeFileSync(
    path.join(distPath, 'index.html'),
    '<!doctype html><html><body><div id="app">HyperTwist static shell</div></body></html>',
    'utf8',
  )
  writeFileSync(path.join(distPath, 'assets', 'app.js'), 'console.log("ready")', 'utf8')

  return {
    enabled: true,
    distPath,
    indexHtmlPath: path.join(distPath, 'index.html'),
    reason: 'forced_found',
  }
}

async function withServer(
  configure: (app: express.Express) => void | Promise<void>,
  run: (baseUrl: string) => Promise<void>,
) {
  const app = express()
  await configure(app)

  app.use((req, res) => {
    res.status(404).json({ ok: false, path: req.path })
  })

  const server = createServer(app)
  await new Promise<void>((resolve) => server.listen(0, resolve))
  const address = server.address()
  if (!address || typeof address === 'string') {
    server.close()
    throw new Error('Failed to bind test server')
  }

  const baseUrl = `http://127.0.0.1:${address.port}`

  try {
    await run(baseUrl)
  } finally {
    await new Promise<void>((resolve, reject) => {
      server.close((error) => {
        if (error) {
          reject(error)
          return
        }
        resolve()
      })
    })
  }
}

afterEach(() => {
  while (tempRoots.length > 0) {
    const root = tempRoots.pop()
    if (root) {
      rmSync(root, { recursive: true, force: true })
    }
  }
})

describe('registerStaticWebsiteRoutes', () => {
  it('serves the built shell for public and app routes without shadowing health, api, auth, or missing assets', async () => {
    const root = makeTempRoot()
    const staticWebsiteConfig = createStaticWebsiteConfig(root)

    await withServer(
      (app) => {
        app.get('/health', (_req, res) => {
          res.json({ ok: true, service: 'hypertwist-auth-server' })
        })
        app.get('/api/auth/health', (_req, res) => {
          res.json({ ok: true, service: 'hypertwist-auth-server', runtime: { mode: 'public' } })
        })
        app.get('/auth', (_req, res) => {
          res.status(404).json({ ok: false, route: 'auth-root' })
        })

        registerStaticWebsiteRoutes(app, staticWebsiteConfig)
      },
      async (baseUrl) => {
        const aboutResponse = await fetch(`${baseUrl}/about`)
        expect(aboutResponse.status).toBe(200)
        expect(await aboutResponse.text()).toContain('HyperTwist static shell')

        const downloadsResponse = await fetch(`${baseUrl}/app/downloads`)
        expect(downloadsResponse.status).toBe(200)
        expect(await downloadsResponse.text()).toContain('HyperTwist static shell')

        const healthResponse = await fetch(`${baseUrl}/health`)
        expect(healthResponse.status).toBe(200)
        expect(await healthResponse.json()).toEqual({ ok: true, service: 'hypertwist-auth-server' })

        const authHealthResponse = await fetch(`${baseUrl}/api/auth/health`)
        expect(authHealthResponse.status).toBe(200)
        expect(await authHealthResponse.json()).toEqual({
          ok: true,
          service: 'hypertwist-auth-server',
          runtime: { mode: 'public' },
        })

        const authRootResponse = await fetch(`${baseUrl}/auth`)
        expect(authRootResponse.status).toBe(404)
        expect(await authRootResponse.json()).toEqual({ ok: false, route: 'auth-root' })

        const assetResponse = await fetch(`${baseUrl}/assets/missing.js`)
        expect(assetResponse.status).toBe(404)
        expect(await assetResponse.json()).toEqual({ ok: false, path: '/assets/missing.js' })
      },
    )
  })
})
