import { describe, expect, it } from 'vitest'
import { reviveOrder, reviveOrderPage } from './revive.js'
import { isOrderWireError } from './errors.js'

function wireOrder(over: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    id: 'o1',
    buyerRef: { userId: 'u1' },
    status: 'paid',
    currency: 'USD',
    priceMode: 'exclusive',
    subtotal: '1000',
    tax: '90',
    discount: '0',
    total: '1090',
    lines: [
      { id: 'l1', orderId: 'o1', variantId: 'v1', kind: 'physical', qty: 2, unitPrice: '500', lineTotal: '1000', vendorId: null },
    ],
    splits: [
      { id: 's1', orderId: 'o1', vendorId: 'vend1', amount: '1000', funder: 'vendor' },
    ],
    fulfillmentState: { steps: { shipped: { at: 'x' } } },
    ...over,
  }
}

describe('reviveOrder', () => {
  it('revives all 7 bigint money fields + literals', () => {
    const o = reviveOrder(wireOrder())
    expect(o.subtotal).toBe(1000n)
    expect(o.tax).toBe(90n)
    expect(o.discount).toBe(0n)
    expect(o.total).toBe(1090n)
    expect(o.lines[0]!.unitPrice).toBe(500n)
    expect(o.lines[0]!.lineTotal).toBe(1000n)
    expect(o.splits[0]!.amount).toBe(1000n)
    expect(o.status).toBe('paid')
    expect(o.priceMode).toBe('exclusive')
    expect(o.lines[0]!.kind).toBe('physical')
    expect(o.splits[0]!.funder).toBe('vendor')
    expect(o.buyerRef).toEqual({ userId: 'u1' })
    expect(o.fulfillmentState.steps.shipped).toEqual({ at: 'x' })
  })

  it('accepts a guestEmail buyerRef', () => {
    const o = reviveOrder(wireOrder({ buyerRef: { guestEmail: 'a@b.c' } }))
    expect(o.buyerRef).toEqual({ guestEmail: 'a@b.c' })
  })

  it('throws OrderWireError on a non-integer amount string', () => {
    try {
      reviveOrder(wireOrder({ total: '10.5' }))
      throw new Error('should have thrown')
    } catch (e) {
      expect(isOrderWireError(e)).toBe(true)
    }
  })

  it('throws on a non-safe-integer numeric amount (must be string-encoded)', () => {
    expect(() => reviveOrder(wireOrder({ total: 2 ** 53 }))).toThrow()
    expect(isOrderWireError((() => { try { reviveOrder(wireOrder({ total: 2 ** 53 })) } catch (e) { return e } })())).toBe(true)
  })

  it('throws on an unknown status literal', () => {
    expect(isOrderWireError((() => { try { reviveOrder(wireOrder({ status: 'bogus' })) } catch (e) { return e } })())).toBe(true)
  })

  it('throws on an unknown priceMode / kind / funder literal', () => {
    expect(isOrderWireError((() => { try { reviveOrder(wireOrder({ priceMode: 'weird' })) } catch (e) { return e } })())).toBe(true)
    expect(isOrderWireError((() => { try { reviveOrder(wireOrder({ lines: [{ id: 'l', orderId: 'o1', variantId: 'v', kind: 'bad', qty: 1, unitPrice: '1', lineTotal: '1', vendorId: null }] })) } catch (e) { return e } })())).toBe(true)
    expect(isOrderWireError((() => { try { reviveOrder(wireOrder({ splits: [{ id: 's', orderId: 'o1', vendorId: null, amount: '1', funder: 'bad' }] })) } catch (e) { return e } })())).toBe(true)
  })

  it('throws on a malformed buyerRef (neither userId nor guestEmail)', () => {
    expect(isOrderWireError((() => { try { reviveOrder(wireOrder({ buyerRef: { nope: 'x' } })) } catch (e) { return e } })())).toBe(true)
  })

  it('throws on a non-object input', () => {
    expect(isOrderWireError((() => { try { reviveOrder(null) } catch (e) { return e } })())).toBe(true)
  })
})

describe('reviveOrderPage', () => {
  it('revives items + validates nextCursor', () => {
    const p = reviveOrderPage({ items: [wireOrder()], nextCursor: 'c1' })
    expect(p.items[0]!.total).toBe(1090n)
    expect(p.nextCursor).toBe('c1')
  })

  it('accepts null nextCursor', () => {
    const p = reviveOrderPage({ items: [], nextCursor: null })
    expect(p.nextCursor).toBeNull()
  })

  it('throws on a bad nextCursor type', () => {
    expect(isOrderWireError((() => { try { reviveOrderPage({ items: [], nextCursor: 5 }) } catch (e) { return e } })())).toBe(true)
  })

  it('throws on a non-array items', () => {
    expect(isOrderWireError((() => { try { reviveOrderPage({ items: 'x', nextCursor: null }) } catch (e) { return e } })())).toBe(true)
  })
})

describe('wire-trust hardening (uniform -react sweep)', () => {
  it('amount string over 21 digits → OrderWireError (bounded BigInt parse, no DoS)', () => {
    for (const long of ['9'.repeat(22), '-' + '9'.repeat(22), '1'.repeat(1_000_000)]) {
      try {
        reviveOrder(wireOrder({ subtotal: long }))
        throw new Error('should have thrown')
      } catch (e) {
        expect(isOrderWireError(e)).toBe(true)
      }
    }
  })

  it('21-digit amount string (boundary) still parses exactly', () => {
    const max = '9'.repeat(21)
    const o = reviveOrder(wireOrder({ subtotal: max, total: '-' + max }))
    expect(o.subtotal).toBe(BigInt(max))
    expect(o.total).toBe(-BigInt(max))
  })

  it('own __proto__ on wire fulfillmentState.steps is stripped (embedded verbatim in the Order)', () => {
    const steps = { shipped: { at: 'x' } } as Record<string, unknown>
    Object.defineProperty(steps, '__proto__', { value: { polluted: true }, enumerable: true, configurable: true, writable: true })
    const o = reviveOrder(wireOrder({ fulfillmentState: { steps } }))
    const revivedSteps = o.fulfillmentState.steps as unknown as Record<string, unknown>
    expect(Object.getOwnPropertyDescriptor(revivedSteps, '__proto__')).toBeUndefined()
    const copy = Object.assign({}, revivedSteps) as { polluted?: boolean }
    expect(copy.polluted).toBeUndefined()
  })
})
