'use strict'

const Dicer = require('../deps/dicer/lib/Dicer')
const assert = require('node:assert')
const fs = require('node:fs')
const path = require('node:path')
const inspect = require('node:util').inspect
const { test } = require('tap')

const FIXTURES_ROOT = path.join(__dirname, 'fixtures/')

test('dicer-multipart', t => {
  const tests =
    [
      {
        source: 'nested',
        opts: { boundary: 'AaB03x' },
        chsize: 32,
        nparts: 2,
        what: 'One nested multipart'
      },
      {
        source: 'many',
        opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
        chsize: 16,
        nparts: 7,
        what: 'Many parts'
      },
      {
        source: 'many-wrongboundary',
        opts: { boundary: 'LOLOLOL' },
        chsize: 8,
        nparts: 0,
        dicerError: true,
        what: 'Many parts, wrong boundary'
      },
      {
        source: 'many-noend',
        opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
        chsize: 16,
        nparts: 7,
        npartErrors: 1,
        dicerError: true,
        what: 'Many parts, end boundary missing, 1 file open'
      },
      {
        source: 'nested-full',
        opts: { boundary: 'AaB03x', headerFirst: true },
        chsize: 32,
        nparts: 2,
        what: 'One nested multipart with preceding header'
      },
      {
        source: 'nested-full',
        opts: { headerFirst: true },
        chsize: 32,
        nparts: 2,
        setBoundary: 'AaB03x',
        what: 'One nested multipart with preceding header, using setBoundary'
      }
    ]

  t.plan(tests.length)

  tests.forEach(function (v) {
    t.test(v.what, t => {
      t.plan(1)
      const fixtureBase = FIXTURES_ROOT + v.source
      const state = { parts: [], preamble: undefined }

      const dicer = new Dicer(v.opts)
      let error
      let partErrors = 0
      let finishes = 0

      dicer.on('preamble', function (p) {
        const preamble = {
          body: undefined,
          bodylen: 0,
          error: undefined,
          header: undefined
        }

        p.on('header', function (h) {
          preamble.header = h
          if (v.setBoundary) { dicer.setBoundary(v.setBoundary) }
        }).on('data', function (data) {
          // make a copy because we are using readSync which re-uses a buffer ...
          const copy = Buffer.allocUnsafe(data.length)
          data.copy(copy)
          data = copy
          if (!preamble.body) { preamble.body = [data] } else { preamble.body.push(data) }
          preamble.bodylen += data.length
        }).on('error', function (err) {
          preamble.error = err
        }).on('end', function () {
          if (preamble.body) { preamble.body = Buffer.concat(preamble.body, preamble.bodylen) }
          if (preamble.body || preamble.header) { state.preamble = preamble }
        })
      })
      dicer.on('part', function (p) {
        const part = {
          body: undefined,
          bodylen: 0,
          error: undefined,
          header: undefined
        }

        p.on('header', function (h) {
          part.header = h
        }).on('data', function (data) {
          if (!part.body) { part.body = [data] } else { part.body.push(data) }
          part.bodylen += data.length
        }).on('error', function (err) {
          part.error = err
          ++partErrors
        }).on('end', function () {
          if (part.body) { part.body = Buffer.concat(part.body, part.bodylen) }
          state.parts.push(part)
        })
      }).on('error', function (err) {
        error = err
      }).on('finish', function () {
        assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'))

        if (v.dicerError) { assert(error !== undefined, makeMsg(v.what, 'Expected error')) } else { assert(error === undefined, makeMsg(v.what, 'Unexpected error: ' + error)) }

        let preamble
        if (fs.existsSync(fixtureBase + '/preamble')) {
          const prebody = fs.readFileSync(fixtureBase + '/preamble')
          if (prebody.length) {
            preamble = {
              body: prebody,
              bodylen: prebody.length,
              error: undefined,
              header: undefined
            }
          }
        }
        if (fs.existsSync(fixtureBase + '/preamble.header')) {
          const prehead = JSON.parse(fs.readFileSync(fixtureBase +
            '/preamble.header', 'binary'))
          if (!preamble) {
            preamble = {
              body: undefined,
              bodylen: 0,
              error: undefined,
              header: prehead
            }
          } else { preamble.header = prehead }
        }
        if (fs.existsSync(fixtureBase + '/preamble.error')) {
          const err = new Error(fs.readFileSync(fixtureBase +
            '/preamble.error', 'binary'))
          if (!preamble) {
            preamble = {
              body: undefined,
              bodylen: 0,
              error: err,
              header: undefined
            }
          } else { preamble.error = err }
        }

        assert.deepEqual(state.preamble,
          preamble,
          makeMsg(v.what,
            'Preamble mismatch:\nActual:' +
            inspect(state.preamble) +
            '\nExpected: ' +
            inspect(preamble)))

        assert.equal(state.parts.length,
          v.nparts,
          makeMsg(v.what,
            'Part count mismatch:\nActual: ' +
            state.parts.length +
            '\nExpected: ' +
            v.nparts))

        if (!v.npartErrors) { v.npartErrors = 0 }
        assert.equal(partErrors,
          v.npartErrors,
          makeMsg(v.what,
            'Part errors mismatch:\nActual: ' +
            partErrors +
            '\nExpected: ' +
            v.npartErrors))

        for (let i = 0, header, body; i < v.nparts; ++i) {
          if (fs.existsSync(fixtureBase + '/part' + (i + 1))) {
            body = fs.readFileSync(fixtureBase + '/part' + (i + 1))
            if (body.length === 0) { body = undefined }
          } else { body = undefined }
          assert.deepEqual(state.parts[i].body,
            body,
            makeMsg(v.what,
              'Part #' + (i + 1) + ' body mismatch'))
          if (fs.existsSync(fixtureBase + '/part' + (i + 1) + '.header')) {
            header = fs.readFileSync(fixtureBase +
              '/part' + (i + 1) + '.header', 'binary')
            header = JSON.parse(header)
          } else { header = undefined }
          assert.deepEqual(state.parts[i].header,
            header,
            makeMsg(v.what,
              'Part #' + (i + 1) +
              ' parsed header mismatch:\nActual: ' +
              inspect(state.parts[i].header) +
              '\nExpected: ' +
              inspect(header)))
        }
        t.pass()
      })

      fs.createReadStream(fixtureBase + '/original').pipe(dicer)
    })
  })
})

function makeMsg (what, msg) {
  return what + ': ' + msg
}
