import { hc } from 'hono/client'
import type { ExtractSchema } from 'hono/types'
import { describe, expectTypeOf, test } from 'vp/test'
import { Store } from 'wata/host'

import * as Handler from './Handler.js'

describe('deviceCode', () => {
  test('accepts host deployment options', () => {
    const handler = Handler.deviceCode({
      baseUrl: (request) => new URL(request.url).origin,
      html: {
        render: () => new Response('verify'),
      },
      store: Store.memory(),
      validate(options) {
        expectTypeOf(options.record).toEqualTypeOf<import('wata/host').DeviceCode.PendingRecord>()
        expectTypeOf(options.request.id).toEqualTypeOf<string | number>()
        expectTypeOf(options.result).toBeUnknown()
        expectTypeOf(options.userCode).toBeString()
        return undefined
      },
    })

    expectTypeOf(handler).toMatchTypeOf<Handler.Handler>()
  })
})

describe('auth identity (OIDC)', () => {
  test('Options.identity opts into issuer-based verification', () => {
    expectTypeOf<Handler.auth.Options>().toMatchTypeOf<{
      identity?:
        | {
            issuer?: string | undefined
            required?: boolean | undefined
          }
        | undefined
    }>()
  })
})

describe('compose', () => {
  // Two ad-hoc schema-typed handlers to exercise schema merging.
  function makeAlpha() {
    const router = Handler.from()
    const app = router.post('/alpha/ping', async (c) => c.json({ pong: 'alpha' as const }))
    return app as Handler.Handler<typeof app>
  }

  function makeBeta() {
    const router = Handler.from()
    const app = router.get('/beta/echo', async (c) => c.json({ echo: 'beta' as const }))
    return app as Handler.Handler<typeof app>
  }

  test('merges route schemas under the mount path', () => {
    const composed = Handler.compose([makeAlpha(), makeBeta()], { path: '/api' })
    type Schema = ExtractSchema<typeof composed>

    type AlphaPost = Schema['/api/alpha/ping']['$post']
    type BetaGet = Schema['/api/beta/echo']['$get']

    // Both routes survive the merge.
    expectTypeOf<AlphaPost>().toBeObject()
    expectTypeOf<BetaGet>().toBeObject()
  })

  test('typed client exposes both handlers via direct property access', () => {
    const composed = Handler.compose([makeAlpha(), makeBeta()], { path: '/api' })
    const client = hc<typeof composed>('http://localhost')

    // Direct, autocomplete-friendly access.
    expectTypeOf(client.api.alpha.ping.$post).toBeFunction()
    expectTypeOf(client.api.beta.echo.$get).toBeFunction()
  })

  test('default mount path `/` exposes routes at the root', () => {
    const composed = Handler.compose([makeAlpha(), makeBeta()])
    const client = hc<typeof composed>('http://localhost')

    expectTypeOf(client.alpha.ping.$post).toBeFunction()
    expectTypeOf(client.beta.echo.$get).toBeFunction()
  })

  test('response bodies stay typed end-to-end after compose', async () => {
    const composed = Handler.compose([makeAlpha(), makeBeta()], { path: '/api' })
    const client = hc<typeof composed>('http://localhost')

    const alphaRes = await client.api.alpha.ping.$post()
    if (alphaRes.status === 200) {
      const body = await alphaRes.json()
      expectTypeOf(body).toMatchTypeOf<{ pong: 'alpha' }>()
    }

    const betaRes = await client.api.beta.echo.$get()
    if (betaRes.status === 200) {
      const body = await betaRes.json()
      expectTypeOf(body).toMatchTypeOf<{ echo: 'beta' }>()
    }
  })

  test('different mount paths are reflected in the composed schema', () => {
    const a = Handler.compose([makeAlpha()], { path: '/v1' })
    const b = Handler.compose([makeBeta()], { path: '/v2' })

    type SchemaA = ExtractSchema<typeof a>
    type SchemaB = ExtractSchema<typeof b>

    expectTypeOf<SchemaA['/v1/alpha/ping']['$post']>().toBeObject()
    expectTypeOf<SchemaB['/v2/beta/echo']['$get']>().toBeObject()
  })

  test('handlers without a route schema (legacy `Handler`) compose without errors', () => {
    // `Handler.from` returns plain `Handler` (default `Hono`), no route
    // schema. Mixing it with typed handlers must not break inference for the
    // typed siblings.
    const plain = Handler.from() as unknown as Handler.Handler
    const composed = Handler.compose([plain, makeAlpha()], { path: '/api' })
    const client = hc<typeof composed>('http://localhost')

    expectTypeOf(client.api.alpha.ping.$post).toBeFunction()
  })
})
