'use strict'

const net = require('net')
const fs = require('fs/promises')
const path = require('path')
const serverFuzzFnMap = require('./server')
const clientFuzzFnMap = require('./client')

const port = process.env.PORT || 0
const timeout = parseInt(process.env.TIMEOUT, 10) || 300_000 // 5 minutes by default

const netServer = net.createServer((socket) => {
  socket.on('data', (data) => {
    // Select server fuzz fn
    const serverFuzzFns = Object.values(serverFuzzFnMap)
    const serverFuzzFn = serverFuzzFns[Math.floor(Math.random() * serverFuzzFns.length)]

    serverFuzzFn(socket, data)
  })
})
const waitForNetServer = netServer.listen(port)

// Set script to exit gracefully after a set period of time.
const timer = setTimeout(() => {
  process.kill(process.pid, 'SIGINT')
}, timeout)

async function writeResults (resultsPath, data) {
  try {
    await fs.writeFile(resultsPath, JSON.stringify(data, null, 2))
    console.log(`=== Written results to ${resultsPath} ===`)
  } catch (err) {
    console.log(`=== Unable to write results to ${resultsPath}`, err, '===')
  }
}

async function fuzz (buf) {
  // Wait for net server to be ready
  await waitForNetServer

  // Select client fuzz fn based on the buf input
  await Promise.all(
    Object.entries(clientFuzzFnMap).map(async ([clientFuzzFnName, clientFuzzFn]) => {
      const results = {}
      try {
        await clientFuzzFn(netServer, results, buf)
      } catch (err) {
        clearTimeout(timer)
        const output = { clientFuzzFnName, buf: { raw: buf, string: buf.toString() }, raw: JSON.stringify({ clientFuzzFnName, buf: { raw: buf, string: buf.toString() }, err, ...results }), err, ...results }

        console.log(`=== Failed fuzz ${clientFuzzFnName} with input '${buf}' ===`)
        console.log('=== Fuzz results start ===')
        console.log(output)
        console.log('=== Fuzz results end ===')

        await writeResults(path.resolve(`fuzz-results-${Date.now()}.json`), output)

        throw err
      }
    })
  )
}

module.exports = {
  fuzz
}
