import { runInNewContext } from 'node:vm'
import { describe, expect, it } from 'vitest'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import { decodeWorkerJson, encodeWorkerJson, snapshotCodeJsonValue } from '../src/worker-json.ts'

describe('snapshotCodeJsonValue', () => {
  it('matches the canonical scalar boundary', () => {
    const unsupported = [undefined, 1n, Symbol('value'), () => 1]
    for (const value of [null, false, 'text', 1.25, -0, Number.NaN, Number.POSITIVE_INFINITY, ...unsupported]) {
      expect(snapshotCodeJsonValue(value)).toEqual(snapshotJsonValue(value))
    }
  })

  it('detaches dense arrays and plain or null-prototype records', () => {
    const shared = { value: 1 }
    const nullPrototype = Object.assign(Object.create(null) as Record<string, unknown>, { shared })
    const source = { list: [nullPrototype, shared], alias: shared }

    const snapshot = snapshotCodeJsonValue(source) as Record<string, unknown>
    shared.value = 2

    expect(snapshot).toEqual({ list: [{ shared: { value: 1 } }, { value: 1 }], alias: { value: 1 } })
    expect(snapshot).not.toBe(source)
    expect((snapshot.list as unknown[])[0]).not.toBe(nullPrototype)
    expect(snapshot.alias).not.toBe(shared)
  })

  it('accepts intrinsic plain containers from another JavaScript realm', () => {
    const foreign = runInNewContext('({ object: { nested: [1] }, array: [2, { ok: true }] })') as {
      object: unknown
      array: unknown
    }

    expect(snapshotCodeJsonValue(foreign.object)).toEqual({ nested: [1] })
    expect(snapshotCodeJsonValue(foreign.array)).toEqual([2, { ok: true }])
  })

  it('reads each accepted slot once and preserves a literal __proto__ key', () => {
    let objectReads = 0
    let arrayReads = 0
    const source = Object.create(null) as Record<string, unknown>
    Object.defineProperty(source, '__proto__', {
      enumerable: true,
      get: () => {
        objectReads += 1
        return { safe: true }
      },
    })
    const array = new Array<unknown>(1)
    Object.defineProperty(array, 0, {
      enumerable: true,
      get: () => {
        arrayReads += 1
        return arrayReads === 1 ? source : undefined
      },
    })

    const snapshot = snapshotCodeJsonValue(array) as Record<string, unknown>[]

    expect(objectReads).toBe(1)
    expect(arrayReads).toBe(1)
    expect(Object.getPrototypeOf(snapshot[0])).toBe(Object.prototype)
    expect(Object.hasOwn(snapshot[0]!, '__proto__')).toBe(true)
    expect(snapshot[0]?.['__proto__']).toEqual({ safe: true })
  })

  it('accepts deeply nested valid JSON without using the JavaScript call stack', () => {
    let value: unknown = 'leaf'
    for (let depth = 0; depth < 5_000; depth++) value = [value]

    let cursor = snapshotCodeJsonValue(value)
    for (let depth = 0; depth < 5_000; depth++) {
      expect(Array.isArray(cursor)).toBe(true)
      cursor = Array.isArray(cursor) ? cursor[0] : undefined
    }
    expect(cursor).toBe('leaf')
  })

  it('rejects exotic containers, sparse arrays, cycles, and invalid children', () => {
    class ExoticObject {
      readonly value = 1
    }
    class ExoticArray extends Array<number> {}
    const cyclic: Record<string, unknown> = {}
    cyclic.self = cyclic
    const decorated = [1]
    Object.defineProperty(decorated, 'extra', { value: true })
    const compensatedSparse = new Array(1)
    Object.defineProperty(compensatedSparse, 'extra', { value: true })
    const symbolDecorated = [1]
    Object.defineProperty(symbolDecorated, Symbol('extra'), { value: true })
    const hiddenObject = Object.defineProperty({}, 'hidden', { value: true })
    const symbolObject = { [Symbol('extra')]: true }
    const customPrototype = Object.create(null) as Record<string, unknown>
    const customPrototypeObject = Object.assign(Object.create(customPrototype) as Record<string, unknown>, { value: 1 })
    const forgedPrototype: unknown[] = []
    Object.setPrototypeOf(forgedPrototype, null)
    const forgedArray = [1]
    Object.setPrototypeOf(forgedArray, forgedPrototype)
    const spoofedObjectPrototype = Object.create(null) as Record<string, unknown>
    const SpoofedObject = function Object() {}
    SpoofedObject.prototype = spoofedObjectPrototype
    Object.defineProperty(spoofedObjectPrototype, 'constructor', { value: SpoofedObject })
    const spoofedObject = Object.create(spoofedObjectPrototype) as Record<string, unknown>
    spoofedObject.value = 1
    const revokedPrototype = Object.create(null) as Record<string, unknown>
    const RevokedObject = function Object() {}
    RevokedObject.prototype = revokedPrototype
    const revokedConstructor = Proxy.revocable(RevokedObject, {})
    Object.defineProperty(revokedPrototype, 'constructor', { value: revokedConstructor.proxy })
    const revokedObject = Object.create(revokedPrototype) as Record<string, unknown>
    revokedConstructor.revoke()
    const spoofedArrayPrototype: unknown[] = []
    Object.setPrototypeOf(spoofedArrayPrototype, Object.prototype)
    const SpoofedArray = function Array() {}
    SpoofedArray.prototype = spoofedArrayPrototype
    Object.defineProperty(spoofedArrayPrototype, 'constructor', { value: SpoofedArray })
    const spoofedArray = [1]
    Object.setPrototypeOf(spoofedArray, spoofedArrayPrototype)

    for (const value of [
      new ExoticObject(),
      new Map([['value', 1]]),
      new ExoticArray(1),
      new Array(1),
      decorated,
      compensatedSparse,
      symbolDecorated,
      hiddenObject,
      symbolObject,
      customPrototypeObject,
      forgedArray,
      spoofedObject,
      revokedObject,
      spoofedArray,
      cyclic,
      [undefined],
      { value: undefined },
    ]) {
      const canonical = snapshotJsonValue(value)
      expect(canonical).toBeUndefined()
      expect(snapshotCodeJsonValue(value)).toEqual(canonical)
    }
  })

  it('rejects an array whose getter mutates the validated length', () => {
    const array = [0, 2]
    Object.defineProperty(array, 0, {
      enumerable: true,
      get: () => {
        array.length = 1
        return 1
      },
    })

    expect(snapshotCodeJsonValue(array)).toBeUndefined()
  })

  it('propagates a throwing getter and releases its recursion guard', () => {
    const failure = new Error('getter failed')
    const source = Object.defineProperty({}, 'value', {
      enumerable: true,
      get: () => { throw failure },
    })

    expect(() => snapshotCodeJsonValue(source)).toThrow(failure)
    expect(snapshotCodeJsonValue({ after: true })).toEqual({ after: true })
  })
})

describe('flat worker JSON wire', () => {
  it('round-trips every JSON root while preserving object keys and container order', () => {
    const withPrototypeKey = Object.create(null) as Record<string, unknown>
    withPrototypeKey.__proto__ = { safe: true }
    const values = [null, false, true, 1.25, 'text', [], {}, [1, { nested: [2] }], withPrototypeKey]
    for (const value of values) {
      const snapshot = snapshotCodeJsonValue(value)
      expect(snapshot).not.toBeUndefined()
      expect(decodeWorkerJson(encodeWorkerJson(snapshot!))).toEqual(snapshot)
    }
    const decoded = decodeWorkerJson(encodeWorkerJson(snapshotCodeJsonValue(withPrototypeKey)!)) as Record<string, unknown>
    expect(Object.hasOwn(decoded, '__proto__')).toBe(true)
    expect(decoded.__proto__).toEqual({ safe: true })
  })

  it('round-trips deep values through a bounded-depth token array', () => {
    let value: unknown = 'leaf'
    for (let depth = 0; depth < 5_000; depth++) value = [value]
    const snapshot = snapshotCodeJsonValue(value)!
    const wire = encodeWorkerJson(snapshot)
    expect(wire).toHaveLength(5_001)

    let cursor = decodeWorkerJson(wire)
    for (let depth = 0; depth < 5_000; depth++) {
      expect(Array.isArray(cursor)).toBe(true)
      cursor = Array.isArray(cursor) ? cursor[0] : undefined
    }
    expect(cursor).toBe('leaf')
  })

  it('rejects malformed, incomplete, lossy, sparse, decorated, and throwing wire values', () => {
    const sparse = new Array(1)
    const compensatedSparse = new Array(1)
    Object.defineProperty(compensatedSparse, 'extra', { value: true })
    const decorated: unknown[] = [null]
    Object.defineProperty(decorated, 'extra', { value: true })
    const throwing: unknown[] = []
    Object.defineProperty(throwing, 0, { enumerable: true, get: () => { throw new Error('wire getter') } })
    const decoratedKeys: unknown[] = ['x']
    Object.defineProperty(decoratedKeys, 'extra', { value: true })
    const foreignMarker: Record<string, unknown> = { kind: 'array', length: 0 }
    Object.setPrototypeOf(foreignMarker, {})
    const hiddenMarker = Object.defineProperty({ kind: 'array', length: 0 }, 'hidden', { value: true })

    for (const value of [
      undefined,
      null,
      {},
      [],
      sparse,
      compensatedSparse,
      decorated,
      throwing,
      [undefined],
      [-0],
      [Number.NaN],
      [Number.POSITIVE_INFINITY],
      [1, 2],
      [[]],
      [foreignMarker],
      [hiddenMarker],
      [{ kind: 'unknown' }],
      [{ kind: 'array', bogus: 0 }],
      [{ kind: 'array' }],
      [{ kind: 'array', length: '1' }],
      [{ kind: 'array', length: -1 }],
      [{ kind: 'array', length: Number.MAX_SAFE_INTEGER + 1 }],
      [{ kind: 'array', length: 1 }],
      [{ kind: 'array', length: 2 }, { kind: 'array', length: 1 }, null],
      [{ kind: 'array', length: 0, extra: true }],
      [{ kind: 'object' }],
      [{ kind: 'object', keys: 'x' }],
      [{ kind: 'object', keys: decoratedKeys }],
      [{ kind: 'object', keys: [1] }],
      [{ kind: 'object', keys: ['x', 'x'] }, 1, 2],
      [{ kind: 'object', keys: ['x'] }],
      [{ kind: 'object', keys: [], extra: true }],
    ]) {
      expect(decodeWorkerJson(value)).toBeUndefined()
    }
  })

  it('rejects invalid values passed through a forged static type', () => {
    expect(() => encodeWorkerJson([undefined] as never)).toThrow(/sparse JSON array/)
    expect(() => encodeWorkerJson({ value: undefined } as never)).toThrow(/undefined JSON object property/)
  })
})
