import { sql } from 'drizzle-orm'
import { drizzle } from 'drizzle-orm/node-postgres'
import pg from 'pg'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import type { TransactionalDatabase } from '@platform-modules/db'
import { withTransactionIdentity } from '@platform-modules/db'
import { isFulfillmentValidationError } from '../errors.js'
import { issueVoucher } from './issue.js'
import { startPg } from '../pg-harness.js'
import { fulfillmentSchema, type FulfillmentSchema } from '../schema.js'

const ORDER_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
const LINE_ID = 'line-voucher-1'

function issueInput(overrides: Partial<Parameters<typeof issueVoucher>[1]> = {}) {
  return {
    orderId: ORDER_ID,
    lineId: LINE_ID,
    qty: 3,
    vendorId: 'vendor-a' as string | null,
    expiresAt: new Date('2026-12-31T00:00:00.000Z'),
    ...overrides,
  }
}

describe('issueVoucher (real Postgres)', () => {
  let db: TransactionalDatabase<FulfillmentSchema>
  let dbB: TransactionalDatabase<FulfillmentSchema>
  let poolB: pg.Pool | undefined
  let stop: (() => Promise<void>) | undefined

  beforeAll(async () => {
    const pgResult = await startPg()
    db = pgResult.db
    const { host, port, user, database } = pgResult.pool.options
    poolB = new pg.Pool({ host, port, user, database, max: 1 })
    dbB = withTransactionIdentity(drizzle(poolB, { schema: fulfillmentSchema })) as unknown as TransactionalDatabase<FulfillmentSchema>
    stop = pgResult.stop
  }, 120_000)

  afterAll(async () => {
    await poolB?.end().catch(() => undefined)
    await stop?.()
  }, 30_000)

  it('qty=3 issues exactly 3 vouchers with unitIndex 0/1/2', async () => {
    const orderId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'
    const input = issueInput({ orderId, lineId: 'line-qty-3' })

    const vouchers = await db.transaction((tx) => issueVoucher(tx, input))

    expect(vouchers).toHaveLength(3)
    expect(vouchers.map((v) => v.unitIndex).sort((a, b) => a - b)).toEqual([0, 1, 2])
    expect(vouchers.every((v) => v.state === 'UNREDEEMED')).toBe(true)
    expect(vouchers.every((v) => v.orderId === orderId)).toBe(true)
    expect(vouchers.every((v) => v.lineId === input.lineId)).toBe(true)
  })

  it('concurrent issue same (orderId, lineId, qty=3): exactly 3 rows total, never 6', async () => {
    const orderId = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc'
    const input = issueInput({ orderId, lineId: 'line-concurrent' })

    const [first, second] = await Promise.all([
      db.transaction((tx) => issueVoucher(tx, input)),
      dbB.transaction((tx) => issueVoucher(tx, input)),
    ])

    expect(first).toHaveLength(3)
    expect(second).toHaveLength(3)

    const firstIds = first.map((v) => v.id).sort()
    const secondIds = second.map((v) => v.id).sort()
    expect(secondIds).toEqual(firstIds)

    const countRes = (await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM voucher WHERE order_id = ${orderId}::uuid
    `)) as unknown as { rows?: Array<{ count: number }> } | Array<{ count: number }>
    const rows = (Array.isArray(countRes) ? countRes : countRes.rows) ?? []
    expect(Number(rows[0]?.count)).toBe(3)
  })

  it('replay returns the same qty-set without extra rows', async () => {
    const orderId = 'dddddddd-dddd-4ddd-8ddd-dddddddddddd'
    const input = issueInput({ orderId, lineId: 'line-replay' })

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

    expect(second.map((v) => v.id).sort()).toEqual(first.map((v) => v.id).sort())

    const countRes = (await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM voucher WHERE order_id = ${orderId}::uuid
    `)) as unknown as { rows?: Array<{ count: number }> } | Array<{ count: number }>
    const rows = (Array.isArray(countRes) ? countRes : countRes.rows) ?? []
    expect(Number(rows[0]?.count)).toBe(3)
  })

  it('rejects an empty lineId (idempotency-key component) with FulfillmentValidationError', async () => {
    const orderId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee'

    await expect(
      db.transaction((tx) => issueVoucher(tx, issueInput({ orderId, lineId: '' }))),
    ).rejects.toSatisfy((e) => isFulfillmentValidationError(e))

    const countRes = (await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM voucher WHERE order_id = ${orderId}::uuid
    `)) as unknown as { rows?: Array<{ count: number }> } | Array<{ count: number }>
    const rows = (Array.isArray(countRes) ? countRes : countRes.rows) ?? []
    expect(Number(rows[0]?.count)).toBe(0)
  })
})
