import { sql } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '@platform-modules/db/pglite'
import { InventoryValidationError, isInventoryValidationError } from './errors.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 = '55555555-5555-4555-8555-555555555555'
const NOW = new Date('2026-06-19T12:00:00.000Z')

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

async function readItem(db: Awaited<ReturnType<typeof freshDb>>, skuId: string) {
  const res = await db.execute(sql`
    SELECT vendor_id, quantity_total, quantity_sold
    FROM inventory_item
    WHERE sku_id = ${skuId}::uuid
  `)
  const rows = (Array.isArray(res) ? res : res.rows) as Array<{
    vendor_id: string | null
    quantity_total: number | null
    quantity_sold: number
  }>
  return rows[0]
}

describe('setInventory', () => {
  it('rejects a malformed skuId with InventoryValidationError', async () => {
    const db = await freshDb()
    await expect(
      db.transaction((tx) => setInventory(tx, { skuId: 'not-a-uuid', quantityTotal: 5 })),
    ).rejects.toSatisfy((e) => isInventoryValidationError(e))
  })

  it('rejects negative and fractional quantityTotal', async () => {
    const db = await freshDb()
    for (const quantityTotal of [-1, 2.5]) {
      await expect(
        db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal })),
      ).rejects.toSatisfy((e) => isInventoryValidationError(e))
    }
  })

  it('upserts a capped item then updates vendorId + quantityTotal on conflict', async () => {
    const db = await freshDb()
    await db.transaction((tx) =>
      setInventory(tx, { skuId: SKU_ID, vendorId: 'v1', quantityTotal: 10 }),
    )

    let row = await readItem(db, SKU_ID)
    expect(Number(row?.quantity_total)).toBe(10)
    expect(row?.vendor_id).toBe('v1')
    expect(Number(row?.quantity_sold)).toBe(0)

    await db.transaction((tx) =>
      setInventory(tx, { skuId: SKU_ID, vendorId: 'v2', quantityTotal: 20 }),
    )
    row = await readItem(db, SKU_ID)
    expect(Number(row?.quantity_total)).toBe(20)
    expect(row?.vendor_id).toBe('v2')
  })

  it('allows unlimited stock (quantityTotal = null)', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: null }))
    const row = await readItem(db, SKU_ID)
    expect(row?.quantity_total).toBeNull()
  })

  it('rejects lowering quantityTotal below already-sold quantity (data-integrity guard)', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 10 }))
    // consume is Wave 2; seed quantity_sold directly to reach the guard.
    await db.execute(sql`
      UPDATE inventory_item SET quantity_sold = 5 WHERE sku_id = ${SKU_ID}::uuid
    `)

    await expect(
      db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 3 })),
    ).rejects.toSatisfy((e) => isInventoryValidationError(e))

    // setting total >= sold is allowed and preserves the existing sold count.
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 5 }))
    const row = await readItem(db, SKU_ID)
    expect(Number(row?.quantity_total)).toBe(5)
    expect(Number(row?.quantity_sold)).toBe(5)
  })

  it('throws InventoryValidationError (the typed class) with the offending field', async () => {
    const db = await freshDb()
    await expect(
      db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: -3 })),
    ).rejects.toMatchObject({
      name: 'InventoryValidationError',
      code: 'INVENTORY_VALIDATION',
      field: 'quantityTotal',
    })
    expect(new InventoryValidationError('quantityTotal')).toBeInstanceOf(InventoryValidationError)
  })

  it('rejects lowering quantityTotal below sold + active reservations (divergence 11)', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 10 }))
    await db.transaction((tx) =>
      reserve(tx, { skuId: SKU_ID, qty: 5, holderRef: 'holderA', now: NOW }),
    )

    await expect(
      db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 4 })),
    ).rejects.toSatisfy((e) => isInventoryValidationError(e))
  })

  it('allows lowering quantityTotal to exactly sold + active reservations', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 10 }))
    await db.transaction((tx) =>
      reserve(tx, { skuId: SKU_ID, qty: 5, holderRef: 'holderA', now: NOW }),
    )

    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 5 }))
    const row = await readItem(db, SKU_ID)
    expect(Number(row?.quantity_total)).toBe(5)
  })

  it('allows lowering quantityTotal to zero after active reservations are released', async () => {
    const db = await freshDb()
    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 10 }))
    const { reservationId } = await db.transaction((tx) =>
      reserve(tx, { skuId: SKU_ID, qty: 5, holderRef: 'holderA', now: NOW }),
    )
    await db.transaction((tx) => release(tx, reservationId))

    await db.transaction((tx) => setInventory(tx, { skuId: SKU_ID, quantityTotal: 0 }))
    const row = await readItem(db, SKU_ID)
    expect(Number(row?.quantity_total)).toBe(0)
  })
})
