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 { isFulfillmentValidationError } from '../errors.js'
import { grantDigitalAccess } from './grant.js'
import { startPg } from '../pg-harness.js'
import { fulfillmentSchema, type FulfillmentSchema } from '../schema.js'

const ORDER_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
const OWNER_KEY = 'user:33333333-3333-4333-8333-333333333333'

function grantInput() {
  return {
    orderId: ORDER_ID,
    itemId: 'variant-digital-1',
    ownerKey: OWNER_KEY,
    blobKey: 'blob/digital/track-1.zip',
  }
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

describe('grantDigitalAccess (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 = drizzle(poolB, { schema: fulfillmentSchema }) as TransactionalDatabase<FulfillmentSchema>
    stop = pgResult.stop
  }, 120_000)

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

  it('concurrent grant same (orderId, itemId, ownerKey): one row, both return same grant id', async () => {
    const input = grantInput()

    let resolveHold!: () => void
    const hold = new Promise<void>((resolve) => {
      resolveHold = resolve
    })

    let txALocked!: () => void
    const txAHasLock = new Promise<void>((resolve) => {
      txALocked = resolve
    })

    const txA = db.transaction(async (txA) => {
      const grant = await grantDigitalAccess(txA, input)
      txALocked()
      await hold
      return grant
    })

    await txAHasLock

    const txB = dbB.transaction(async (txB) => {
      return grantDigitalAccess(txB, input)
    })

    await sleep(200)
    resolveHold()

    const [grantA, grantB] = await Promise.all([txA, txB])
    expect(grantA.id).toBe(grantB.id)

    const countRes = (await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM access_grant WHERE order_id = ${ORDER_ID}::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(1)
  })

  it('idempotent replay returns the same grant without a second row', async () => {
    const orderId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'
    const input = {
      orderId,
      itemId: 'variant-digital-2',
      ownerKey: 'email:guest@example.com',
      blobKey: 'blob/digital/track-2.zip',
    }

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

    expect(second.id).toBe(first.id)
    expect(second.ownerKey).toBe(first.ownerKey)

    const countRes = (await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM access_grant 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(1)
  })

  it('rejects an un-namespaced ownerKey and empty itemId/blobKey (idempotency-key components)', async () => {
    const orderId = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc'
    const bad = [
      { orderId, itemId: 'variant-x', ownerKey: 'guest@example.com', blobKey: 'blob/x.zip' },
      { orderId, itemId: 'variant-x', ownerKey: 'user:', blobKey: 'blob/x.zip' },
      { orderId, itemId: '', ownerKey: OWNER_KEY, blobKey: 'blob/x.zip' },
      { orderId, itemId: 'variant-x', ownerKey: OWNER_KEY, blobKey: '' },
    ]

    for (const input of bad) {
      await expect(
        db.transaction((tx) => grantDigitalAccess(tx, input)),
      ).rejects.toSatisfy((e) => isFulfillmentValidationError(e))
    }

    const countRes = (await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM access_grant 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)
  })
})
