import { eq } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import { pushSchema } from './migrate.js'
import { getPromoByCode } from './reads.js'
import { promo, promotionsSchema } from './schema.js'

const PLATFORM_PROMO_ID = '11111111-1111-4111-8111-111111111111'
const VENDOR_PROMO_ID = '22222222-2222-4222-8222-222222222222'
const BOGO_PROMO_ID = '33333333-3333-4333-8333-333333333333'
const BIGINT_PROMO_ID = '44444444-4444-4444-8444-444444444444'
const CAPPED_PROMO_ID = '55555555-5555-4555-8555-555555555555'
const VENDOR_ID = 'vendor-acme'

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

describe('getPromoByCode', () => {
  it('returns a platform promo when vendorId is null or omitted', async () => {
    const db = await freshDb()
    await db.insert(promo).values({
      id: PLATFORM_PROMO_ID,
      code: 'SAVE10',
      kind: 'percentage',
      valueBps: 1000,
      scope: { kind: 'all' },
      eligibility: {},
      funder: 'platform',
      vendorId: null,
      active: true,
    })

    const byNull = await getPromoByCode(db, 'SAVE10', null)
    const byOmitted = await getPromoByCode(db, 'SAVE10')

    expect(byNull).toMatchObject({
      id: PLATFORM_PROMO_ID,
      code: 'SAVE10',
      kind: 'percentage',
      valueBps: 1000,
      vendorId: null,
      active: true,
    })
    expect(byOmitted?.id).toBe(PLATFORM_PROMO_ID)
  })

  it('returns a vendor-scoped promo when vendorId is provided', async () => {
    const db = await freshDb()
    await db.insert(promo).values({
      id: VENDOR_PROMO_ID,
      code: 'VENDOR20',
      kind: 'fixed',
      valueAmount: 2000n,
      currency: 'USD',
      scope: { kind: 'vendor', vendorId: VENDOR_ID },
      eligibility: { membersOnly: true },
      funder: 'vendor',
      vendorId: VENDOR_ID,
      active: true,
    })

    const loaded = await getPromoByCode(db, 'VENDOR20', VENDOR_ID)

    expect(loaded).toMatchObject({
      id: VENDOR_PROMO_ID,
      code: 'VENDOR20',
      kind: 'fixed',
      valueAmount: 2000n,
      currency: 'USD',
      vendorId: VENDOR_ID,
      funder: 'vendor',
      eligibility: { membersOnly: true },
    })
  })

  it('returns null when no matching promo exists', async () => {
    const db = await freshDb()
    expect(await getPromoByCode(db, 'MISSING')).toBeNull()
    expect(await getPromoByCode(db, 'MISSING', VENDOR_ID)).toBeNull()
  })

  it('resolves NULL-distinct platform vs vendor promos sharing the same code', async () => {
    const db = await freshDb()
    await db.insert(promo).values([
      {
        id: PLATFORM_PROMO_ID,
        code: 'SAVE10',
        kind: 'percentage',
        valueBps: 500,
        scope: { kind: 'all' },
        eligibility: {},
        funder: 'platform',
        vendorId: null,
        active: true,
      },
      {
        id: VENDOR_PROMO_ID,
        code: 'SAVE10',
        kind: 'percentage',
        valueBps: 1500,
        scope: { kind: 'vendor', vendorId: VENDOR_ID },
        eligibility: {},
        funder: 'vendor',
        vendorId: VENDOR_ID,
        active: true,
      },
    ])

    const platform = await getPromoByCode(db, 'SAVE10', null)
    const vendor = await getPromoByCode(db, 'SAVE10', VENDOR_ID)

    expect(platform?.id).toBe(PLATFORM_PROMO_ID)
    expect(platform?.valueBps).toBe(500)
    expect(vendor?.id).toBe(VENDOR_PROMO_ID)
    expect(vendor?.valueBps).toBe(1500)
  })

  it('HF-3: partial unique index REJECTS two platform promos sharing a code', async () => {
    const db = await freshDb()
    await db.insert(promo).values({
      id: PLATFORM_PROMO_ID,
      code: 'SAVE10',
      kind: 'percentage',
      valueBps: 1000,
      scope: { kind: 'all' },
      eligibility: {},
      funder: 'platform',
      vendorId: null,
      active: true,
    })

    // second platform promo (vendor_id NULL) with the SAME code must violate uq_promo_code_platform —
    // proves the partial index ENFORCES, not just that the query predicate disambiguates.
    await expect(
      db.insert(promo).values({
        id: '55555555-5555-4555-8555-555555555555',
        code: 'SAVE10',
        kind: 'percentage',
        valueBps: 2000,
        scope: { kind: 'all' },
        eligibility: {},
        funder: 'platform',
        vendorId: null,
        active: true,
      }),
    ).rejects.toThrow()
  })

  it('HF-3: partial unique index REJECTS two promos sharing a (code, vendor_id)', async () => {
    const db = await freshDb()
    await db.insert(promo).values({
      id: VENDOR_PROMO_ID,
      code: 'SAVE10',
      kind: 'percentage',
      valueBps: 1000,
      scope: { kind: 'vendor', vendorId: VENDOR_ID },
      eligibility: {},
      funder: 'vendor',
      vendorId: VENDOR_ID,
      active: true,
    })

    // same code + same vendor must violate uq_promo_code_vendor.
    await expect(
      db.insert(promo).values({
        id: '66666666-6666-4666-8666-666666666666',
        code: 'SAVE10',
        kind: 'percentage',
        valueBps: 2000,
        scope: { kind: 'vendor', vendorId: VENDOR_ID },
        eligibility: {},
        funder: 'vendor',
        vendorId: VENDOR_ID,
        active: true,
      }),
    ).rejects.toThrow()

    // but a platform promo (vendor_id NULL) sharing the code is ALLOWED (NULL-distinctness) —
    // proves the vendor index is partial, not a blanket UNIQUE(code, vendor_id).
    await expect(
      db.insert(promo).values({
        id: '77777777-7777-4777-8777-777777777777',
        code: 'SAVE10',
        kind: 'percentage',
        valueBps: 500,
        scope: { kind: 'all' },
        eligibility: {},
        funder: 'platform',
        vendorId: null,
        active: true,
      }),
    ).resolves.not.toThrow()
  })

  it('round-trips bogo fields assembled from columns', async () => {
    const db = await freshDb()
    await db.insert(promo).values({
      id: BOGO_PROMO_ID,
      code: 'BOGO1',
      kind: 'bogo',
      bogoBuyQty: 2,
      bogoGetQty: 1,
      scope: { kind: 'all' },
      eligibility: {},
      funder: 'platform',
      vendorId: null,
      active: true,
    })

    const loaded = await getPromoByCode(db, 'BOGO1')
    expect(loaded?.bogo).toEqual({ buyQty: 2, getQty: 1 })
    expect(loaded?.valueBps).toBeUndefined()
  })

  it('round-trips bigint columns as bigint', async () => {
    const db = await freshDb()
    const largeAmount = 9_007_199_254_740_993n
    await db.insert(promo).values({
      id: BIGINT_PROMO_ID,
      code: 'BIGFIXED',
      kind: 'fixed',
      valueAmount: largeAmount,
      currency: 'USD',
      minOrderAmount: 50_000n,
      scope: { kind: 'all' },
      eligibility: {},
      funder: 'platform',
      vendorId: null,
      active: true,
    })

    const loaded = await getPromoByCode(db, 'BIGFIXED')
    expect(loaded?.valueAmount).toBe(largeAmount)
    expect(typeof loaded?.valueAmount).toBe('bigint')
    expect(loaded?.minOrderAmount).toBe(50_000n)
    expect(typeof loaded?.minOrderAmount).toBe('bigint')

    const [row] = await db.select().from(promo).where(eq(promo.id, BIGINT_PROMO_ID))
    expect(row?.valueAmount).toBe(largeAmount)
  })

  it('maps optional max discount and max order columns when present', async () => {
    const db = await freshDb()
    await db.insert(promo).values({
      id: CAPPED_PROMO_ID,
      code: 'CAP50',
      kind: 'percentage',
      valueBps: 5000,
      currency: 'USD',
      maxDiscountAmount: 2_500n,
      minOrderAmount: 5_000n,
      maxOrderAmount: 20_000n,
      scope: { kind: 'all' },
      eligibility: {},
      funder: 'platform',
      vendorId: null,
      active: true,
    })

    const loaded = await getPromoByCode(db, 'CAP50')

    expect(loaded).toMatchObject({
      id: CAPPED_PROMO_ID,
      maxDiscountAmount: 2_500n,
      minOrderAmount: 5_000n,
      maxOrderAmount: 20_000n,
    })
    expect(typeof loaded?.maxDiscountAmount).toBe('bigint')
    expect(typeof loaded?.maxOrderAmount).toBe('bigint')
  })
})
