import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import { pushSchema } from './migrate.js'
import { getProductById, getProductBySlug, listProducts } from './reads.js'
import {
  catalogSchema,
  category,
  collection,
  product,
  productCategory,
  productCollection,
  variant,
  variantPrice,
} from './schema.js'

const FIXED_NOW = new Date('2026-06-15T12:00:00.000Z')

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

async function seedProduct(
  db: Awaited<ReturnType<typeof freshDb>>,
  values: Partial<typeof product.$inferInsert> & { slug: string; title: string; kind: 'physical' | 'digital' | 'voucher' },
) {
  const [row] = await db
    .insert(product)
    .values({
      kind: values.kind,
      slug: values.slug,
      title: values.title,
      status: values.status ?? 'draft',
      vendorId: values.vendorId ?? null,
      availableFrom: values.availableFrom,
      availableUntil: values.availableUntil,
      tags: values.tags ?? [],
    })
    .returning()
  return row!
}

async function seedVariantWithPrices(
  db: Awaited<ReturnType<typeof freshDb>>,
  productId: string,
  sku: string,
  prices: { currency: string; amount: bigint; priceMode: 'inclusive' | 'exclusive' }[],
) {
  const [v] = await db.insert(variant).values({ productId, sku }).returning()
  for (const p of prices) {
    await db.insert(variantPrice).values({ variantId: v!.id, ...p })
  }
  return v!
}

describe('commerce-catalog reads', () => {
  it('hides draft products from public audience and shows them to admin', async () => {
    const db = await freshDb()
    const row = await seedProduct(db, { kind: 'physical', slug: 'draft-widget', title: 'Draft' })

    expect(await getProductBySlug(db, 'draft-widget', { audience: 'public' })).toBeNull()
    expect(await getProductById(db, row.id, { audience: 'public' })).toBeNull()

    const admin = await getProductBySlug(db, 'draft-widget', { audience: 'admin' })
    expect(admin?.slug).toBe('draft-widget')
    expect(await getProductById(db, row.id, { audience: 'admin' })).not.toBeNull()
  })

  it('excludes products outside the availability window for public reads', async () => {
    const db = await freshDb()

    const future = await seedProduct(db, {
      kind: 'digital',
      slug: 'future-drop',
      title: 'Future',
      status: 'active',
      availableFrom: new Date('2026-07-01T00:00:00.000Z'),
    })
    const expired = await seedProduct(db, {
      kind: 'digital',
      slug: 'past-drop',
      title: 'Past',
      status: 'active',
      availableUntil: new Date('2026-06-01T00:00:00.000Z'),
    })
    await seedProduct(db, {
      kind: 'digital',
      slug: 'live-now',
      title: 'Live',
      status: 'active',
      availableFrom: new Date('2026-06-01T00:00:00.000Z'),
      availableUntil: new Date('2026-07-01T00:00:00.000Z'),
    })

    const opts = { audience: 'public' as const, now: FIXED_NOW }

    expect(await getProductBySlug(db, 'future-drop', opts)).toBeNull()
    expect(await getProductById(db, future.id, opts)).toBeNull()
    expect(await getProductBySlug(db, 'past-drop', opts)).toBeNull()
    expect(await getProductById(db, expired.id, opts)).toBeNull()
    expect(await getProductBySlug(db, 'live-now', opts)).not.toBeNull()
  })

  it('returns null for malformed ids without throwing', async () => {
    const db = await freshDb()
    await seedProduct(db, { kind: 'physical', slug: 'ok', title: 'OK', status: 'active' })

    await expect(getProductById(db, 'not-a-uuid', { audience: 'admin' })).resolves.toBeNull()
    await expect(getProductById(db, '00000000-0000-0000-0000-000000000000', { audience: 'admin' })).resolves.toBeNull()
  })

  it('returns null for invalid slug shape and missing slug', async () => {
    const db = await freshDb()
    await seedProduct(db, { kind: 'physical', slug: 'exists', title: 'Exists', status: 'active' })

    expect(await getProductBySlug(db, 'INVALID', { audience: 'admin' })).toBeNull()
    expect(await getProductBySlug(db, 'no-such-slug', { audience: 'admin' })).toBeNull()
  })

  it('hydrates variants with all multi-currency prices', async () => {
    const db = await freshDb()
    const row = await seedProduct(db, { kind: 'physical', slug: 'multi', title: 'Multi', status: 'active' })
    await seedVariantWithPrices(db, row.id, 'SKU-MC', [
      { currency: 'USD', amount: 1000n, priceMode: 'exclusive' },
      { currency: 'ILS', amount: 3500n, priceMode: 'inclusive' },
      { currency: 'EUR', amount: 900n, priceMode: 'exclusive' },
    ])

    const loaded = await getProductById(db, row.id, { audience: 'admin' })
    expect(loaded?.variants).toHaveLength(1)
    expect(loaded?.variants[0]?.prices).toHaveLength(3)
    expect(loaded?.variants[0]?.prices.map((p) => p.currency).sort()).toEqual(['EUR', 'ILS', 'USD'])
    expect(loaded?.variants[0]?.prices.find((p) => p.currency === 'ILS')?.amount).toBe(3500n)
  })

  it('lists products with pagination and filters', async () => {
    const db = await freshDb()

    const p1 = await seedProduct(db, { kind: 'physical', slug: 'alpha', title: 'Alpha', status: 'active', tags: ['sale'] })
    const p2 = await seedProduct(db, { kind: 'digital', slug: 'beta', title: 'Beta', status: 'active', tags: ['new'] })
    await seedProduct(db, { kind: 'voucher', slug: 'gamma', title: 'Gamma', status: 'draft', tags: ['sale'] })

    const [cat] = await db.insert(category).values({ name: 'Gear', slug: 'gear' }).returning()
    const [col] = await db.insert(collection).values({ name: 'Featured', slug: 'featured' }).returning()
    await db.insert(productCategory).values({ productId: p1.id, categoryId: cat!.id })
    await db.insert(productCollection).values({ collectionId: col!.id, productId: p2.id })

    const page1 = await listProducts(db, { audience: 'admin', page: 1 })
    expect(page1.total).toBe(3)
    expect(page1.page).toBe(1)
    expect(page1.pageSize).toBe(20)
    expect(page1.items).toHaveLength(3)

    const byKind = await listProducts(db, { audience: 'admin', kind: 'digital' })
    expect(byKind.items.map((p) => p.slug)).toEqual(['beta'])

    const byStatus = await listProducts(db, { audience: 'admin', status: 'draft' })
    expect(byStatus.items.map((p) => p.slug)).toEqual(['gamma'])

    const byTag = await listProducts(db, { audience: 'admin', tag: 'sale' })
    expect(byTag.items.map((p) => p.slug).sort()).toEqual(['alpha', 'gamma'])

    const byCategory = await listProducts(db, { audience: 'admin', category: 'gear' })
    expect(byCategory.items.map((p) => p.slug)).toEqual(['alpha'])

    const byCollection = await listProducts(db, { audience: 'admin', collection: 'featured' })
    expect(byCollection.items.map((p) => p.slug)).toEqual(['beta'])

    const publicActive = await listProducts(db, { audience: 'public', now: FIXED_NOW })
    expect(publicActive.items.map((p) => p.slug).sort()).toEqual(['alpha', 'beta'])
  })

  it('scopes list and slug reads by vendorId', async () => {
    const db = await freshDb()
    const vendorA = '11111111-1111-4111-8111-111111111111'
    const vendorB = '22222222-2222-4222-8222-222222222222'

    await seedProduct(db, { kind: 'physical', slug: 'shared-slug', title: 'A', status: 'active', vendorId: vendorA })
    await seedProduct(db, { kind: 'physical', slug: 'shared-slug', title: 'B', status: 'active', vendorId: vendorB })
    await seedProduct(db, { kind: 'physical', slug: 'platform-only', title: 'P', status: 'active', vendorId: null })

    const scoped = await getProductBySlug(db, 'shared-slug', { audience: 'admin', vendorId: vendorA })
    expect(scoped?.title).toBe('A')

    const vendorList = await listProducts(db, { audience: 'admin', vendorId: vendorB })
    expect(vendorList.items).toHaveLength(1)
    expect(vendorList.items[0]?.title).toBe('B')
  })

  it('paginates list results', async () => {
    const db = await freshDb()
    for (let i = 0; i < 25; i++) {
      await seedProduct(db, {
        kind: 'physical',
        slug: `item-${i}`,
        title: `Item ${i}`,
        status: 'active',
      })
    }

    const page1 = await listProducts(db, { audience: 'admin', page: 1 })
    expect(page1.items).toHaveLength(20)
    expect(page1.total).toBe(25)

    const page2 = await listProducts(db, { audience: 'admin', page: 2 })
    expect(page2.items).toHaveLength(5)
    expect(page2.page).toBe(2)
  })

  it('entityIds AND visibility floor: a private product in entityIds is NOT public-listed', async () => {
    const db = await freshDb()
    const pub = await seedProduct(db, { kind: 'physical', slug: 'pub-active', title: 'Public', status: 'active' })
    const priv = await seedProduct(db, { kind: 'physical', slug: 'priv-draft', title: 'Private', status: 'draft' })

    const page = await listProducts(db, {
      audience: 'public',
      now: FIXED_NOW,
      entityIds: [pub.id, priv.id],
    })
    expect(page.items.map((p) => p.id)).toEqual([pub.id])
  })

  it('empty entityIds yields an empty page, never all products', async () => {
    const db = await freshDb()
    await seedProduct(db, { kind: 'physical', slug: 'visible-one', title: 'Visible', status: 'active' })
    await seedProduct(db, { kind: 'digital', slug: 'visible-two', title: 'Visible 2', status: 'active' })

    const page = await listProducts(db, { audience: 'public', now: FIXED_NOW, entityIds: [] })
    expect(page.items).toEqual([])
    expect(page.total).toBe(0)
  })

  it('undefined entityIds is unfiltered (baseline unchanged)', async () => {
    const db = await freshDb()
    await seedProduct(db, { kind: 'physical', slug: 'baseline', title: 'Baseline', status: 'active' })

    const page = await listProducts(db, { audience: 'public', now: FIXED_NOW })
    expect(page.items.length).toBeGreaterThan(0)
  })

  it('entityIds over MAX_ENTITY_IDS is capped without error or injection', async () => {
    const db = await freshDb()
    const ids: string[] = []
    for (let i = 0; i < 257; i++) {
      const row = await seedProduct(db, {
        kind: 'physical',
        slug: `capped-${i}`,
        title: `Capped ${i}`,
        status: 'active',
      })
      ids.push(row.id)
    }

    const page = await listProducts(db, {
      audience: 'public',
      now: FIXED_NOW,
      entityIds: ids,
    })
    expect(page.entityIdsCapped).toBe(true)
    expect(page.items.length).toBeLessThanOrEqual(256)
    expect(page.total).toBeLessThanOrEqual(256)
  })

  it('entityIds at or under MAX_ENTITY_IDS does not set entityIdsCapped', async () => {
    const db = await freshDb()
    const ids: string[] = []
    for (let i = 0; i < 3; i++) {
      const row = await seedProduct(db, {
        kind: 'physical',
        slug: `under-cap-${i}`,
        title: `Under Cap ${i}`,
        status: 'active',
      })
      ids.push(row.id)
    }

    const page = await listProducts(db, {
      audience: 'public',
      now: FIXED_NOW,
      entityIds: ids,
    })
    expect(page.entityIdsCapped).toBeFalsy()
    expect(page.items).toHaveLength(3)
  })

  it('entityIds intersects with a category filter (both ANDed)', async () => {
    const db = await freshDb()
    const pub = await seedProduct(db, { kind: 'physical', slug: 'in-cat', title: 'In Cat', status: 'active' })
    const other = await seedProduct(db, { kind: 'physical', slug: 'out-cat', title: 'Out Cat', status: 'active' })
    const [cat] = await db.insert(category).values({ name: 'Widgets', slug: 'widgets' }).returning()
    await db.insert(productCategory).values({ productId: pub.id, categoryId: cat!.id })

    const page = await listProducts(db, {
      audience: 'public',
      now: FIXED_NOW,
      entityIds: [pub.id, other.id],
      category: 'widgets',
    })
    expect(page.items.map((p) => p.id)).toEqual([pub.id])
  })

  it('malformed entityIds yields an empty page, not a 500', async () => {
    const db = await freshDb()
    await seedProduct(db, { kind: 'physical', slug: 'real', title: 'Real', status: 'active' })
    const page = await listProducts(db, { audience: 'public', now: FIXED_NOW, entityIds: ['not-a-uuid', 'also bad'] })
    expect(page.items).toEqual([])
    expect(page.total).toBe(0)
  })
})
