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 { isOrderValidationError } from './errors.js'
import { listOrders } from './list-orders.js'
import { pushSchema } from './migrate.js'
import { order, ordersSchema } from './schema.js'
import { ORDER_LIST_MAX_LIMIT, type Actor, type NewOrder } from './types.js'

const VARIANT_A = '11111111-1111-4111-8111-111111111111'
const BUYER_A = '33333333-3333-4333-8333-333333333333'
const BUYER_B = '44444444-4444-4444-8444-444444444444'
const ADMIN_ACTOR: Actor = { isAdmin: true }
const VENDOR_A = 'vendor-a'
const VENDOR_B = 'vendor-b'

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

function baseOrder(
  buyerId: string,
  over: { vendorId?: string | null; status?: string } = {},
): NewOrder {
  const vendorId = over.vendorId ?? null
  return {
    idempotencyKey: crypto.randomUUID(),
    buyerRef: { userId: buyerId },
    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,
      },
    ],
    splits: [{ vendorId, amount: 1100n, funder: vendorId ? 'vendor' : 'platform' }],
  }
}

async function seedBuyerOrders(db: Awaited<ReturnType<typeof freshDb>>, buyerId: string, count: number) {
  const ids: string[] = []
  for (let i = 0; i < count; i++) {
    const created = await db.transaction((tx) => createOrder(tx, baseOrder(buyerId)))
    ids.push(created.id)
  }
  return ids
}

describe('listOrders', () => {
  it('returns only each buyer own orders when two buyers are seeded', async () => {
    const db = await freshDb()
    const buyerAOrders = await seedBuyerOrders(db, BUYER_A, 2)
    const buyerBOrders = await seedBuyerOrders(db, BUYER_B, 2)

    const pageA = await listOrders(db, { userId: BUYER_A })
    const pageB = await listOrders(db, { userId: BUYER_B })

    expect(pageA.items).toHaveLength(2)
    expect(pageB.items).toHaveLength(2)
    expect(new Set(pageA.items.map((o) => o.id))).toEqual(new Set(buyerAOrders))
    expect(new Set(pageB.items.map((o) => o.id))).toEqual(new Set(buyerBOrders))
    expect(pageA.items.every((o) => 'userId' in o.buyerRef && o.buyerRef.userId === BUYER_A)).toBe(
      true,
    )
  })

  it('scopes vendor list to orders containing own-vendor lines', async () => {
    const db = await freshDb()
    const vendorOrder = await db.transaction((tx) =>
      createOrder(tx, baseOrder(BUYER_A, { vendorId: VENDOR_A })),
    )
    await db.transaction((tx) => createOrder(tx, baseOrder(BUYER_B, { vendorId: VENDOR_B })))

    const page = await listOrders(db, { vendorId: VENDOR_A })
    expect(page.items).toHaveLength(1)
    expect(page.items[0]?.id).toBe(vendorOrder.id)
  })

  it('returns all orders for admin', async () => {
    const db = await freshDb()
    await seedBuyerOrders(db, BUYER_A, 2)
    await seedBuyerOrders(db, BUYER_B, 2)

    const page = await listOrders(db, ADMIN_ACTOR)
    expect(page.items).toHaveLength(4)
  })

  it('narrows results with a status filter', async () => {
    const db = await freshDb()
    const pending = await db.transaction((tx) => createOrder(tx, baseOrder(BUYER_A)))
    const paid = await db.transaction((tx) => createOrder(tx, baseOrder(BUYER_A)))
    await db.update(order).set({ status: 'paid' }).where(eq(order.id, paid.id))

    const page = await listOrders(db, { userId: BUYER_A }, { status: 'paid' })
    expect(page.items).toHaveLength(1)
    expect(page.items[0]?.id).toBe(paid.id)
    expect(page.items.some((o) => o.id === pending.id)).toBe(false)
  })

  it('clamps an over-cap limit and sets nextCursor when more rows exist', async () => {
    const db = await freshDb()
    await seedBuyerOrders(db, BUYER_A, ORDER_LIST_MAX_LIMIT + 5)

    const page = await listOrders(db, { userId: BUYER_A }, { limit: 500 })
    expect(page.items.length).toBeLessThanOrEqual(ORDER_LIST_MAX_LIMIT)
    expect(page.nextCursor).not.toBeNull()
  })

  it('returns an empty page for an actor with no role', async () => {
    const db = await freshDb()
    await seedBuyerOrders(db, BUYER_A, 2)

    const page = await listOrders(db, {})
    expect(page.items).toEqual([])
    expect(page.nextCursor).toBeNull()
  })

  it('round-trips cursor paging with disjoint pages covering all rows', async () => {
    const db = await freshDb()
    const allIds = await seedBuyerOrders(db, BUYER_A, 5)

    const page1 = await listOrders(db, { userId: BUYER_A }, { limit: 2 })
    expect(page1.items).toHaveLength(2)
    expect(page1.nextCursor).not.toBeNull()

    const page2 = await listOrders(db, { userId: BUYER_A }, { limit: 2, cursor: page1.nextCursor! })
    expect(page2.items).toHaveLength(2)

    const page3 = await listOrders(db, { userId: BUYER_A }, { limit: 2, cursor: page2.nextCursor! })
    expect(page3.items).toHaveLength(1)
    expect(page3.nextCursor).toBeNull()

    const seen = [...page1.items, ...page2.items, ...page3.items].map((o) => o.id)
    expect(new Set(seen).size).toBe(5)
    expect(new Set(seen)).toEqual(new Set(allIds))
    expect(page1.items.every((o) => !page2.items.some((p) => p.id === o.id))).toBe(true)
  })

  it('rejects an invalid status with OrderValidationError', async () => {
    const db = await freshDb()

    await expect(
      listOrders(db, { userId: BUYER_A }, { status: 'not-a-status' as 'pending' }),
    ).rejects.toSatisfy((e) => isOrderValidationError(e))
  })
})
