import { describe, expect, it } from 'vitest'
import {
  CheckoutValidationError,
  FulfillmentIncompleteError,
  OrderNotChargeableError,
  PaymentFailedError,
  isCheckoutValidationError,
  isFulfillmentIncompleteError,
  isOrderNotChargeableError,
  isPaymentFailedError,
} from './errors.js'

describe('CheckoutValidationError', () => {
  it('maps each reason to its httpStatus and carries stale split', () => {
    expect(new CheckoutValidationError('EMPTY_CART').httpStatus).toBe(400)
    expect(new CheckoutValidationError('INVALID_CHARGE_KEY').httpStatus).toBe(400)
    expect(new CheckoutValidationError('STALE_ITEMS').httpStatus).toBe(409)
    expect(new CheckoutValidationError('CURRENCY_MISMATCH').httpStatus).toBe(422)

    const stale = { removed: ['v1'], updated: [] }
    const e = new CheckoutValidationError('STALE_ITEMS', stale)
    expect(e.name).toBe('CheckoutValidationError')
    expect(e.code).toBe('CHECKOUT_VALIDATION')
    expect(e.reason).toBe('STALE_ITEMS')
    expect(e.stale).toBe(stale)
  })
})

describe('OrderNotChargeableError', () => {
  it('carries orderId, code, and 409 status', () => {
    const e = new OrderNotChargeableError('order-1')
    expect(e.name).toBe('OrderNotChargeableError')
    expect(e.code).toBe('ORDER_NOT_CHARGEABLE')
    expect(e.httpStatus).toBe(409)
    expect(e.orderId).toBe('order-1')
  })
})

describe('PaymentFailedError', () => {
  it('wraps the provider cause and reports 402', () => {
    const cause = new Error('provider down')
    const e = new PaymentFailedError(cause)
    expect(e.name).toBe('PaymentFailedError')
    expect(e.code).toBe('PAYMENT_FAILED')
    expect(e.httpStatus).toBe(402)
    expect(e.cause).toBe(cause)
  })
})

describe('FulfillmentIncompleteError', () => {
  it('carries fulfillment context and reports 500', () => {
    const e = new FulfillmentIncompleteError({
      orderId: 'order-1',
      overall: 'unfulfillable',
      failures: [{ lineId: 'line-1', error: 'blobKey' }],
    })
    expect(e.name).toBe('FulfillmentIncompleteError')
    expect(e.code).toBe('FULFILLMENT_INCOMPLETE')
    expect(e.httpStatus).toBe(500)
    expect(e.context.overall).toBe('unfulfillable')
    expect(isFulfillmentIncompleteError(e)).toBe(true)
    expect(isFulfillmentIncompleteError(new Error('x'))).toBe(false)
  })
})

describe('structural type-guards (true + fallback + cross-family)', () => {
  const validation = new CheckoutValidationError('EMPTY_CART')
  const notChargeable = new OrderNotChargeableError('order-1')
  const paymentFailed = new PaymentFailedError(new Error('x'))

  it('isCheckoutValidationError: true for its own, false for siblings + non-objects', () => {
    expect(isCheckoutValidationError(validation)).toBe(true)
    expect(isCheckoutValidationError(notChargeable)).toBe(false)
    expect(isCheckoutValidationError(paymentFailed)).toBe(false)
    expect(isCheckoutValidationError(new Error('plain'))).toBe(false)
    expect(isCheckoutValidationError(null)).toBe(false)
    expect(isCheckoutValidationError(undefined)).toBe(false)
    expect(isCheckoutValidationError('CHECKOUT_VALIDATION')).toBe(false)
    // A look-alike that carries only one of name/code must NOT pass (structural identity).
    expect(isCheckoutValidationError({ code: 'CHECKOUT_VALIDATION' })).toBe(false)
    expect(isCheckoutValidationError({ name: 'CheckoutValidationError' })).toBe(false)
  })

  it('isOrderNotChargeableError: true for its own, false for siblings + non-objects', () => {
    expect(isOrderNotChargeableError(notChargeable)).toBe(true)
    expect(isOrderNotChargeableError(validation)).toBe(false)
    expect(isOrderNotChargeableError(paymentFailed)).toBe(false)
    expect(isOrderNotChargeableError(new Error('plain'))).toBe(false)
    expect(isOrderNotChargeableError(null)).toBe(false)
    expect(isOrderNotChargeableError({ code: 'ORDER_NOT_CHARGEABLE' })).toBe(false)
  })

  it('isPaymentFailedError: true for its own, false for siblings + non-objects', () => {
    expect(isPaymentFailedError(paymentFailed)).toBe(true)
    expect(isPaymentFailedError(validation)).toBe(false)
    expect(isPaymentFailedError(notChargeable)).toBe(false)
    expect(isPaymentFailedError(new Error('plain'))).toBe(false)
    expect(isPaymentFailedError(null)).toBe(false)
    expect(isPaymentFailedError({ code: 'PAYMENT_FAILED' })).toBe(false)
  })
})
