import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import { isReviewValidationError } from './errors.js'
import { listReviews, listReviewsByVendor } from './list.js'
import { pushReviewsSchema } from './migrate.js'
import { review, reviewsSchema } from './schema.js'
import type { ReviewStatus } from './types.js'

const PRODUCT_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
const PRODUCT_ID_2 = 'dddddddd-dddd-4ddd-8ddd-dddddddddddd'
const VENDOR_A = 'vendor-a'
const VENDOR_B = 'vendor-b'

async function freshDb() {
  const db = createPgliteClient({ schema: reviewsSchema })
  await pushReviewsSchema(db)
  return db
}

let userCounter = 0
function nextUserId(): string {
  userCounter += 1
  const hex = userCounter.toString(16).padStart(12, '0')
  return `bbbbbbbb-bbbb-4bbb-8bbb-${hex}`
}

let purchaseCounter = 0
function nextPurchaseId(): string {
  purchaseCounter += 1
  const hex = purchaseCounter.toString(16).padStart(12, '0')
  return `cccccccc-cccc-4ccc-8ccc-${hex}`
}

type SeedOverrides = { vendorId?: string; productId?: string }

async function seed(
  db: Awaited<ReturnType<typeof freshDb>>,
  status: ReviewStatus,
  createdAt: Date,
  body: string,
  overrides?: SeedOverrides,
): Promise<string> {
  const [row] = await db
    .insert(review)
    .values({
      productId: overrides?.productId ?? PRODUCT_ID,
      userId: nextUserId(),
      vendorId: overrides?.vendorId ?? VENDOR_A,
      purchaseId: nextPurchaseId(),
      rating: 5,
      body,
      status,
      createdAt,
      updatedAt: createdAt,
    })
    .returning()
  return row!.id
}

describe('listReviews', () => {
  it('list-pagination-keyset — newest-first, stable cursor, no dupes or gaps across pages', async () => {
    const db = await freshDb()
    const now = new Date()
    // three approved reviews, distinct createdAt (precision:3 keeps these distinct)
    await seed(db, 'approved', now, 'oldest')
    await seed(db, 'approved', new Date(now.getTime() + 1), 'middle')
    await seed(db, 'approved', new Date(now.getTime() + 2), 'newest')

    const page1 = await listReviews(db, PRODUCT_ID, { audience: 'public', limit: 2 })
    expect(page1.items).toHaveLength(2)
    expect(page1.nextCursor).not.toBeNull()
    // newest-first ordering
    expect(page1.items[0]?.body).toBe('newest')
    expect(page1.items[1]?.body).toBe('middle')

    const page2 = await listReviews(db, PRODUCT_ID, {
      audience: 'public',
      limit: 2,
      cursor: page1.nextCursor!,
    })
    expect(page2.items).toHaveLength(1)
    expect(page2.items[0]?.body).toBe('oldest')
    expect(page2.nextCursor).toBeNull()

    // no overlap between pages — union covers all three exactly once
    const ids = [...page1.items, ...page2.items].map((r) => r.id)
    expect(new Set(ids).size).toBe(3)
  })

  it('list-malformed-cursor — throws ReviewValidationError', async () => {
    const db = await freshDb()
    await seed(db, 'approved', new Date(), 'one')

    await expect(
      listReviews(db, PRODUCT_ID, { audience: 'public', cursor: 'garbage' }),
    ).rejects.toSatisfy((e) => isReviewValidationError(e))
  })

  it('list-admin-audience — returns all statuses (pending + approved + rejected)', async () => {
    const db = await freshDb()
    const now = new Date()
    await seed(db, 'pending', now, 'p')
    await seed(db, 'approved', new Date(now.getTime() + 1), 'a')
    await seed(db, 'rejected', new Date(now.getTime() + 2), 'r')

    const page = await listReviews(db, PRODUCT_ID, { audience: 'admin' })
    expect(page.items).toHaveLength(3)
    const statuses = page.items.map((r) => r.status).sort()
    expect(statuses).toEqual(['approved', 'pending', 'rejected'])
  })
})

describe('listReviewsByVendor', () => {
  it('list-by-vendor-public-only-approved', async () => {
    const db = await freshDb()
    const now = new Date()
    await seed(db, 'approved', now, 'approved-review')
    await seed(db, 'pending', new Date(now.getTime() + 1), 'pending-review')

    const page = await listReviewsByVendor(db, VENDOR_A, { audience: 'public' })
    expect(page.items).toHaveLength(1)
    expect(page.items[0]?.status).toBe('approved')
    expect(page.items[0]?.body).toBe('approved-review')
  })

  it('list-by-vendor-admin-all-statuses', async () => {
    const db = await freshDb()
    const now = new Date()
    await seed(db, 'approved', now, 'approved-review')
    await seed(db, 'pending', new Date(now.getTime() + 1), 'pending-review')

    const page = await listReviewsByVendor(db, VENDOR_A, { audience: 'admin' })
    expect(page.items).toHaveLength(2)
    const statuses = page.items.map((r) => r.status).sort()
    expect(statuses).toEqual(['approved', 'pending'])
  })

  it('list-by-vendor-vendorid-filter', async () => {
    const db = await freshDb()
    const now = new Date()
    await seed(db, 'approved', now, 'vendor-a-review')
    await seed(db, 'approved', new Date(now.getTime() + 1), 'vendor-b-review', {
      vendorId: VENDOR_B,
    })

    const page = await listReviewsByVendor(db, VENDOR_A, { audience: 'admin' })
    expect(page.items).toHaveLength(1)
    expect(page.items[0]?.vendorId).toBe(VENDOR_A)
    expect(page.items[0]?.body).toBe('vendor-a-review')
  })

  it('list-by-vendor-productid-narrowing', async () => {
    const db = await freshDb()
    const now = new Date()
    await seed(db, 'approved', now, 'product-one')
    await seed(db, 'approved', new Date(now.getTime() + 1), 'product-two', {
      productId: PRODUCT_ID_2,
    })

    const page = await listReviewsByVendor(db, VENDOR_A, {
      audience: 'admin',
      productId: PRODUCT_ID,
    })
    expect(page.items).toHaveLength(1)
    expect(page.items[0]?.productId).toBe(PRODUCT_ID)
    expect(page.items[0]?.body).toBe('product-one')
  })

  it('list-by-vendor-cursor-pagination', async () => {
    const db = await freshDb()
    const now = new Date()
    await seed(db, 'approved', now, 'oldest')
    await seed(db, 'approved', new Date(now.getTime() + 1), 'middle')
    await seed(db, 'approved', new Date(now.getTime() + 2), 'newest')

    const page1 = await listReviewsByVendor(db, VENDOR_A, { audience: 'public', limit: 2 })
    expect(page1.items).toHaveLength(2)
    expect(page1.nextCursor).not.toBeNull()
    expect(page1.items[0]?.body).toBe('newest')
    expect(page1.items[1]?.body).toBe('middle')

    const page2 = await listReviewsByVendor(db, VENDOR_A, {
      audience: 'public',
      limit: 2,
      cursor: page1.nextCursor!,
    })
    expect(page2.items).toHaveLength(1)
    expect(page2.items[0]?.body).toBe('oldest')
    expect(page2.nextCursor).toBeNull()
  })

  it('list-by-vendor-empty-result', async () => {
    const db = await freshDb()

    const page = await listReviewsByVendor(db, 'no-such-vendor', { audience: 'admin' })
    expect(page).toEqual({ items: [], nextCursor: null })
  })
})
