// packages/commerce-catalog-react/src/revive.test.ts
import { describe, expect, it } from 'vitest'
import { reviveProduct, reviveProductPage } from './revive.js'
import { isProductWireError } from './errors.js'

const wireProduct = (over: Record<string, unknown> = {}) => ({
  id: 'p1',
  kind: 'physical',
  vendorId: null,
  slug: 'tee',
  title: 'Tee',
  status: 'active',
  media: [{ key: 'm1', alt: 'a' }],
  tags: ['x'],
  availableFrom: '2026-01-01T00:00:00.000Z',
  availableUntil: null,
  createdAt: '2026-06-01T12:00:00.000Z',
  updatedAt: '2026-06-02T12:00:00.000Z',
  variants: [
    { id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [
      { currency: 'USD', amount: '1999', priceMode: 'exclusive' },
    ] },
  ],
  ...over,
})

describe('reviveProduct', () => {
  it('string amount → bigint, deep over variants[].prices[]', () => {
    const p = reviveProduct(wireProduct())
    expect(p.variants[0]!.prices[0]!.amount).toBe(1999n)
    expect(typeof p.variants[0]!.prices[0]!.amount).toBe('bigint')
  })
  it('number amount → bigint', () => {
    const p = reviveProduct(wireProduct({
      variants: [{ id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [
        { currency: 'USD', amount: 2500, priceMode: 'exclusive' }] }],
    }))
    expect(p.variants[0]!.prices[0]!.amount).toBe(2500n)
  })
  it('unsafe-integer numeric amount → ProductWireError (JSON.parse rounds ≥2^53)', () => {
    const bad = wireProduct({
      variants: [{ id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [
        { currency: 'USD', amount: 9007199254740992, priceMode: 'exclusive' }] }],
    })
    expect(() => reviveProduct(bad)).toThrow()
    try { reviveProduct(bad) } catch (e) { expect(isProductWireError(e)).toBe(true) }
  })
  it('MAX_SAFE_INTEGER numeric amount → bigint (boundary still accepted)', () => {
    const p = reviveProduct(wireProduct({
      variants: [{ id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [
        { currency: 'USD', amount: 9007199254740991, priceMode: 'exclusive' }] }],
    }))
    expect(p.variants[0]!.prices[0]!.amount).toBe(9007199254740991n)
  })
  it('ISO-string → Date on createdAt/updatedAt', () => {
    const p = reviveProduct(wireProduct())
    expect(p.createdAt).toBeInstanceOf(Date)
    expect(p.updatedAt).toBeInstanceOf(Date)
    expect(p.createdAt.toISOString()).toBe('2026-06-01T12:00:00.000Z')
  })
  it('present availableFrom → Date; null availableUntil preserved; absent → undefined', () => {
    const p = reviveProduct(wireProduct())
    expect(p.availableFrom).toBeInstanceOf(Date)
    expect(p.availableUntil).toBeNull()
    const { availableFrom: _a, ...noFrom } = wireProduct()
    const p2 = reviveProduct(noFrom)
    expect(p2.availableFrom).toBeUndefined()
  })
  it('empty/absent variants → []', () => {
    const { variants: _v, ...noVar } = wireProduct()
    expect(reviveProduct(noVar).variants).toEqual([])
  })
  it('malformed amount → ProductWireError', () => {
    const bad = wireProduct({
      variants: [{ id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [
        { currency: 'USD', amount: 'not-a-number', priceMode: 'exclusive' }] }],
    })
    expect(() => reviveProduct(bad)).toThrow()
    try { reviveProduct(bad) } catch (e) { expect(isProductWireError(e)).toBe(true) }
  })
  it('empty / whitespace / hex amount string → ProductWireError (no silent 0n / mis-parse)', () => {
    // BigInt('')/BigInt('  ') → 0n and BigInt('0x10') → 16n; §5 forbids a silent 0n at the wire.
    for (const amount of ['', '   ', '0x10', '19.99', '1e3']) {
      const bad = wireProduct({
        variants: [{ id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [
          { currency: 'USD', amount, priceMode: 'exclusive' }] }],
      })
      expect(() => reviveProduct(bad)).toThrow()
      try { reviveProduct(bad) } catch (e) { expect(isProductWireError(e)).toBe(true) }
    }
  })
  it('negative integer amount string → bigint (signed minor-units allowed)', () => {
    const p = reviveProduct(wireProduct({
      variants: [{ id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [
        { currency: 'USD', amount: '-500', priceMode: 'exclusive' }] }],
    }))
    expect(p.variants[0]!.prices[0]!.amount).toBe(-500n)
  })
  it('unparseable required date → ProductWireError', () => {
    expect(() => reviveProduct(wireProduct({ createdAt: 'garbage' }))).toThrow()
    try { reviveProduct(wireProduct({ createdAt: 'garbage' })) }
    catch (e) { expect(isProductWireError(e)).toBe(true) }
  })
  it('non-object input → ProductWireError', () => {
    expect(() => reviveProduct(null)).toThrow()
    try { reviveProduct(42) } catch (e) { expect(isProductWireError(e)).toBe(true) }
  })
})

describe('reviveProductPage', () => {
  it('maps revival over items and carries counts', () => {
    const page = reviveProductPage({
      items: [wireProduct(), wireProduct({ id: 'p2', slug: 'mug' })],
      total: 2, page: 1, pageSize: 20,
    })
    expect(page.items).toHaveLength(2)
    expect(page.items[0]!.variants[0]!.prices[0]!.amount).toBe(1999n)
    expect(page.items[1]!.createdAt).toBeInstanceOf(Date)
    expect(page.total).toBe(2)
    expect(page.pageSize).toBe(20)
  })
  it('non-array items → ProductWireError', () => {
    try { reviveProductPage({ items: 'nope', total: 0, page: 1, pageSize: 20 }) }
    catch (e) { expect(isProductWireError(e)).toBe(true) }
  })
  it('non-number count (empty string / null / coercible) → ProductWireError (no silent 0)', () => {
    // Number('') and Number(null) are 0; counts are JSON-faithful numbers, so a
    // non-number is corrupt wire and must throw, never silently become 0 (§5).
    for (const total of ['', null, 'abc', undefined]) {
      expect(() => reviveProductPage({ items: [], total, page: 1, pageSize: 20 })).toThrow()
      try { reviveProductPage({ items: [], total, page: 1, pageSize: 20 }) }
      catch (e) { expect(isProductWireError(e)).toBe(true) }
    }
  })
  it('accepts total: 0 — a real empty page keeps its 0 count (not coerced away)', () => {
    const p = reviveProductPage({ items: [], total: 0, page: 1, pageSize: 20 })
    expect(p.items).toEqual([])
    expect(p.total).toBe(0)
    expect(p.page).toBe(1)
    expect(p.pageSize).toBe(20)
  })
  it('negative count → ProductWireError', () => {
    expect(() => reviveProductPage({ items: [], total: -1, page: 1, pageSize: 20 })).toThrow()
    try { reviveProductPage({ items: [], total: -1, page: 1, pageSize: 20 }) }
    catch (e) { expect(isProductWireError(e)).toBe(true) }
  })
  it('fractional count → ProductWireError', () => {
    expect(() => reviveProductPage({ items: [], total: 2, page: 1.5, pageSize: 20 })).toThrow()
    try { reviveProductPage({ items: [], total: 2, page: 1.5, pageSize: 20 }) }
    catch (e) { expect(isProductWireError(e)).toBe(true) }
  })
  it('unsafe-integer count → ProductWireError', () => {
    expect(() => reviveProductPage({ items: [], total: 9007199254740992, page: 1, pageSize: 20 })).toThrow()
    try { reviveProductPage({ items: [], total: 9007199254740992, page: 1, pageSize: 20 }) }
    catch (e) { expect(isProductWireError(e)).toBe(true) }
  })
})

describe('wire-trust hardening (uniform -react sweep)', () => {
  const priced = (amount: unknown) =>
    wireProduct({
      variants: [
        { id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [
          { currency: 'USD', amount, priceMode: 'exclusive' },
        ] },
      ],
    })

  it('amount string over 21 digits → ProductWireError (bounded BigInt parse, no DoS)', () => {
    for (const long of ['9'.repeat(22), '-' + '9'.repeat(22), '1'.repeat(1_000_000)]) {
      try {
        reviveProduct(priced(long))
        throw new Error('should have thrown')
      } catch (e) {
        expect(isProductWireError(e)).toBe(true)
      }
    }
  })

  it('21-digit amount string (boundary) still parses exactly', () => {
    const max = '9'.repeat(21)
    const p = reviveProduct(priced(max))
    expect(p.variants[0]!.prices[0]!.amount).toBe(BigInt(max))
    const neg = reviveProduct(priced('-' + max))
    expect(neg.variants[0]!.prices[0]!.amount).toBe(-BigInt(max))
  })

  it('own __proto__ in wire JSON is stripped — never spread into the revived Product', () => {
    const raw = { ...wireProduct() } as Record<string, unknown>
    Object.defineProperty(raw, '__proto__', { value: { polluted: true }, enumerable: true, configurable: true, writable: true })
    const revived = reviveProduct(raw) as unknown as Record<string, unknown>
    expect(Object.getOwnPropertyDescriptor(revived, '__proto__')).toBeUndefined()
    const copy = Object.assign({}, revived) as { polluted?: boolean }
    expect(copy.polluted).toBeUndefined()
  })

  it('own __proto__ on a nested wire price object is stripped', () => {
    const price = { currency: 'USD', amount: '1999', priceMode: 'exclusive' } as Record<string, unknown>
    Object.defineProperty(price, '__proto__', { value: { polluted: true }, enumerable: true, configurable: true, writable: true })
    const p = reviveProduct(wireProduct({
      variants: [{ id: 'v1', productId: 'p1', sku: 'S', attributes: {}, prices: [price] }],
    }))
    const revivedPrice = p.variants[0]!.prices[0]! as unknown as Record<string, unknown>
    expect(Object.getOwnPropertyDescriptor(revivedPrice, '__proto__')).toBeUndefined()
  })
})
