import { describe, expect, it } from 'vitest'
import { MixedCurrencyError } from './errors.js'
import { createMemoryCartStore } from './testing.js'
import type { Cart, PriceSnapshot } from './types.js'

const usdPrice = (amount: bigint): PriceSnapshot => ({
  amount,
  currency: 'USD',
  priceMode: 'inclusive',
})

const eurPrice = (amount: bigint): PriceSnapshot => ({
  amount,
  currency: 'EUR',
  priceMode: 'inclusive',
})

function cart(id: string, lines: Cart['lines']): Cart {
  const subtotal = lines.reduce(
    (sum, line) => sum + line.price.amount * BigInt(line.qty),
    0n,
  )
  return {
    id,
    currency: lines[0]?.price.currency ?? '',
    lines,
    subtotal,
  }
}

describe('createMemoryCartStore', () => {
  it('load/save roundtrips a cart', async () => {
    const store = createMemoryCartStore()
    const saved = cart('user-1', [
      {
        lineId: 'line-1',
        variantId: 'variant-a',
        qty: 1,
        price: usdPrice(1000n),
        vendorId: null,
      },
    ])

    await store.save(saved)
    expect(await store.load('user-1')).toEqual(saved)
  })

  it('merge sums duplicate variant qty', async () => {
    const store = createMemoryCartStore()
    await store.save(
      cart('guest-1', [
        {
          lineId: 'guest-line',
          variantId: 'variant-a',
          qty: 2,
          price: usdPrice(1000n),
          vendorId: null,
        },
      ]),
    )
    await store.save(
      cart('user-1', [
        {
          lineId: 'user-line',
          variantId: 'variant-a',
          qty: 1,
          price: usdPrice(1200n),
          vendorId: null,
        },
      ]),
    )

    const merged = await store.merge('guest-1', 'user-1')
    expect(merged.lines).toHaveLength(1)
    expect(merged.lines[0]?.qty).toBe(3)
    expect(merged.lines[0]?.price.amount).toBe(1200n)
    expect(merged.subtotal).toBe(3600n)
    expect(await store.load('guest-1')).toBeNull()
    expect(await store.load('user-1')).toEqual(merged)
  })

  it('merge keeps the user-cart price snapshot on duplicate variants', async () => {
    const store = createMemoryCartStore()
    await store.save(
      cart('guest-1', [
        {
          lineId: 'guest-line',
          variantId: 'variant-a',
          qty: 1,
          price: usdPrice(2000n),
          vendorId: null,
        },
      ]),
    )
    await store.save(
      cart('user-1', [
        {
          lineId: 'user-line',
          variantId: 'variant-a',
          qty: 1,
          price: usdPrice(1500n),
          vendorId: null,
        },
      ]),
    )

    const merged = await store.merge('guest-1', 'user-1')
    expect(merged.lines[0]?.price.amount).toBe(1500n)
    expect(merged.lines[0]?.qty).toBe(2)
  })

  it('merge throws MixedCurrencyError on currency mismatch', async () => {
    const store = createMemoryCartStore()
    await store.save(
      cart('guest-1', [
        {
          lineId: 'guest-line',
          variantId: 'variant-a',
          qty: 1,
          price: eurPrice(1000n),
          vendorId: null,
        },
      ]),
    )
    await store.save(
      cart('user-1', [
        {
          lineId: 'user-line',
          variantId: 'variant-b',
          qty: 1,
          price: usdPrice(1000n),
          vendorId: null,
        },
      ]),
    )

    await expect(store.merge('guest-1', 'user-1')).rejects.toBeInstanceOf(MixedCurrencyError)
  })

  it('merge unions disjoint variants', async () => {
    const store = createMemoryCartStore()
    await store.save(
      cart('guest-1', [
        {
          lineId: 'guest-line',
          variantId: 'variant-guest',
          qty: 2,
          price: usdPrice(500n),
          vendorId: null,
        },
      ]),
    )
    await store.save(
      cart('user-1', [
        {
          lineId: 'user-line',
          variantId: 'variant-user',
          qty: 1,
          price: usdPrice(1000n),
          vendorId: null,
        },
      ]),
    )

    const merged = await store.merge('guest-1', 'user-1')
    expect(merged.lines).toHaveLength(2)
    expect(merged.lines.map((line) => line.variantId).sort()).toEqual([
      'variant-guest',
      'variant-user',
    ])
    expect(merged.subtotal).toBe(2000n)
  })
})
