'use strict'

const { once } = require('events')
const { join } = require('path')
const ThreadStream = require('thread-stream')
const { MessageChannel } = require('worker_threads')
const { test } = require('tap')

workerTest('transport-on-data.js')
workerTest('transport-async-iteration.js', ' when using async iteration')

function workerTest (filename, description = '') {
  test(`does not wait for pino to send config by default${description}`, function ({ same, plan }) {
    plan(4)
    const { port1, port2 } = new MessageChannel()
    const stream = new ThreadStream({
      filename: join(__dirname, 'fixtures', filename),
      workerData: { port: port1 },
      workerOpts: {
        transferList: [port1]
      }
    })

    const expected = [{
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'hello world'
    }, {
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'another message',
      prop: 42
    }]

    const emptyPinoConfig = {
      levels: undefined,
      messageKey: undefined,
      errorKey: undefined
    }

    port2.on('message', function (message) {
      same(expected.shift(), message.data)
      same(emptyPinoConfig, message.pinoConfig)
    })

    const lines = expected.map(JSON.stringify).join('\n')
    stream.write(lines)
    stream.end()
  })

  test(`does not wait for pino to send config if transport is not expecting it${description}`, function ({ same, plan }) {
    plan(4)
    const { port1, port2 } = new MessageChannel()
    const stream = new ThreadStream({
      filename: join(__dirname, 'fixtures', filename),
      workerData: {
        port: port1,
        pinoWillSendConfig: true
      },
      workerOpts: {
        transferList: [port1]
      }
    })

    const expected = [{
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'hello world'
    }, {
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'another message',
      prop: 42
    }]

    const emptyPinoConfig = {
      levels: undefined,
      messageKey: undefined,
      errorKey: undefined
    }

    const pinoConfig = {
      levels: {
        labels: { 30: 'info' },
        values: { info: 30 }
      },
      messageKey: 'msg',
      errorKey: 'err'
    }

    stream.worker.postMessage({ code: 'PINO_CONFIG', config: pinoConfig })
    // stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig })

    port2.on('message', function (message) {
      same(expected.shift(), message.data)
      same(emptyPinoConfig, message.pinoConfig)
    })

    const lines = expected.map(JSON.stringify).join('\n')
    stream.write(lines)
    stream.end()
  })

  test(`waits for the pino config when pino intends to send it and the transport requests it${description}`, function ({ same, plan }) {
    plan(4)
    const { port1, port2 } = new MessageChannel()
    const stream = new ThreadStream({
      filename: join(__dirname, 'fixtures', filename),
      workerData: {
        port: port1,
        pinoWillSendConfig: true,
        opts: {
          expectPinoConfig: true
        }
      },
      workerOpts: {
        transferList: [port1]
      }
    })

    const expected = [{
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'hello world'
    }, {
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'another message',
      prop: 42
    }]

    const pinoConfig = {
      levels: {
        labels: { 30: 'info' },
        values: { info: 30 }
      },
      messageKey: 'msg',
      errorKey: 'err'
    }

    port2.on('message', function (message) {
      same(expected.shift(), message.data)
      same(pinoConfig, message.pinoConfig)
    })

    const lines = expected.map(JSON.stringify).join('\n')
    stream.worker.postMessage({ code: 'PINO_CONFIG', config: pinoConfig })
    // stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig })
    stream.write(lines)
    stream.end()
  })

  test(`continues to listen if it receives a message that is not PINO_CONFIG${description}`, function ({ same, plan }) {
    plan(4)
    const { port1, port2 } = new MessageChannel()
    const stream = new ThreadStream({
      filename: join(__dirname, 'fixtures', 'transport-on-data.js'),
      workerData: {
        port: port1,
        pinoWillSendConfig: true,
        opts: {
          expectPinoConfig: true
        }
      },
      workerOpts: {
        transferList: [port1]
      }
    })

    const expected = [{
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'hello world'
    }, {
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'another message',
      prop: 42
    }]

    const pinoConfig = {
      levels: {
        labels: { 30: 'info' },
        values: { info: 30 }
      },
      messageKey: 'msg',
      errorKey: 'err'
    }

    port2.on('message', function (message) {
      same(expected.shift(), message.data)
      same(pinoConfig, message.pinoConfig)
    })

    const lines = expected.map(JSON.stringify).join('\n')
    stream.worker.postMessage('not a PINO_CONFIG')
    // stream.emit('message', 'not a PINO_CONFIG')
    stream.worker.postMessage({ code: 'NOT_PINO_CONFIG', config: { levels: 'foo', messageKey: 'bar', errorKey: 'baz' } })
    // stream.emit('message', { code: 'NOT_PINO_CONFIG', config: { levels: 'foo', messageKey: 'bar', errorKey: 'baz' } })
    stream.worker.postMessage({ code: 'PINO_CONFIG', config: pinoConfig })
    // stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig })
    stream.write(lines)
    stream.end()
  })

  test(`waits for the pino config even if it is sent after write${description}`, function ({ same, plan }) {
    plan(4)
    const { port1, port2 } = new MessageChannel()
    const stream = new ThreadStream({
      filename: join(__dirname, 'fixtures', filename),
      workerData: {
        port: port1,
        pinoWillSendConfig: true,
        opts: {
          expectPinoConfig: true
        }
      },
      workerOpts: {
        transferList: [port1]
      }
    })

    const expected = [{
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'hello world'
    }, {
      level: 30,
      time: 1617955768092,
      pid: 2942,
      hostname: 'MacBook-Pro.local',
      msg: 'another message',
      prop: 42
    }]

    const pinoConfig = {
      levels: {
        labels: { 30: 'info' },
        values: { info: 30 }
      },
      messageKey: 'msg',
      errorKey: 'err'
    }

    port2.on('message', function (message) {
      same(expected.shift(), message.data)
      same(pinoConfig, message.pinoConfig)
    })

    const lines = expected.map(JSON.stringify).join('\n')
    stream.write(lines)
    stream.worker.postMessage({ code: 'PINO_CONFIG', config: pinoConfig })
    // stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig })
    stream.end()
  })

  test(`emits an error if the transport expects pino to send the config, but pino is not going to${description}`, async function ({ plan, same, ok }) {
    plan(2)
    const stream = new ThreadStream({
      filename: join(__dirname, 'fixtures', filename),
      workerData: {
        opts: {
          expectPinoConfig: true
        }
      }
    })
    const [err] = await once(stream, 'error')
    same(err.message, 'This transport is not compatible with the current version of pino. Please upgrade pino to the latest version.')
    ok(stream.destroyed)
  })
}

test('waits for the pino config when pipelining', function ({ same, plan }) {
  plan(2)
  const { port1, port2 } = new MessageChannel()
  const stream = new ThreadStream({
    filename: join(__dirname, 'fixtures', 'worker-pipeline.js'),
    workerData: {
      pinoWillSendConfig: true,
      targets: [{
        target: './transport-transform.js',
        options: {
          opts: { expectPinoConfig: true }
        }
      }, {
        target: './transport-on-data.js',
        options: {
          port: port1
        }
      }]
    },
    workerOpts: {
      transferList: [port1]
    }
  })

  const expected = [{
    level: 'info(30)',
    time: 1617955768092,
    pid: 2942,
    hostname: 'MacBook-Pro.local',
    msg: 'HELLO WORLD',
    service: 'from transform'
  }, {
    level: 'info(30)',
    time: 1617955768092,
    pid: 2942,
    hostname: 'MacBook-Pro.local',
    msg: 'ANOTHER MESSAGE',
    prop: 42,
    service: 'from transform'
  }]

  const lines = [{
    level: 30,
    time: 1617955768092,
    pid: 2942,
    hostname: 'MacBook-Pro.local',
    msg: 'hello world'
  }, {
    level: 30,
    time: 1617955768092,
    pid: 2942,
    hostname: 'MacBook-Pro.local',
    msg: 'another message',
    prop: 42
  }].map(JSON.stringify).join('\n')

  const pinoConfig = {
    levels: {
      labels: { 30: 'info' },
      values: { info: 30 }
    },
    messageKey: 'msg',
    errorKey: 'err'
  }

  port2.on('message', function (message) {
    same(expected.shift(), message.data)
  })

  stream.worker.postMessage({ code: 'PINO_CONFIG', config: pinoConfig })
  // stream.emit('message', { code: 'PINO_CONFIG', config: pinoConfig })
  stream.write(lines)
  stream.end()
})
