import { eq } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '@platform-modules/db/pglite'
import { createOrder } from './create-order.js'
import { OrderIntegrityError, OrderValidationError, isOrderIdempotencyConflictError, isOrderIntegrityError, isOrderValidationError } from './errors.js'
import { assertOrderIntegrity } from './integrity.js'
import { pushSchema } from './migrate.js'
import { order, orderLine, ordersSchema, vendorSplit } from './schema.js'
import type { NewOrder } from './types.js'

const VARIANT_A = '11111111-1111-4111-8111-111111111111'
const VARIANT_B = '22222222-2222-4222-8222-222222222222'
const BUYER_ID = '33333333-3333-4333-8333-333333333333'

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

function baseOrder(over: Partial<NewOrder> = {}): NewOrder {
  return {
    idempotencyKey: crypto.randomUUID(),
    buyerRef: { userId: BUYER_ID },
    currency: 'USD',
    priceMode: 'exclusive',
    subtotal: 1000n,
    tax: 100n,
    discount: 0n,
    total: 1100n,
    lines: [
      {
        variantId: VARIANT_A,
        kind: 'physical',
        qty: 2,
        unitPrice: 500n,
        lineTotal: 1000n,
        currency: 'USD',
        vendorId: null,
      },
    ],
    splits: [{ vendorId: null, amount: 1100n, funder: 'platform' }],
    ...over,
  }
}

describe('createOrder', () => {
  it('creates a pending order with persisted lines and splits', async () => {
    const db = await freshDb()
    const input = baseOrder()

    const created = await db.transaction((tx) => createOrder(tx, input))

    expect(created.status).toBe('pending')
    expect(created.total).toBe(1100n)
    expect(created.lines).toHaveLength(1)
    expect(created.splits).toHaveLength(1)
    expect(created.fulfillmentState.steps).toEqual({})
    expect(() => assertOrderIntegrity({ ...input, lines: input.lines, splits: input.splits })).not.toThrow()

    const [row] = await db.select().from(order).where(eq(order.id, created.id))
    expect(row?.status).toBe('pending')

    const lines = await db.select().from(orderLine).where(eq(orderLine.orderId, created.id))
    expect(lines).toHaveLength(1)
    expect(lines[0]?.lineTotal).toBe(1000n)

    const splits = await db.select().from(vendorSplit).where(eq(vendorSplit.orderId, created.id))
    expect(splits).toHaveLength(1)
    expect(splits[0]?.amount).toBe(1100n)
  })

  it('rejects mixed currency with OrderValidationError', async () => {
    const db = await freshDb()
    await expect(
      db.transaction((tx) =>
        createOrder(
          tx,
          baseOrder({
            lines: [
              {
                variantId: VARIANT_A,
                kind: 'physical',
                qty: 1,
                unitPrice: 500n,
                lineTotal: 500n,
                currency: 'USD',
              },
              {
                variantId: VARIANT_B,
                kind: 'digital',
                qty: 1,
                unitPrice: 500n,
                lineTotal: 500n,
                currency: 'EUR',
              },
            ],
            subtotal: 1000n,
            total: 1100n,
            tax: 100n,
          }),
        ),
      ),
    ).rejects.toSatisfy((e) => isOrderValidationError(e) && (e as OrderValidationError).field === 'currency')
  })

  it('rejects invalid line qty values with OrderValidationError', async () => {
    const db = await freshDb()
    for (const [qty, lineTotal] of [
      [0, 0n],
      [-1, -500n],
      [1.5, 750n],
    ] as const) {
      await expect(
        db.transaction((tx) =>
          createOrder(
            tx,
            baseOrder({
              lines: [
                {
                  variantId: VARIANT_A,
                  kind: 'physical',
                  qty,
                  unitPrice: 500n,
                  lineTotal,
                  currency: 'USD',
                },
              ],
              subtotal: lineTotal > 0n ? lineTotal : 500n,
              total: lineTotal > 0n ? lineTotal : 500n,
              tax: 0n,
              splits: [{ vendorId: null, amount: lineTotal > 0n ? lineTotal : 500n, funder: 'platform' }],
            }),
          ),
        ),
      ).rejects.toSatisfy((e) => isOrderValidationError(e))
    }
  })

  it('rejects lineTotal mismatch with OrderIntegrityError', async () => {
    const db = await freshDb()
    await expect(
      db.transaction((tx) =>
        createOrder(
          tx,
          baseOrder({
            lines: [
              {
                variantId: VARIANT_A,
                kind: 'physical',
                qty: 2,
                unitPrice: 500n,
                lineTotal: 999n,
                currency: 'USD',
              },
            ],
          }),
        ),
      ),
    ).rejects.toSatisfy((e) => isOrderIntegrityError(e))
  })

  it('rejects both buyer refs and neither buyer ref', async () => {
    const db = await freshDb()

    await expect(
      db.transaction((tx) =>
        createOrder(
          tx,
          baseOrder({
            buyerRef: { userId: BUYER_ID, guestEmail: 'guest@example.com' } as never,
          }),
        ),
      ),
    ).rejects.toSatisfy((e) => isOrderValidationError(e) && (e as OrderValidationError).field === 'buyerRef')

    await expect(
      db.transaction((tx) =>
        createOrder(
          tx,
          baseOrder({
            buyerRef: {} as never,
          }),
        ),
      ),
    ).rejects.toSatisfy((e) => isOrderValidationError(e) && (e as OrderValidationError).field === 'buyerRef')
  })

  it('preserves bigint totals above 2^53 exactly', async () => {
    const db = await freshDb()
    const huge = 9007199254740993n
    const total = huge + 100n

    const created = await db.transaction((tx) =>
      createOrder(
        tx,
        baseOrder({
          subtotal: huge,
          tax: 100n,
          discount: 0n,
          total,
          lines: [
            {
              variantId: VARIANT_A,
              kind: 'digital',
              qty: 1,
              unitPrice: huge,
              lineTotal: huge,
              currency: 'USD',
            },
          ],
          splits: [{ vendorId: null, amount: total, funder: 'platform' }],
        }),
      ),
    )

    expect(created.subtotal).toBe(huge)
    expect(created.total).toBe(total)
    expect(created.subtotal).not.toBe(BigInt(Number(huge)))
    expect(created.subtotal > BigInt(Number.MAX_SAFE_INTEGER)).toBe(true)

    const [row] = await db.select().from(order).where(eq(order.id, created.id))
    expect(row?.subtotal).toBe(huge)
    expect(row?.total).toBe(total)
    expect(row?.subtotal).not.toBe(BigInt(Number(huge)))
  })

  it('create-idempotent-same-key: same key + body returns same order, one row, no duplicate children', async () => {
    const db = await freshDb()
    const key = 'idem-same-key-test'
    const input = baseOrder({ idempotencyKey: key })

    const first = await db.transaction((tx) => createOrder(tx, input))
    const second = await db.transaction((tx) => createOrder(tx, input))

    expect(second.id).toBe(first.id)

    const orderRows = await db.select().from(order)
    expect(orderRows).toHaveLength(1)

    const lines = await db.select().from(orderLine).where(eq(orderLine.orderId, first.id))
    expect(lines).toHaveLength(1)

    const splits = await db.select().from(vendorSplit).where(eq(vendorSplit.orderId, first.id))
    expect(splits).toHaveLength(1)
  })

  it('create-idempotent-body-mismatch: same key + different body throws OrderIdempotencyConflictError, one row', async () => {
    const db = await freshDb()
    const key = 'idem-mismatch-key'
    const original = baseOrder({ idempotencyKey: key })

    await db.transaction((tx) => createOrder(tx, original))

    const mismatched = baseOrder({
      idempotencyKey: key,
      subtotal: 2000n,
      total: 2100n,
      lines: [
        {
          variantId: VARIANT_A,
          kind: 'physical',
          qty: 4,
          unitPrice: 500n,
          lineTotal: 2000n,
          currency: 'USD',
          vendorId: null,
        },
      ],
      splits: [{ vendorId: null, amount: 2100n, funder: 'platform' }],
    })

    await expect(db.transaction((tx) => createOrder(tx, mismatched))).rejects.toSatisfy((e) =>
      isOrderIdempotencyConflictError(e),
    )

    const orderRows = await db.select().from(order)
    expect(orderRows).toHaveLength(1)
    expect(orderRows[0]?.total).toBe(original.total)
  })

  it('create-distinct-keys: different keys create distinct orders', async () => {
    const db = await freshDb()
    const first = await db.transaction((tx) => createOrder(tx, baseOrder({ idempotencyKey: 'key-a' })))
    const second = await db.transaction((tx) => createOrder(tx, baseOrder({ idempotencyKey: 'key-b' })))

    expect(second.id).not.toBe(first.id)

    const orderRows = await db.select().from(order)
    expect(orderRows).toHaveLength(2)
  })
})
