import { validateSync, ValidationError as VE, ValidatorOptions } from 'class-validator'
import { createHash } from 'crypto'
import { PassThrough, Readable } from 'stream'

export class ValidationError extends Error {
  override message: string

  constructor(errors: VE[]) {
    super()
    this.message = errors
      .map((e) => Object.values(e.constraints!))
      .flat()
      .join(', ')
  }
}

export function validateSyncOrFail(data: object, options: ValidatorOptions = {}) {
  options = Object.assign({ whitelist: true, forbidUnknownValues: true, skipMissingProperties: false }, options)
  const errors = validateSync(data, options)
  if (errors.length > 0) {
    throw new ValidationError(errors)
  }
}

export function sha3(url: string) {
  return createHash('sha3-256').update(url).digest('hex')
}

export function sortObjectByKeys<T extends object>(obj: T): T {
  return Object.fromEntries(Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0]))) as T
}

export function splitter(from: NodeJS.ReadableStream, ...streams: NodeJS.WritableStream[]) {
  const splitter = new PassThrough()
  for (const stream of streams) {
    splitter.pipe(stream)
  }
  from.pipe(splitter)
}

export function testForPrefixOrRegexp(str: string, values: (string | RegExp)[]): boolean {
  for (const value of values) {
    if (typeof value === 'string') {
      if (str.startsWith(value)) return true
    } else if (value.test(str)) return true
  }
  return false
}

export class StreamUtils {
  static fromBuffer(buffer: Buffer) {
    const stream = new Readable()
    stream.push(buffer)
    stream.push(null)
    return stream
  }

  static toBuffer(stream: NodeJS.ReadableStream) {
    return new Promise<Buffer>((resolve, reject) => {
      const chunks: Buffer[] = []
      stream.on('data', (chunk) => chunks.push(chunk))
      stream.on('error', reject)
      stream.on('end', () => resolve(Buffer.concat(chunks)))
    })
  }
}