import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '@platform-modules/db/pglite'
import {
  InventoryItemNotFoundError,
  OversoldError,
  isInventoryItemNotFoundError,
  isInventoryValidationError,
  isOversoldError,
} from './errors.js'
import { commitStock } from './commit-stock.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 = '66666666-6666-4666-8666-666666666666'
const ABSENT_SKU = '77777777-7777-4777-8777-777777777777'

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

describe('commitStock', () => {
  it('increments quantity_sold within cap', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 5 }))

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

  it('throws OversoldError when qty exceeds remaining cap', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 2 }))

    await expect(
      db.transaction((tx) => commitStock(tx, SKU_ID, 3)),
    ).rejects.toSatisfy((e) => isOversoldError(e))

    try {
      await db.transaction((tx) => commitStock(tx, SKU_ID, 3))
    } catch (e) {
      expect(e).toBeInstanceOf(OversoldError)
      expect((e as OversoldError).skuId).toBe(SKU_ID)
      expect((e as OversoldError).requested).toBe(3)
    }
  })

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

    await expect(
      db.transaction((tx) => commitStock(tx, ABSENT_SKU, 1)),
    ).rejects.toSatisfy((e) => isInventoryItemNotFoundError(e))

    try {
      await db.transaction((tx) => commitStock(tx, ABSENT_SKU, 1))
    } catch (e) {
      expect(e).toBeInstanceOf(InventoryItemNotFoundError)
      expect((e as InventoryItemNotFoundError).skuId).toBe(ABSENT_SKU)
    }
  })

  it('always commits for unlimited stock (quantity_total = null)', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: null }))

    await db.transaction((tx) => commitStock(tx, SKU_ID, 1000))
    expect(await getAvailability(db, SKU_ID)).toBeNull()
  })

  it('rejects non-positive and non-integer qty with InventoryValidationError', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 10 }))

    for (const qty of [0, -1, 1.5]) {
      await expect(
        db.transaction((tx) => commitStock(tx, SKU_ID, qty)),
      ).rejects.toSatisfy((e) => isInventoryValidationError(e))
    }
  })

  it('throws OversoldError when an active reservation consumes remaining cap', async () => {
    const db = await freshDb()
    const now = new Date('2026-06-19T12:00:00.000Z')
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 1 }))

    const { reservationId } = await db.transaction((tx) =>
      reserve(tx, { skuId: SKU_ID, qty: 1, holderRef: 'A', now }),
    )

    await expect(
      db.transaction((tx) => commitStock(tx, SKU_ID, 1)),
    ).rejects.toSatisfy((e) => isOversoldError(e))

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

  it('commits for unlimited stock even when active reservations exist', async () => {
    const db = await freshDb()
    const now = new Date('2026-06-19T12:00:00.000Z')
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: null }))

    await db.transaction((tx) =>
      reserve(tx, { skuId: SKU_ID, qty: 1, holderRef: 'A', now }),
    )

    await db.transaction((tx) => commitStock(tx, SKU_ID, 1))
    expect(await getAvailability(db, SKU_ID)).toBeNull()
  })
})
