import { Base64 } from 'ox'
import { KeyAuthorization } from 'ox/tempo'
import { Account as TempoAccount } from 'viem/tempo'
import { describe, expect, test } from 'vp/test'
import * as z from 'zod/mini'

import { accounts, chain, privateKeys, webAuthnAccounts } from '../../test/config.js'
import * as CliAuth from './CliAuth.js'
import * as Handler from './Handler.js'

const root = accounts[0]!
const webAuthnRoot = webAuthnAccounts[0]!
const accessKey = TempoAccount.fromP256(privateKeys[1]!)
const secpAccessKey = accounts[1]!
const expiry = Math.floor(Date.now() / 1000) + 3_600
const token = '0x20c0000000000000000000000000000000000001' as const
const limit = 1_000n
const limits = [
  {
    limit,
    token,
  },
] as const

async function authorize(
  code: string,
  options: {
    accessKey?:
      | {
          address: `0x${string}`
          keyType: 'secp256k1' | 'p256' | 'webAuthn'
        }
      | undefined
    accessKeyAddress?: `0x${string}` | undefined
    expiry?: number | undefined
    limits?: readonly { token: `0x${string}`; limit: bigint }[] | undefined
  } = {},
) {
  const key = options.accessKey ?? accessKey
  const signed = await root.signKeyAuthorization(
    {
      accessKeyAddress: options.accessKeyAddress ?? key.address,
      keyType: key.keyType,
    },
    {
      chainId: BigInt(chain.id),
      expiry: options.expiry ?? expiry,
      limits: options.limits ?? limits,
    },
  )
  const keyAuthorization = KeyAuthorization.toRpc(signed)

  return {
    accountAddress: root.address,
    code,
    keyAuthorization: z.decode(CliAuth.keyAuthorization, {
      ...keyAuthorization,
      address: keyAuthorization.keyId,
    }),
  } satisfies z.output<typeof CliAuth.authorizeRequest>
}

async function authorizeWebAuthn(
  code: string,
  options: {
    accessKey?:
      | {
          address: `0x${string}`
          keyType: 'secp256k1' | 'p256' | 'webAuthn'
        }
      | undefined
    expiry?: number | undefined
    limits?: readonly { token: `0x${string}`; limit: bigint }[] | undefined
  } = {},
) {
  const key = options.accessKey ?? secpAccessKey
  const signed = await webAuthnRoot.signKeyAuthorization(
    {
      accessKeyAddress: key.address,
      keyType: key.keyType,
    },
    {
      chainId: BigInt(chain.id),
      expiry: options.expiry ?? expiry,
      limits: options.limits ?? limits,
    },
  )
  const keyAuthorization = KeyAuthorization.toRpc(signed)

  return {
    accountAddress: webAuthnRoot.address,
    code,
    keyAuthorization: z.decode(CliAuth.keyAuthorization, {
      ...keyAuthorization,
      address: keyAuthorization.keyId,
    }),
  } satisfies z.output<typeof CliAuth.authorizeRequest>
}

async function createRequest(
  codeVerifier = 'device-code-verifier',
  options: {
    accessKey?:
      | {
          keyType: 'secp256k1' | 'p256' | 'webAuthn'
          publicKey: `0x${string}`
        }
      | undefined
    expiry?: number | undefined
    keyType?: 'secp256k1' | 'p256' | 'webAuthn' | undefined
    limits?: readonly { token: `0x${string}`; limit: bigint }[] | undefined
  } = {},
) {
  const key = options.accessKey ?? accessKey
  return {
    codeVerifier,
    request: {
      codeChallenge: await createCodeChallenge(codeVerifier),
      ...('expiry' in options
        ? typeof options.expiry !== 'undefined'
          ? { expiry: options.expiry }
          : {}
        : { expiry }),
      ...('keyType' in options
        ? options.keyType
          ? { keyType: options.keyType }
          : {}
        : { keyType: key.keyType }),
      ...('limits' in options ? (options.limits ? { limits: options.limits } : {}) : { limits }),
      pubKey: key.publicKey,
    } satisfies z.output<typeof CliAuth.createRequest>,
  }
}

async function post<request extends z.ZodMiniType, response extends z.ZodMiniType>(
  handler: Handler.Handler,
  options: {
    body: z.output<request>
    request: request
    response?: response | undefined
    url: string
  },
) {
  const result = await handler.fetch(
    new Request(options.url, {
      body: JSON.stringify(z.encode(options.request, options.body)),
      headers: { 'content-type': 'application/json' },
      method: 'POST',
    }),
  )
  const json = (await result.json().catch(() => ({}))) as z.input<response>

  return {
    body: options.response ? z.decode(options.response, json) : json,
    status: result.status,
  }
}

async function get<response extends z.ZodMiniType>(
  handler: Handler.Handler,
  options: {
    response?: response | undefined
    url: string
  },
) {
  const result = await handler.fetch(new Request(options.url))
  const json = (await result.json().catch(() => ({}))) as z.input<response>

  return {
    body: options.response ? z.decode(options.response, json) : json,
    status: result.status,
  }
}

describe('from', () => {
  test('default: shares defaults across the device-code flow', async () => {
    const store = CliAuth.Store.memory()
    const now = () => 1_000
    const { codeVerifier, request } = await createRequest()
    const cli = CliAuth.from({
      chains: [chain],
      now,
      random: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]),
      store,
      ttlMs: 30_000,
    })

    const created = await cli.createDeviceCode({ request })
    const entry = await store.get(created.code)
    const authorized = await cli.authorize({
      request: await authorize(created.code),
    })
    const polled = await cli.poll({
      code: created.code,
      request: {
        codeVerifier,
      },
    })

    expect(created).toMatchInlineSnapshot(`
      {
        "code": "ABCDEFGH",
      }
    `)
    expect(entry).toMatchInlineSnapshot(`
      {
        "chainId": 1337n,
        "code": "ABCDEFGH",
        "codeChallenge": "NUwjc1h8PuXcsvSOG44Rp4bMayBXnOkriHEJ19CaSQM",
        "createdAt": 1000,
        "expiresAt": 31000,
        "expiry": ${expiry},
        "keyType": "p256",
        "limits": [
          {
            "limit": 1000n,
            "token": "0x20c0000000000000000000000000000000000001",
          },
        ],
        "pubKey": "${accessKey.publicKey}",
        "status": "pending",
      }
    `)
    expect(authorized).toMatchInlineSnapshot(`
      {
        "status": "authorized",
      }
    `)
    expect(polled.status).toMatchInlineSnapshot(`"authorized"`)
  })
})

describe('createDeviceCode', () => {
  test('default: creates a pending device code', async () => {
    const store = CliAuth.Store.memory()
    const now = () => 1_000
    const { request } = await createRequest()

    const result = await CliAuth.createDeviceCode({
      chainId: chain.id,
      now,
      random: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]),
      request,
      store,
      ttlMs: 30_000,
    })
    const entry = await store.get(result.code)

    expect(result).toMatchInlineSnapshot(`
      {
        "code": "ABCDEFGH",
      }
    `)
    expect(entry).toMatchInlineSnapshot(`
      {
        "chainId": 1337n,
        "code": "ABCDEFGH",
        "codeChallenge": "NUwjc1h8PuXcsvSOG44Rp4bMayBXnOkriHEJ19CaSQM",
        "createdAt": 1000,
        "expiresAt": 31000,
        "expiry": ${expiry},
        "keyType": "p256",
        "limits": [
          {
            "limit": 1000n,
            "token": "0x20c0000000000000000000000000000000000001",
          },
        ],
        "pubKey": "${accessKey.publicKey}",
        "status": "pending",
      }
    `)
  })

  test('behavior: policy rejection returns an error from the handler', async () => {
    const { request } = await createRequest()
    const handler = Handler.codeAuth({
      policy: {
        validate() {
          throw new Error('Expiry exceeds policy.')
        },
      },
    })

    const result = await post(handler, {
      body: request,
      request: CliAuth.createRequest,
      url: 'http://localhost/auth/pkce/code',
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "body": {
          "error": "Expiry exceeds policy.",
        },
        "status": 400,
      }
    `)
  })

  test('behavior: handler rate-limits all CLI auth endpoints together', async () => {
    const handler = Handler.codeAuth({
      rateLimit: CliAuth.RateLimit.memory({ max: 1, windowMs: 60_000 }),
    })

    const first = await get(handler, {
      url: 'http://localhost/auth/pkce/pending/ABCDEFGH',
    })
    const second = await get(handler, {
      url: 'http://localhost/auth/pkce/pending/ABCDEFGH',
    })

    expect(first.status).toMatchInlineSnapshot(`404`)
    expect(second).toMatchInlineSnapshot(`
      {
        "body": {
          "error": "Rate limit exceeded.",
        },
        "status": 429,
      }
    `)
  })

  test('behavior: Cloudflare rate-limit adapter shares one key across endpoints', async () => {
    const calls: { key: string }[] = []
    const rateLimit = CliAuth.RateLimit.cloudflare({
      limit(options) {
        calls.push(options)
        return { success: true }
      },
    })

    await rateLimit.limit({
      key: '127.0.0.1',
      request: new Request('http://localhost/auth/pkce/code'),
    })

    expect(calls).toMatchInlineSnapshot(`
      [
        {
          "key": "cli-auth:127.0.0.1",
        },
      ]
    `)
  })

  test('behavior: handler ignores untrusted forwarding headers for rate-limit keys', async () => {
    const calls: { key: string }[] = []
    const handler = Handler.codeAuth({
      rateLimit: {
        limit(options) {
          calls.push({ key: options.key })
          return { success: true }
        },
      },
    })

    await get(handler, {
      url: 'http://localhost/auth/pkce/pending/ABCDEFGH',
    })
    await handler.fetch(
      new Request('http://localhost/auth/pkce/pending/ABCDEFGH', {
        headers: {
          'x-forwarded-for': '192.0.2.1',
          'x-real-ip': '192.0.2.2',
        },
      }),
    )
    await handler.fetch(
      new Request('http://localhost/auth/pkce/pending/ABCDEFGH', {
        headers: {
          'cf-connecting-ip': '192.0.2.3',
        },
      }),
    )

    expect(calls).toMatchInlineSnapshot(`
      [
        {
          "key": "unknown",
        },
        {
          "key": "unknown",
        },
        {
          "key": "192.0.2.3",
        },
      ]
    `)
  })

  test('behavior: handler accepts a custom rate-limit key for trusted proxies', async () => {
    const calls: { key: string }[] = []
    const handler = Handler.codeAuth({
      rateLimit: {
        limit(options) {
          calls.push({ key: options.key })
          return { success: true }
        },
      },
      rateLimitKey(request) {
        return request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'
      },
    })

    await handler.fetch(
      new Request('http://localhost/auth/pkce/pending/ABCDEFGH', {
        headers: {
          'x-forwarded-for': '192.0.2.1, 192.0.2.2',
        },
      }),
    )

    expect(calls).toMatchInlineSnapshot(`
      [
        {
          "key": "192.0.2.1",
        },
      ]
    `)
  })

  test('behavior: invalid input returns 400', async () => {
    const handler = Handler.codeAuth()
    const response = await handler.fetch(
      new Request('http://localhost/auth/pkce/code', {
        body: JSON.stringify({ expiry }),
        headers: { 'content-type': 'application/json' },
        method: 'POST',
      }),
    )

    const body = await response.json()

    expect(body.error).toMatchInlineSnapshot(`
      "[\n  {\n    "expected": "string",\n    "code": "invalid_type",\n    "path": [\n      "codeChallenge"\n    ],\n    "message": "Invalid input"\n  },\n  {\n    "expected": "string",\n    "code": "invalid_type",\n    "path": [\n      "pubKey"\n    ],\n    "message": "Expected hex value"\n  }\n]"
    `)
    expect(response.status).toMatchInlineSnapshot(`400`)
  })

  test('behavior: handler rejects oversized JSON request bodies', async () => {
    const { request } = await createRequest()
    const handler = Handler.codeAuth({ maxBodyBytes: 16 })
    const response = await handler.fetch(
      new Request('http://localhost/auth/pkce/code', {
        body: JSON.stringify(z.encode(CliAuth.createRequest, request)),
        headers: { 'content-type': 'application/json' },
        method: 'POST',
      }),
    )
    const body = await response.json()

    expect({ body, status: response.status }).toMatchInlineSnapshot(`
      {
        "body": {
          "error": "Request body is too large.",
        },
        "status": 400,
      }
    `)
  })

  test('behavior: handler rejects oversized streamed JSON request bodies', async () => {
    const handler = Handler.codeAuth({ maxBodyBytes: 16 })
    const response = await handler.fetch(
      new Request('http://localhost/auth/pkce/code', {
        body: new ReadableStream({
          start(controller) {
            controller.enqueue(new TextEncoder().encode('{"codeChallenge":"'))
            controller.enqueue(new TextEncoder().encode('x"}'))
            controller.close()
          },
        }),
        duplex: 'half',
        headers: { 'content-type': 'application/json' },
        method: 'POST',
      } as never),
    )
    const body = await response.json()

    expect({ body, status: response.status }).toMatchInlineSnapshot(`
      {
        "body": {
          "error": "Request body is too large.",
        },
        "status": 400,
      }
    `)
  })

  test('behavior: rejects invalid public keys at creation time', async () => {
    const { request } = await createRequest()

    await expect(
      CliAuth.createDeviceCode({
        chainId: chain.id,
        request: { ...request, pubKey: '0x1234' },
        store: CliAuth.Store.memory(),
      }),
    ).rejects
      .toThrowErrorMatchingInlineSnapshot(`[PublicKey.InvalidSerializedSizeError: Value \`0x1234\` is an invalid public key size.

Expected: 33 bytes (compressed + prefix), 64 bytes (uncompressed) or 65 bytes (uncompressed + prefix).
Received 2 bytes.]`)
  })

  test('behavior: rejects too many limits', async () => {
    const { request } = await createRequest()
    const item = {
      limit: '0x1' as const,
      token: '0x20c0000000000000000000000000000000000001' as const,
    }
    const result = await z.safeDecodeAsync(CliAuth.createRequest, {
      ...z.encode(CliAuth.createRequest, request),
      limits: Array.from({ length: 11 }, () => item),
    })

    expect(result).toMatchInlineSnapshot(`
    	{
    	  "error": [$ZodError: [
    	  {
    	    "origin": "array",
    	    "code": "too_big",
    	    "maximum": 10,
    	    "inclusive": true,
    	    "path": [
    	      "limits"
    	    ],
    	    "message": "Invalid input"
    	  }
    	]],
    	  "success": false,
    	}
    `)
  })

  test('behavior: rejects malformed limit tokens', async () => {
    const { request } = await createRequest()
    const result = await z.safeDecodeAsync(CliAuth.createRequest, {
      ...z.encode(CliAuth.createRequest, request),
      limits: [{ limit: '0x1', token: '0x1234' }],
    })

    expect(result).toMatchInlineSnapshot(`
    	{
    	  "error": [$ZodError: [
    	  {
    	    "code": "invalid_format",
    	    "format": "template_literal",
    	    "pattern": "^0x[0-9a-fA-F]{40}$",
    	    "path": [
    	      "limits",
    	      0,
    	      "token"
    	    ],
    	    "message": "Expected address"
    	  }
    	]],
    	  "success": false,
    	}
    `)
  })

  test('behavior: handler rejects requests for unconfigured chains', async () => {
    const handler = Handler.codeAuth({
      chains: [chain],
    })
    const { request } = await createRequest()

    const result = await post(handler, {
      body: {
        ...request,
        chainId: BigInt(chain.id + 1),
      },
      request: CliAuth.createRequest,
      url: 'http://localhost/auth/pkce/code',
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "body": {
          "error": "Chain 1338 not configured",
        },
        "status": 400,
      }
    `)
  })

  test('behavior: supports pubkey-only requests with server defaults', async () => {
    const store = CliAuth.Store.memory()
    const now = () => 1_000
    const defaultExpiry = 4_600
    let validatedChainId: bigint | undefined
    const { request } = await createRequest('device-code-verifier', {
      accessKey: secpAccessKey,
      expiry: undefined,
      keyType: undefined,
      limits: undefined,
    })

    const result = await CliAuth.createDeviceCode({
      chainId: chain.id,
      now,
      policy: {
        validate({ chainId, expiry, limits }) {
          validatedChainId = chainId
          return {
            expiry: expiry ?? defaultExpiry,
            ...(limits ? { limits } : {}),
          }
        },
      },
      random: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]),
      request,
      store,
      ttlMs: 30_000,
    })
    const entry = await store.get(result.code)

    expect({ entry, validatedChainId }).toMatchInlineSnapshot(`
      {
        "entry": {
          "chainId": 1337n,
          "code": "ABCDEFGH",
          "codeChallenge": "NUwjc1h8PuXcsvSOG44Rp4bMayBXnOkriHEJ19CaSQM",
          "createdAt": 1000,
          "expiresAt": 31000,
          "expiry": 4600,
          "keyType": "secp256k1",
          "pubKey": "${secpAccessKey.publicKey}",
          "status": "pending",
        },
        "validatedChainId": 1337n,
      }
    `)
  })
})

describe('pending', () => {
  test('default: returns request details for a pending entry', async () => {
    const store = CliAuth.Store.memory()
    const { request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      random: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]),
      request,
      store,
    })

    const result = await CliAuth.pending({
      code,
      store,
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "accessKeyAddress": "${accessKey.address.toLowerCase()}",
        "chainId": 1337n,
        "code": "ABCDEFGH",
        "expiry": ${expiry},
        "keyType": "p256",
        "limits": [
          {
            "limit": 1000n,
            "token": "0x20c0000000000000000000000000000000000001",
          },
        ],
        "pubKey": "${accessKey.publicKey}",
        "status": "pending",
      }
    `)
  })

  test('behavior: handler returns 404 for an unknown code', async () => {
    const handler = Handler.codeAuth()

    const result = await get(handler, {
      url: 'http://localhost/auth/pkce/pending/ABCDEFGH',
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "body": {
          "error": "Unknown device code.",
        },
        "status": 404,
      }
    `)
  })

  test('behavior: handler returns 400 for a completed code', async () => {
    const store = CliAuth.Store.memory()
    const handler = Handler.codeAuth({
      chains: [chain],
      store,
    })
    const { codeVerifier, request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      random: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]),
      request,
      store,
    })

    await CliAuth.authorize({
      chainId: chain.id,
      request: await authorize(code),
      store,
    })
    await CliAuth.poll({
      code,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    const result = await get(handler, {
      url: `http://localhost/auth/pkce/pending/${code}`,
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "body": {
          "error": "Device code already completed.",
        },
        "status": 400,
      }
    `)
  })

  test('behavior: handler accepts a hyphenated code', async () => {
    const store = CliAuth.Store.memory()
    const handler = Handler.codeAuth({
      chains: [chain],
      store,
    })
    const { request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      random: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]),
      request,
      store,
    })

    const result = await get(handler, {
      response: CliAuth.pendingResponse,
      url: `http://localhost/auth/pkce/pending/${code.slice(0, 4)}-${code.slice(4)}`,
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "body": {
          "accessKeyAddress": "${accessKey.address.toLowerCase()}",
          "chainId": 1337n,
          "code": "ABCDEFGH",
          "expiry": ${expiry},
          "keyType": "p256",
          "limits": [
            {
              "limit": 1000n,
              "token": "0x20c0000000000000000000000000000000000001",
            },
          ],
          "pubKey": "${accessKey.publicKey}",
          "status": "pending",
        },
        "status": 200,
      }
    `)
  })
})

describe('poll', () => {
  test('default: returns pending while awaiting authorization', async () => {
    const store = CliAuth.Store.memory()
    const { codeVerifier, request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })

    const result = await CliAuth.poll({
      code,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "status": "pending",
      }
    `)
  })

  test('behavior: rejects a PKCE mismatch', async () => {
    const handler = Handler.codeAuth({
      chains: [chain],
      store: CliAuth.Store.memory(),
    })
    const { request } = await createRequest()
    const created = await post(handler, {
      body: request,
      request: CliAuth.createRequest,
      response: CliAuth.createResponse,
      url: 'http://localhost/auth/pkce/code',
    })

    const result = await post(handler, {
      body: {
        codeVerifier: 'wrong',
      },
      request: CliAuth.pollRequest,
      url: `http://localhost/auth/pkce/poll/${(created.body as z.output<typeof CliAuth.createResponse>).code}`,
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "body": {
          "error": "Invalid code verifier.",
        },
        "status": 400,
      }
    `)
  })

  test('behavior: consumes an authorization exactly once', async () => {
    const store = CliAuth.Store.memory()
    const { codeVerifier, request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })

    await CliAuth.authorize({
      chainId: chain.id,
      request: await authorize(code),
      store,
    })

    const first = await CliAuth.poll({
      code,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })
    const second = await CliAuth.poll({
      code,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    const first_ =
      first.status === 'authorized'
        ? {
            ...first,
            keyAuthorization: {
              ...first.keyAuthorization,
              signature: {
                type: first.keyAuthorization.signature.type,
              },
            },
          }
        : first

    expect({ first: first_, second }).toMatchInlineSnapshot(`
      {
        "first": {
          "accountAddress": "${root.address}",
          "keyAuthorization": {
            "address": "${accessKey.address}",
            "chainId": 1337n,
            "expiry": ${expiry},
            "keyId": "${accessKey.address}",
            "keyType": "p256",
            "limits": [
              {
                "limit": 1000n,
                "token": "0x20c0000000000000000000000000000000000001",
              },
            ],
            "signature": {
              "type": "secp256k1",
            },
          },
          "status": "authorized",
        },
        "second": {
          "status": "expired",
        },
      }
    `)
  })

  test('behavior: accepts a hyphenated code when polling', async () => {
    const store = CliAuth.Store.memory()
    const { codeVerifier, request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })

    const result = await CliAuth.poll({
      code: `${code.slice(0, 4)}-${code.slice(4)}`,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "status": "pending",
      }
    `)
  })

  test('behavior: expires after TTL elapses', async () => {
    let time = 10_000
    const now = () => time
    const store = CliAuth.Store.memory()
    const { codeVerifier, request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      now,
      request,
      store,
      ttlMs: 10,
    })

    time += 11

    const result = await CliAuth.poll({
      code,
      now,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    expect(result).toMatchInlineSnapshot(`
      {
        "status": "expired",
      }
    `)
  })
})

describe('authorize', () => {
  test('default: authorizes and returns the signed key authorization', async () => {
    const store = CliAuth.Store.memory()
    const { codeVerifier, request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })

    const authorized = await CliAuth.authorize({
      chainId: chain.id,
      request: await authorize(code),
      store,
    })
    const polled = await CliAuth.poll({
      code,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    expect(authorized).toMatchInlineSnapshot(`
      {
        "status": "authorized",
      }
    `)
    expect(polled.status).toMatchInlineSnapshot(`"authorized"`)
  })

  test('behavior: accepts user-approved expiry and limit changes', async () => {
    const store = CliAuth.Store.memory()
    const { codeVerifier, request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })
    const approvedLimits = [
      {
        limit: 2_000n,
        token,
      },
    ] as const

    const authorized = await CliAuth.authorize({
      chainId: chain.id,
      request: await authorize(code, { expiry: expiry + 60 * 60 * 24 * 6, limits: approvedLimits }),
      store,
    })
    const polled = await CliAuth.poll({
      code,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    if (polled.status !== 'authorized') throw new Error('Expected device code to be authorized.')

    expect(authorized).toMatchInlineSnapshot(`
      {
        "status": "authorized",
      }
    `)
    expect({ expiry: polled.keyAuthorization.expiry, limits: polled.keyAuthorization.limits })
      .toMatchInlineSnapshot(`
        {
          "expiry": ${expiry + 60 * 60 * 24 * 6},
          "limits": [
            {
              "limit": 2000n,
              "token": "0x20c0000000000000000000000000000000000001",
            },
          ],
        }
      `)
  })

  test('behavior: rejects unsigned expiry and limit changes', async () => {
    const store = CliAuth.Store.memory()
    const { request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })
    const authorized = await authorize(code)

    await expect(
      CliAuth.authorize({
        chainId: chain.id,
        request: {
          ...authorized,
          keyAuthorization: {
            ...authorized.keyAuthorization,
            expiry: expiry + 1,
          },
        },
        store,
      }),
    ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Key authorization signature is invalid.]`)

    await expect(
      CliAuth.authorize({
        chainId: chain.id,
        request: {
          ...authorized,
          keyAuthorization: {
            ...authorized.keyAuthorization,
            limits: [{ limit: limit + 1n, token }],
          },
        },
        store,
      }),
    ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Key authorization signature is invalid.]`)
  })

  test('behavior: rejects a mismatched key authorization', async () => {
    const store = CliAuth.Store.memory()
    const { request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })

    await expect(
      CliAuth.authorize({
        chainId: chain.id,
        request: await authorize(code, { accessKeyAddress: secpAccessKey.address }),
        store,
      }),
    ).rejects.toThrowErrorMatchingInlineSnapshot(
      `[Error: Key authorization key does not match the device-code request.]`,
    )
  })

  test('behavior: accepts a hyphenated code when authorizing', async () => {
    const store = CliAuth.Store.memory()
    const { codeVerifier, request } = await createRequest()
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })
    const displayCode = `${code.slice(0, 4)}-${code.slice(4)}`

    const authorized = await CliAuth.authorize({
      chainId: chain.id,
      request: await authorize(displayCode),
      store,
    })
    const polled = await CliAuth.poll({
      code,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    expect(authorized).toMatchInlineSnapshot(`
      {
        "status": "authorized",
      }
    `)
    expect(polled.status).toMatchInlineSnapshot(`"authorized"`)
  })

  test('behavior: accepts a WebAuthn signature envelope from RPC', async () => {
    const store = CliAuth.Store.memory()
    const { codeVerifier, request } = await createRequest('device-code-verifier', {
      accessKey: secpAccessKey,
      keyType: secpAccessKey.keyType,
    })
    const { code } = await CliAuth.createDeviceCode({
      chainId: chain.id,
      request,
      store,
    })

    const authorized = await CliAuth.authorize({
      chainId: chain.id,
      request: await authorizeWebAuthn(code),
      store,
    })
    const polled = await CliAuth.poll({
      code,
      request: {
        codeVerifier: codeVerifier,
      },
      store,
    })

    const keyAuthorization =
      polled.status === 'authorized'
        ? {
            ...polled.keyAuthorization,
            signature: {
              type: polled.keyAuthorization.signature.type,
            },
          }
        : undefined

    expect({
      authorized,
      polled:
        polled.status === 'authorized'
          ? {
              ...polled,
              keyAuthorization: keyAuthorization,
            }
          : polled,
    }).toMatchInlineSnapshot(`
      {
        "authorized": {
          "status": "authorized",
        },
        "polled": {
          "accountAddress": "${webAuthnRoot.address}",
          "keyAuthorization": {
            "address": "${secpAccessKey.address}",
            "chainId": 1337n,
            "expiry": ${expiry},
            "keyId": "${secpAccessKey.address}",
            "keyType": "secp256k1",
            "limits": [
              {
                "limit": 1000n,
                "token": "0x20c0000000000000000000000000000000000001",
              },
            ],
            "signature": {
              "type": "webAuthn",
            },
          },
          "status": "authorized",
        },
      }
    `)
  })
})

describe('Store.kv', () => {
  test('default: persists encoded entries through KV', async () => {
    const store = CliAuth.Store.kv({
      async delete() {},
      async get<_value = unknown>(_key: string): Promise<_value> {
        throw new Error('Not implemented.')
      },
      async set() {},
    })

    expect(typeof store.create).toMatchInlineSnapshot(`"function"`)
    expect(typeof store.get).toMatchInlineSnapshot(`"function"`)
    expect(typeof store.authorize).toMatchInlineSnapshot(`"function"`)
    expect(typeof store.consume).toMatchInlineSnapshot(`"function"`)
    expect(typeof store.delete).toMatchInlineSnapshot(`"function"`)
  })
})

async function createCodeChallenge(codeVerifier: string) {
  const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
  return Base64.fromBytes(new Uint8Array(hash), { pad: false, url: true })
}
