import { describe, expect, it } from 'vitest'
import {
  DownloadNotFoundError,
  FulfillmentValidationError,
  isDownloadNotFoundError,
  isFulfillmentValidationError,
  isLabelPurchaseError,
  isUnfulfillableLineError,
  isVoucherAlreadyRedeemedError,
  isVoucherExpiredError,
  isVoucherWrongVendorError,
  LabelPurchaseError,
  UnfulfillableLineError,
  VoucherAlreadyRedeemedError,
  VoucherExpiredError,
  VoucherWrongVendorError,
} from './errors.js'
import { FulfillmentMigrateError, isFulfillmentMigrateError } from './migrate.js'
import { createMemoryCarrierAdapter, createMemoryStorageAdapter } from './testing.js'
import type {
  AccessGrant,
  Address,
  CarrierTrackResult,
  CarrierWebhookResult,
  FulfillmentNotification,
  FulfillmentResult,
  Label,
  Voucher,
} from './types.js'
import { ownerKeyOf } from './types.js'

describe('createMemoryCarrierAdapter', () => {
  it('increments buyLabelCallCount on each buyLabel call', async () => {
    const carrier = createMemoryCarrierAdapter()
    const args = {
      shipmentId: '00000000-0000-4000-8000-000000000001',
      address: {
        line1: '1 Test St',
        city: 'Testville',
        postalCode: '12345',
        country: 'US',
      } satisfies Address,
      idempotencyKey: 'label:00000000-0000-4000-8000-000000000001',
    }

    expect(carrier.buyLabelCallCount).toBe(0)
    await carrier.buyLabel(args)
    expect(carrier.buyLabelCallCount).toBe(1)
    await carrier.buyLabel(args)
    expect(carrier.buyLabelCallCount).toBe(2)
  })
})

describe('createMemoryStorageAdapter', () => {
  it('returns a deterministic signedUrl for the same key and ttl', async () => {
    const storage = createMemoryStorageAdapter()
    const first = await storage.signedUrl('blob/digital/track-1.zip', 3600)
    const second = await storage.signedUrl('blob/digital/track-1.zip', 3600)

    expect(first.url).toBe(
      'https://memory-storage.test/blob%2Fdigital%2Ftrack-1.zip?ttl=3600',
    )
    expect(second.url).toBe(first.url)
    expect(first.expiresAt!.getTime()).toBeLessThanOrEqual(second.expiresAt!.getTime() + 10)
    expect(first.expiresAt!.getTime()).toBeGreaterThanOrEqual(second.expiresAt!.getTime() - 10)
  })

  it('records put calls', async () => {
    const storage = createMemoryStorageAdapter()
    const body = new Uint8Array([1, 2, 3])
    await storage.put('blob/key', body)
    expect(storage.puts).toEqual([{ key: 'blob/key', body }])
  })
})

describe('error structural guards', () => {
  const plain = new Error('plain')

  it.each([
    [FulfillmentValidationError, isFulfillmentValidationError, new FulfillmentValidationError('field')],
    [VoucherAlreadyRedeemedError, isVoucherAlreadyRedeemedError, new VoucherAlreadyRedeemedError('v-1')],
    [VoucherExpiredError, isVoucherExpiredError, new VoucherExpiredError('v-1')],
    [
      VoucherWrongVendorError,
      isVoucherWrongVendorError,
      new VoucherWrongVendorError({ voucherId: 'v-1', scanningVendorId: 'vendor-b' }),
    ],
    [
      LabelPurchaseError,
      isLabelPurchaseError,
      new LabelPurchaseError('ship-1', 'carrier down'),
    ],
    [
      UnfulfillableLineError,
      isUnfulfillableLineError,
      new UnfulfillableLineError({ orderId: 'o-1', lineId: 'l-1', kind: 'unknown' }),
    ],
    [DownloadNotFoundError, isDownloadNotFoundError, new DownloadNotFoundError('grant-1')],
    [
      FulfillmentMigrateError,
      isFulfillmentMigrateError,
      new FulfillmentMigrateError('create table failed'),
    ],
  ])('%s guard is true on own error and false on plain Error', (_Ctor, guard, own) => {
    expect(guard(own)).toBe(true)
    expect(guard(plain)).toBe(false)
  })
})

describe('types compile', () => {
  it('accepts representative fulfillment shapes', () => {
    const grant: AccessGrant = {
      id: '00000000-0000-4000-8000-000000000010',
      orderId: '00000000-0000-4000-8000-000000000011',
      itemId: 'variant-1',
      ownerKey: ownerKeyOf({ userId: 'user-1' }),
      blobKey: 'blob/digital/track-1.zip',
      createdAt: new Date(),
    }

    const voucher: Voucher = {
      id: '00000000-0000-4000-8000-000000000012',
      orderId: grant.orderId,
      lineId: 'line-1',
      unitIndex: 0,
      vendorId: null,
      state: 'UNREDEEMED',
      expiresAt: null,
      redeemedAt: null,
      createdAt: new Date(),
    }

    const label: Label = {
      id: 'label-1',
      trackingNumber: 'TRACK-001',
    }

    const track: CarrierTrackResult = {
      trackingNumber: label.trackingNumber,
      status: 'in_transit',
    }

    const webhook: CarrierWebhookResult = {
      eventId: 'evt-1',
      trackingNumber: label.trackingNumber,
      status: 'delivered',
    }

    const notification: FulfillmentNotification = {
      kind: 'voucher-issued',
      orderId: grant.orderId,
      lineId: voucher.lineId,
      codes: ['CODE-1'],
    }

    const result: FulfillmentResult = {
      orderId: grant.orderId,
      overall: 'fulfilled',
      lines: [{ lineId: voucher.lineId, kind: 'voucher', voucherIds: [voucher.id] }],
    }

    expect(grant.ownerKey).toBe('user:user-1')
    expect(track.status).toBe('in_transit')
    expect(webhook.eventId).toBe('evt-1')
    expect(notification.kind).toBe('voucher-issued')
    expect(result.overall).toBe('fulfilled')
  })
})
