import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '@platform-modules/db/pglite'
import {
  InventoryItemNotFoundError,
  isInventoryItemNotFoundError,
  isInventoryValidationError,
} from './errors.js'
import { consume } from './consume.js'
import { getAvailability } from './get-availability.js'
import { pushSchema } from './migrate.js'
import { release } from './release.js'
import { reserve } from './reserve.js'
import { inventorySchema } from './schema.js'
import { setInventory } from './set-inventory.js'

const SKU_ID = '88888888-8888-4888-8888-888888888888'
const ABSENT_SKU = '99999999-9999-4999-8999-999999999999'
const NOW = new Date('2026-06-19T12:00:00.000Z')
const PAST_NOW = new Date('2026-06-19T10:00:00.000Z')
const TTL_MS = 15 * 60 * 1000

async function freshDb() {
  const db = createPgliteClient({ schema: inventorySchema })
  await pushSchema(db)
  return db
}

describe('getAvailability', () => {
  it('computes total - sold - reserved for capped stock', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 10 }))

    expect(await getAvailability(db, SKU_ID)).toBe(10)

    const { reservationId } = await db.transaction((tx) =>
      reserve(tx, { skuId: SKU_ID, qty: 3, holderRef: 'h1', now: NOW }),
    )
    expect(await getAvailability(db, SKU_ID)).toBe(7)

    await db.transaction((tx) => release(tx, reservationId))
    expect(await getAvailability(db, SKU_ID)).toBe(10)
  })

  it('returns null for unlimited stock', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: null }))
    expect(await getAvailability(db, SKU_ID)).toBeNull()
  })

  it('recovers availability after consume removes the active hold', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 5 }))

    const { reservationId } = await db.transaction((tx) =>
      reserve(tx, { skuId: SKU_ID, qty: 2, holderRef: 'h1', now: NOW }),
    )
    expect(await getAvailability(db, SKU_ID)).toBe(3)

    await db.transaction((tx) => consume(tx, reservationId))
    expect(await getAvailability(db, SKU_ID)).toBe(3)
  })

  it('throws InventoryItemNotFoundError when the sku row is absent', async () => {
    const db = await freshDb()

    await expect(getAvailability(db, ABSENT_SKU)).rejects.toSatisfy((e) =>
      isInventoryItemNotFoundError(e),
    )

    try {
      await getAvailability(db, ABSENT_SKU)
    } catch (e) {
      expect(e).toBeInstanceOf(InventoryItemNotFoundError)
      expect((e as InventoryItemNotFoundError).skuId).toBe(ABSENT_SKU)
    }
  })

  it('rejects a malformed skuId with InventoryValidationError', async () => {
    const db = await freshDb()
    await expect(getAvailability(db, 'not-a-uuid')).rejects.toSatisfy((e) =>
      isInventoryValidationError(e),
    )
  })

  it('counts expired-but-unswept holds against availability (expiry-blind active)', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 5 }))

    await db.transaction((tx) =>
      reserve(tx, {
        skuId: SKU_ID,
        qty: 2,
        holderRef: 'expired-hold',
        now: PAST_NOW,
        ttlMs: TTL_MS,
      }),
    )

    // expires_at is in the past, but sweep has not run — hold still counts.
    expect(await getAvailability(db, SKU_ID)).toBe(3)
  })
})
