export type TransactionIdentity = {
  readonly $platformTransactionIdentity: 'v1'
}

/** Structural phantom marker; runtime authenticity is callback-scoped. */
export type TransactionCapability<S = unknown> = {
  readonly $platformTransaction: { readonly schema: S }
}

export class TransactionCapabilityError extends Error {
  readonly code = 'transaction-capability'

  constructor(readonly reason: 'inactive' | 'identity-mismatch') {
    super(
      reason === 'inactive'
        ? 'Expected an active transaction minted by a @platform-modules/db transaction callback'
        : 'Transaction identity does not match the active transaction',
    )
    this.name = 'TransactionCapabilityError'
  }
}

export function isTransactionCapabilityError(error: unknown): error is TransactionCapabilityError {
  if (typeof error !== 'object' || error === null) return false
  const candidate = error as { code?: unknown; reason?: unknown }
  return (
    candidate.code === 'transaction-capability' &&
    (candidate.reason === 'inactive' || candidate.reason === 'identity-mismatch')
  )
}

const transactionAuthenticatorKey = Symbol.for('@platform-modules/db.transaction-authenticator')
const transactionIdentityValidatorKey = Symbol.for('@platform-modules/db.transaction-identity-validator')

type TransactionIdentityAuthenticator = (
  candidate: object,
) => TransactionIdentity | undefined

type TransactionIdentityValidator = (candidate: object) => boolean

function getActiveTransactionIdentity(tx: object): TransactionIdentity {
  const authenticate = Reflect.get(tx, transactionAuthenticatorKey) as
    | TransactionIdentityAuthenticator
    | undefined
  const identity = authenticate?.(tx)
  const validate = identity === undefined
    ? undefined
    : Reflect.get(identity, transactionIdentityValidatorKey) as
      | TransactionIdentityValidator
      | undefined
  if (identity === undefined || validate?.(tx) !== true) {
    throw new TransactionCapabilityError('inactive')
  }
  return identity
}

export function getTransactionIdentity(tx: TransactionCapability): TransactionIdentity {
  return getActiveTransactionIdentity(tx)
}

export function assertTransactionIdentity(
  tx: TransactionCapability,
  expectedIdentity: TransactionIdentity,
): void {
  if (getTransactionIdentity(tx) !== expectedIdentity) {
    throw new TransactionCapabilityError('identity-mismatch')
  }
}

type TransactionalHandle = {
  transaction?: (...args: unknown[]) => unknown
  execute?: (...args: unknown[]) => unknown
  all?: (...args: unknown[]) => unknown
}

function hasTransactionProperty(
  handle: object & TransactionalHandle,
  property: string | symbol,
): boolean {
  if (property === 'execute') {
    return handle.execute !== undefined || handle.all !== undefined
  }
  if (property === 'transaction') return handle.transaction !== undefined
  return Reflect.has(handle, property)
}

const transactionProperties = new Set<string | symbol>([
  'query',
  '$with',
  '$count',
  'with',
  'select',
  'selectDistinct',
  'selectDistinctOn',
  'update',
  'insert',
  'delete',
  'refreshMaterializedView',
  'run',
  'all',
  'get',
  'values',
  'execute',
  'transaction',
])

const terminalOperationProperties = new Set<string | symbol>([
  'execute',
  'run',
  'all',
  'get',
  'findMany',
  'findFirst',
])

function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
  return (
    (typeof value === 'object' && value !== null) ||
    typeof value === 'function'
  ) && typeof Reflect.get(value, 'then') === 'function'
}

function normalizeRows(result: unknown): unknown {
  if (
    typeof result === 'object' &&
    result !== null &&
    'rows' in result &&
    Array.isArray(result.rows)
  ) {
    return result.rows
  }
  return result
}

function guardValue<T>(
  value: T,
  assertActive: () => void,
  trackOperation: <Result>(operation: PromiseLike<Result>) => Promise<Result>,
): T {
  if (
    (typeof value !== 'object' || value === null) &&
    typeof value !== 'function'
  ) {
    return value
  }

  const source = value as object
  return new Proxy({}, {
    get(_target, property) {
      assertActive()
      const member = Reflect.get(source, property, source)
      if (typeof member === 'function') {
        return (...args: unknown[]) => {
          const result = Reflect.apply(member, source, args)
          if (
            terminalOperationProperties.has(property) &&
            isPromiseLike(result)
          ) {
            return guardValue(
              trackOperation(result),
              assertActive,
              trackOperation,
            )
          }
          if (property === 'then') {
            return trackOperation(Promise.resolve(result))
          }
          return guardValue(result, assertActive, trackOperation)
        }
      }
      return guardValue(member, assertActive, trackOperation)
    },
    getOwnPropertyDescriptor(_target, property) {
      assertActive()
      if (!Reflect.has(source, property)) return undefined
      return {
        configurable: true,
        enumerable: Reflect.getOwnPropertyDescriptor(source, property)?.enumerable ?? false,
        value: guardValue(
          Reflect.get(source, property, source),
          assertActive,
          trackOperation,
        ),
        writable: false,
      }
    },
    getPrototypeOf() {
      assertActive()
      return null
    },
    has(_target, property) {
      assertActive()
      return Reflect.has(source, property)
    },
    ownKeys() {
      assertActive()
      return Reflect.ownKeys(source)
    },
    set() {
      return false
    },
    defineProperty() {
      return false
    },
    deleteProperty() {
      return false
    },
    setPrototypeOf() {
      return false
    },
    preventExtensions() {
      return false
    },
    isExtensible() {
      assertActive()
      return true
    },
  }) as T
}

async function runTransaction<T extends object, R>(
  owner: object,
  transaction: (...args: unknown[]) => unknown,
  callback: (tx: T & TransactionCapability) => Promise<R>,
  config?: unknown,
): Promise<R> {
  return await transaction.call(
    owner,
    async (raw: T) => {
      let active = true
      let facade: T & TransactionCapability
      let identity: TransactionIdentity
      const operations: Promise<unknown>[] = []
      const trackOperation = <Result>(operation: PromiseLike<Result>): Promise<Result> => {
        const promise = Promise.resolve(operation)
        operations.push(promise)
        void promise.catch(() => undefined)
        return promise
      }
      const settleOperations = async (): Promise<void> => {
        let index = 0
        let rejected = false
        let rejection: unknown
        while (index < operations.length) {
          const end = operations.length
          const results = await Promise.allSettled(operations.slice(index, end))
          for (const result of results) {
            if (result.status === 'rejected' && !rejected) {
              rejected = true
              rejection = result.reason
            }
          }
          index = end
        }
        if (rejected) throw rejection
      }
      const validateIdentity: TransactionIdentityValidator =
        (candidate) => active && candidate === facade
      const authenticate: TransactionIdentityAuthenticator =
        (candidate) => validateIdentity(candidate) ? identity : undefined
      const assertActive = () => {
        getActiveTransactionIdentity(facade)
      }
      facade = new Proxy({}, {
        get(_target, property) {
          if (property === transactionAuthenticatorKey) return authenticate
          if (typeof property === 'number' || !transactionProperties.has(property)) return undefined
          const handle = raw as T & TransactionalHandle
          if (property === 'execute') {
            return (...args: unknown[]) => {
              try {
                assertActive()
                const execute = handle.execute ?? handle.all
                if (execute === undefined) {
                  throw new TransactionCapabilityError('inactive')
                }
                return trackOperation(
                  Promise.resolve(execute.apply(raw, args)).then(normalizeRows),
                )
              } catch (error) {
                const rejection = Promise.reject(error)
                void rejection.catch(() => undefined)
                return rejection
              }
            }
          }
          if (property === 'transaction' && handle.transaction !== undefined) {
            return async <NestedResult>(
              nestedCallback: (nested: T & TransactionCapability) => Promise<NestedResult>,
              nestedConfig?: unknown,
            ) => {
              assertActive()
              return trackOperation(
                runTransaction(raw, handle.transaction!, nestedCallback, nestedConfig),
              )
            }
          }

          const member = Reflect.get(raw, property, raw)
          if (typeof member === 'function') {
            return (...args: unknown[]) => {
              assertActive()
              const result = Reflect.apply(member, raw, args)
              if (
                (terminalOperationProperties.has(property) || property === 'values') &&
                isPromiseLike(result)
              ) {
                return guardValue(
                  trackOperation(result),
                  assertActive,
                  trackOperation,
                )
              }
              return guardValue(result, assertActive, trackOperation)
            }
          }
          assertActive()
          return guardValue(member, assertActive, trackOperation)
        },
        getOwnPropertyDescriptor(_target, property) {
          if (
            typeof property === 'number' ||
            !transactionProperties.has(property) ||
            !hasTransactionProperty(raw as T & TransactionalHandle, property)
          ) return undefined
          assertActive()
          return {
            configurable: true,
            enumerable: false,
            value: Reflect.get(facade, property),
            writable: false,
          }
        },
        getPrototypeOf() {
          assertActive()
          return null
        },
        has(_target, property) {
          assertActive()
          return (
            typeof property !== 'number' &&
            transactionProperties.has(property) &&
            hasTransactionProperty(raw as T & TransactionalHandle, property)
          )
        },
        ownKeys() {
          assertActive()
          return [...transactionProperties].filter((property) =>
            hasTransactionProperty(raw as T & TransactionalHandle, property),
          )
        },
        set() {
          return false
        },
        defineProperty() {
          return false
        },
        deleteProperty() {
          return false
        },
        setPrototypeOf() {
          return false
        },
        preventExtensions() {
          return false
        },
        isExtensible() {
          assertActive()
          return true
        },
      }) as T & TransactionCapability

      identity = Object.freeze(Object.defineProperty(
        { $platformTransactionIdentity: 'v1' } as const,
        transactionIdentityValidatorKey,
        { value: validateIdentity },
      ))
      try {
        const result = await callback(facade)
        await settleOperations()
        return result
      } catch (error) {
        await settleOperations().catch(() => undefined)
        throw error
      } finally {
        active = false
      }
    },
    config,
  ) as R
}

/**
 * Brands a driver-built handle so its transactions carry platform transaction identity. First-party
 * adapters apply it for you. It is public because a consumer that constructs its own driver client — a
 * real-Postgres test harness, or any driver with no first-party adapter — otherwise cannot produce a
 * value satisfying `TransactionalDatabase`, and reaching into `db/src` for it breaks the moment the
 * package is consumed as published `dist`.
 */
export function withTransactionIdentity<T extends object>(database: T): T {
  const transaction = (database as T & TransactionalHandle).transaction
  if (transaction === undefined) return database

  Object.defineProperty(database, 'transaction', {
    configurable: true,
    value: <R>(
      callback: (tx: object & TransactionCapability) => Promise<R>,
      config?: unknown,
    ) => runTransaction(database, transaction, callback, config),
    writable: true,
  })

  return database
}
