import { describe, expect, it, vi } from 'vitest'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { encodeBase64UrlBytes } from 'relay-contracts'
import { generateIdentity } from '../src/keys/identity.js'
import { openKeystore } from '../src/keys/keystore.js'
import {
  DEFAULT_ENROLL_MODE,
  EnrollError,
  PairingCodeExpiredError,
  PairingCodeSpentError,
  redeemPairingCode,
} from '../src/enroll/pair.js'

const ENROLL_URL = 'https://example.com/enroll'
const VALID_UUID = '11111111-1111-4111-8111-111111111111'

function freshKs() {
  const dir = mkdtempSync(join(tmpdir(), 'wta-pair-'))
  return { dir, ks: openKeystore(dir) }
}

function jsonResponse(status: number, body: unknown): Response {
  return {
    ok: status >= 200 && status < 300,
    status,
    json: async () => body,
  } as unknown as Response
}

function okBody(wrappedSecret: Uint8Array) {
  return {
    hostId: VALID_UUID,
    subdomain: 'host-42',
    cert: 'CERTPEM',
    caChain: 'CAPEM',
    hostContentSecret: encodeBase64UrlBytes(wrappedSecret),
  }
}

describe('redeemPairingCode (§4.5, T4)', () => {
  it('defaults to ed25519 mode', () => {
    expect(DEFAULT_ENROLL_MODE).toBe('ed25519')
  })

  it('sends only pubkey + csr, never the private key (INV4)', async () => {
    const { dir, ks } = freshKs()
    const id = generateIdentity()
    const fetchImpl = vi.fn(async (_u: string | URL | Request, init?: RequestInit) => {
      const body = JSON.parse(String(init!.body)) as Record<string, unknown>
      expect(body).toHaveProperty('agentPubkey')
      expect(body).toHaveProperty('csr')
      expect(JSON.stringify(body)).not.toContain('PRIVATE KEY')
      expect(body).not.toHaveProperty('privateKey')
      return jsonResponse(200, okBody(new Uint8Array([9, 9, 9])))
    })
    await redeemPairingCode(ENROLL_URL, 'ABCD-1234', id, ks, { fetchImpl: fetchImpl as unknown as typeof fetch })
    rmSync(dir, { recursive: true, force: true })
  })

  it('stores cert + unwrapped content secret; wrapped bytes not persisted (FIX 3)', async () => {
    const { dir, ks } = freshKs()
    const id = generateIdentity()
    const wrapped = new Uint8Array([1, 2, 3])
    const unwrapped = new Uint8Array([7, 7, 7])
    const fetchImpl = async () => jsonResponse(200, okBody(wrapped))
    await redeemPairingCode(ENROLL_URL, 'ABCD-1234', id, ks, {
      fetchImpl: fetchImpl as unknown as typeof fetch,
      unwrapContentSecret: () => unwrapped,
    })
    expect(ks.loadCert()).toEqual({ certPem: 'CERTPEM', caChainPem: 'CAPEM' })
    const stored = ks.loadContentSecret()!
    expect(Buffer.from(stored).equals(Buffer.from(unwrapped))).toBe(true)
    expect(Buffer.from(stored).equals(Buffer.from(wrapped))).toBe(false)
    rmSync(dir, { recursive: true, force: true })
  })

  it('maps 409 → PairingCodeSpentError (single-use)', async () => {
    const { dir, ks } = freshKs()
    const fetchImpl = async () => jsonResponse(409, {})
    await expect(
      redeemPairingCode(ENROLL_URL, 'X', generateIdentity(), ks, { fetchImpl: fetchImpl as unknown as typeof fetch }),
    ).rejects.toBeInstanceOf(PairingCodeSpentError)
    rmSync(dir, { recursive: true, force: true })
  })

  it('maps 410 → PairingCodeExpiredError', async () => {
    const { dir, ks } = freshKs()
    const fetchImpl = async () => jsonResponse(410, {})
    await expect(
      redeemPairingCode(ENROLL_URL, 'X', generateIdentity(), ks, { fetchImpl: fetchImpl as unknown as typeof fetch }),
    ).rejects.toBeInstanceOf(PairingCodeExpiredError)
    rmSync(dir, { recursive: true, force: true })
  })

  it('rejects a schema-mismatched response (boundary validation)', async () => {
    const { dir, ks } = freshKs()
    const fetchImpl = async () => jsonResponse(200, { hostId: 'not-a-uuid', subdomain: 'x' })
    await expect(
      redeemPairingCode(ENROLL_URL, 'X', generateIdentity(), ks, { fetchImpl: fetchImpl as unknown as typeof fetch }),
    ).rejects.toBeInstanceOf(EnrollError)
    rmSync(dir, { recursive: true, force: true })
  })
})
