/// <reference types="node" />
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { transformSync } from 'esbuild'
import { expect, it } from 'vitest'
import { withTransactionIdentity } from './transaction.js'

it('shares active transaction identity across physical package copies', async () => {
  const source = readFileSync(new URL('./transaction.ts', import.meta.url), 'utf8')
  const compiled = transformSync(source, {
    loader: 'ts',
    format: 'esm',
    target: 'es2022',
  }).code
  const directory = mkdtempSync(join(tmpdir(), 'db-transaction-copies-'))
  const firstPath = join(directory, 'first.mjs')
  const secondPath = join(directory, 'second.mjs')
  writeFileSync(firstPath, compiled)
  writeFileSync(secondPath, compiled)

  const [first, second] = await Promise.all([
    import(pathToFileURL(firstPath).href),
    import(pathToFileURL(secondPath).href),
  ])
  const database = first.withTransactionIdentity({
    transaction: async <T>(callback: (tx: object) => Promise<T>) => callback({}),
  })

  await database.transaction(async (tx: object) => {
    const identity = first.getTransactionIdentity(tx)
    expect(second.getTransactionIdentity(tx)).toBe(identity)

    const authenticator = Reflect.get(
      tx,
      Symbol.for('@platform-modules/db.transaction-authenticator'),
    )
    const copiedFacade = Object.assign(
      { $platformTransaction: { schema: {} } },
      { [Symbol.for('@platform-modules/db.transaction-authenticator')]: authenticator },
    )
    expect(() => second.getTransactionIdentity(copiedFacade)).toThrowError(
      expect.objectContaining({ code: 'transaction-capability', reason: 'inactive' }),
    )

    const forgedFacade = Object.assign(
      { $platformTransaction: { schema: {} } },
      {
        [Symbol.for('@platform-modules/db.transaction-authenticator')]: () => identity,
      },
    )
    expect(() => second.assertTransactionIdentity(forgedFacade, identity)).toThrowError(
      expect.objectContaining({ code: 'transaction-capability', reason: 'inactive' }),
    )
  })
})

it('waits for unobserved execute operations before completing the callback', async () => {
  let release: ((rows: unknown[]) => void) | undefined
  const operation = new Promise<unknown[]>((resolve) => {
    release = resolve
  })
  let committed = false
  const database = {
    transaction: async <T>(callback: (tx: object) => Promise<T>) => {
      const result = await callback({ execute: () => operation })
      committed = true
      return result
    },
  }
  withTransactionIdentity(database)

  const completion = database.transaction(async (tx) => {
    void (tx as { execute(query: object): Promise<unknown> }).execute({})
    return 'done'
  })
  await Promise.resolve()
  await Promise.resolve()
  expect(committed).toBe(false)

  release!([])
  await expect(completion).resolves.toBe('done')
  expect(committed).toBe(true)
})

it('waits for unobserved terminal operations before completing the callback', async () => {
  let release: (() => void) | undefined
  const operation = new Promise<void>((resolve) => {
    release = resolve
  })
  const thenable = {
    then: operation.then.bind(operation),
  } as PromiseLike<void>
  let committed = false
  const database = {
    transaction: async <T>(callback: (tx: object) => Promise<T>) => {
      const result = await callback({ run: () => thenable })
      committed = true
      return result
    },
  }
  withTransactionIdentity(database)

  const completion = database.transaction(async (tx) => {
    void (tx as { run(query: object): Promise<void> }).run({})
    return 'done'
  })
  await Promise.resolve()
  await Promise.resolve()
  expect(committed).toBe(false)

  release!()
  await expect(completion).resolves.toBe('done')
  expect(committed).toBe(true)
})

it('propagates undefined rejection reasons from unobserved operations', async () => {
  const database = {
    transaction: async <T>(callback: (tx: object) => Promise<T>) =>
      callback({ execute: () => Promise.reject(undefined) }),
  }
  withTransactionIdentity(database)

  await expect(database.transaction(async (tx) => {
    void (tx as { execute(query: object): Promise<unknown> }).execute({})
  })).rejects.toBeUndefined()
})

it('propagates an unobserved execute rejection to the transaction callback', async () => {
  const database = {
    transaction: async <T>(callback: (tx: object) => Promise<T>) =>
      callback({ execute: () => Promise.reject(new Error('execute failed')) }),
  }
  withTransactionIdentity(database)

  await expect(database.transaction(async (tx) => {
    void (tx as { execute(query: object): Promise<unknown> }).execute({})
  })).rejects.toThrow('execute failed')
})
