import { sql } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { isInventoryValidationError, isOversoldError } from './errors.js'
import { postReceipt } from './post-receipt.js'
import { transferStock } from './transfer-stock.js'
import {
  FIFO_ITEM_ID,
  LOCATION_A,
  LOCATION_B,
  NOW,
  TENANT_ID,
  firstRows,
  freshDb,
  seedItem,
  seedLocation,
} from './test-helpers.js'

describe('transferStock', () => {
  it('moves stock in one transaction and carries cost across', async () => {
    const db = await freshDb()
    await seedLocation(db, LOCATION_A)
    await seedLocation(db, LOCATION_B)
    await seedItem(db, FIFO_ITEM_ID, 'fifo')

    await postReceipt(db, {
      tenantId: TENANT_ID,
      itemId: FIFO_ITEM_ID,
      locationId: LOCATION_A,
      qty: 2,
      unitCost: 3,
      holderRef: 'receipt-a-1',
      occurredAt: new Date('2026-07-04T10:00:00.000Z'),
    })
    await postReceipt(db, {
      tenantId: TENANT_ID,
      itemId: FIFO_ITEM_ID,
      locationId: LOCATION_A,
      qty: 2,
      unitCost: 5,
      holderRef: 'receipt-a-2',
      occurredAt: new Date('2026-07-04T11:00:00.000Z'),
    })

    const result = await transferStock(db, {
      tenantId: TENANT_ID,
      itemId: FIFO_ITEM_ID,
      fromLocationId: LOCATION_A,
      toLocationId: LOCATION_B,
      qty: 3,
      holderRef: 'transfer-1',
      occurredAt: NOW,
    })

    expect(result.transferOut.transferGroup).toBe(result.transferIn.transferGroup)
    expect(Number(result.transferOut.cogsAmount)).toBe(11)
    expect(Number(result.transferIn.unitCost)).toBeCloseTo(11 / 3, 6)

    const locationB = firstRows(await db.execute(sql`
      SELECT COALESCE(SUM(qty_delta), 0)::numeric AS qty
      FROM stock_movement
      WHERE tenant_id = ${TENANT_ID}::uuid
        AND item_id = ${FIFO_ITEM_ID}::uuid
        AND location_id = ${LOCATION_B}::uuid
    `))
    expect(Number(locationB[0]?.qty ?? 0)).toBe(3)
  })

  it('fails closed when transfer_out would oversell', async () => {
    const db = await freshDb()
    await seedLocation(db, LOCATION_A)
    await seedLocation(db, LOCATION_B)
    await seedItem(db, FIFO_ITEM_ID, 'fifo')

    await expect(
      transferStock(db, {
        tenantId: TENANT_ID,
        itemId: FIFO_ITEM_ID,
        fromLocationId: LOCATION_A,
        toLocationId: LOCATION_B,
        qty: 1,
        holderRef: 'transfer-oversell',
        occurredAt: NOW,
      }),
    ).rejects.toSatisfy((error) => isOversoldError(error))
  })

  it('replaying the same holderRef returns the existing pair without adding movements', async () => {
    const db = await freshDb()
    await seedLocation(db, LOCATION_A)
    await seedLocation(db, LOCATION_B)
    await seedItem(db, FIFO_ITEM_ID, 'fifo')

    await postReceipt(db, {
      tenantId: TENANT_ID,
      itemId: FIFO_ITEM_ID,
      locationId: LOCATION_A,
      qty: 4,
      unitCost: 7,
      holderRef: 'receipt-idempotent',
      occurredAt: NOW,
    })

    const beforeCount = firstRows(await db.execute(sql`
      SELECT COUNT(*)::int AS count
      FROM stock_movement
      WHERE tenant_id = ${TENANT_ID}::uuid
    `))

    const first = await transferStock(db, {
      tenantId: TENANT_ID,
      itemId: FIFO_ITEM_ID,
      fromLocationId: LOCATION_A,
      toLocationId: LOCATION_B,
      qty: 2,
      holderRef: 'transfer-idempotent',
      occurredAt: NOW,
    })
    const replay = await transferStock(db, {
      tenantId: TENANT_ID,
      itemId: FIFO_ITEM_ID,
      fromLocationId: LOCATION_A,
      toLocationId: LOCATION_B,
      qty: 2,
      holderRef: 'transfer-idempotent',
      occurredAt: NOW,
    })

    expect(replay.transferOut.id).toBe(first.transferOut.id)
    expect(replay.transferIn.id).toBe(first.transferIn.id)

    const afterCount = firstRows(await db.execute(sql`
      SELECT COUNT(*)::int AS count
      FROM stock_movement
      WHERE tenant_id = ${TENANT_ID}::uuid
    `))
    expect(Number(afterCount[0]?.count ?? 0)).toBe(Number(beforeCount[0]?.count ?? 0) + 2)
  })

  it('rejects self-transfer as InventoryValidationError', async () => {
    const db = await freshDb()
    await seedLocation(db, LOCATION_A)
    await seedItem(db, FIFO_ITEM_ID, 'fifo')

    await expect(
      transferStock(db, {
        tenantId: TENANT_ID,
        itemId: FIFO_ITEM_ID,
        fromLocationId: LOCATION_A,
        toLocationId: LOCATION_A,
        qty: 1,
        holderRef: 'transfer-self',
        occurredAt: NOW,
      }),
    ).rejects.toSatisfy((error) => isInventoryValidationError(error))
  })
})
