import { afterEach, describe, expect, it, vi } from 'vitest'
import type { One } from '../vite/types'
import { createWorkerHandler, type LazyRoutes } from './workerHandler'

// route files are router-root relative, the way createRoutesManifest emits them
const pageRoute = {
  file: './some-page.tsx',
  page: '/some-page',
  namedRegex: '^/some-page(?:/)?$',
  urlPath: '/some-page',
  urlCleanPath: '/some-page',
  routeKeys: {},
  type: 'ssr',
  middlewares: [],
} satisfies One.BuildInfo['manifest']['pageRoutes'][number]

const apiRoute = {
  file: './api/status+api.ts',
  page: '/api/status',
  namedRegex: '^/api/status(?:/)?$',
  urlPath: '/api/status',
  urlCleanPath: '/api/status',
  routeKeys: {},
  type: 'api',
  middlewares: [],
} satisfies One.BuildInfo['manifest']['apiRoutes'][number]

const buildInfo = {
  constants: { CACHE_KEY: 'test' },
  routeToBuildInfo: {
    [pageRoute.file]: {
      type: 'ssr',
      path: pageRoute.page,
      routeFile: pageRoute.file,
      middlewares: [],
      preloadPath: '',
      cssPreloadPath: '',
      loaderPath: '',
      cleanPath: pageRoute.page,
      htmlPath: '',
      clientJsPath: '',
      serverJsPath: '',
      params: {},
      preloads: [],
      css: [],
      layoutCSS: [],
    },
  },
  pathToRoute: {},
  routeMap: {},
  manifest: {
    pageRoutes: [pageRoute],
    apiRoutes: [apiRoute],
    allRoutes: [pageRoute, apiRoute],
  },
  preloads: {},
  cssPreloads: {},
  loaders: {},
} satisfies One.BuildInfo

// route modules are reached through the server entry's own route map, keyed by
// the router-root prefixed path its `import.meta.glob` produces
const routeKey = `/app/${pageRoute.file.slice(2)}`

const makeServerEntry =
  (importPage: () => Promise<any> = async () => ({})) =>
  async () => ({
    default: {
      render: () => '<html></html>',
      options: { routes: { [routeKey]: importPage } },
    },
  })

const lazyRoutes = {
  serverEntry: makeServerEntry(),
  api: {
    [apiRoute.page]: async () => ({
      HEAD: () => new Response(null, { status: 204 }),
    }),
  },
  middlewares: {},
} satisfies LazyRoutes

function createHandler() {
  vi.stubEnv('ONE_BUFFERED_SSR', '1')
  return createWorkerHandler({
    oneOptions: { router: { root: 'app' }, web: { defaultRenderMode: 'ssr' } },
    buildInfo,
    lazyRoutes,
  }).handleRequest
}

afterEach(() => {
  vi.unstubAllEnvs()
})

describe('createWorkerHandler', () => {
  it('answers HEAD page requests exactly like GET', async () => {
    const handleRequest = createHandler()
    const get = await handleRequest(new Request('https://example.com/some-page'))
    const head = await handleRequest(
      new Request('https://example.com/some-page', { method: 'HEAD' })
    )

    if (!(get instanceof Response) || !(head instanceof Response)) {
      throw new Error('expected both GET and HEAD to return a Response')
    }
    // workerd drops the body for HEAD, so the handler must agree with GET on
    // status and content type. asserting an empty body here would only be
    // asserting the transport's job.
    expect(get.status).toBe(200)
    expect(head.status).toBe(get.status)
    expect(head.headers.get('content-type')).toBe(get.headers.get('content-type'))
  })

  it('does not route POST requests to page handlers', async () => {
    const response = await createHandler()(
      new Request('https://example.com/some-page', { method: 'POST' })
    )

    expect(response).toBeNull()
  })

  it('continues routing HEAD requests to API handlers', async () => {
    const response = await createHandler()(
      new Request('https://example.com/api/status', { method: 'HEAD' })
    )

    expect(response).toBeInstanceOf(Response)
    expect(response?.status).toBe(204)
  })

  it('disableModuleCache re-imports the page module on each request', async () => {
    vi.stubEnv('ONE_BUFFERED_SSR', '1')
    let imports = 0
    const handleRequest = createWorkerHandler({
      oneOptions: { router: { root: 'app' }, web: { defaultRenderMode: 'ssr' } },
      buildInfo,
      disableModuleCache: true,
      lazyRoutes: {
        ...lazyRoutes,
        serverEntry: makeServerEntry(async () => {
          imports += 1
          return {}
        }),
      },
    }).handleRequest

    await handleRequest(new Request('https://example.com/some-page'))
    await handleRequest(new Request('https://example.com/some-page'))
    expect(imports).toBeGreaterThan(1)
  })
})
