'use strict'

const { tspl } = require('@matteo.collina/tspl')
const { test, after } = require('node:test')
const { Client, Pool } = require('..')
const { createServer } = require('node:http')
const { readFileSync, createReadStream } = require('node:fs')
const { wrapWithAsyncIterable } = require('./utils/async-iterators')

test('basic get, async await support', async (t) => {
  t = tspl(t, { plan: 5 })

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
    t.strictEqual('/', req.url)
    t.strictEqual('GET', req.method)
    res.setHeader('content-type', 'text/plain')
    res.end('hello')
  })
  after(() => server.close())

  server.listen(0, async () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.on('disconnect', () => {
      if (!client.closed && !client.destroyed) {
        t.fail('unexpected disconnect')
      }
    })

    try {
      const { statusCode, headers, body } = await client.request({ path: '/', method: 'GET' })
      t.strictEqual(statusCode, 200)
      t.strictEqual(headers['content-type'], 'text/plain')
      const bufs = []
      body.on('data', (buf) => {
        bufs.push(buf)
      })
      body.on('end', () => {
        t.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
      })
    } catch (err) {
      t.fail(err)
    }
  })

  await t.completed
})

function postServer (t, expected) {
  return function (req, res) {
    t.strictEqual(req.url, '/')
    t.strictEqual(req.method, 'POST')

    req.setEncoding('utf8')
    let data = ''

    req.on('data', function (d) { data += d })

    req.on('end', () => {
      t.strictEqual(data, expected)
      res.end('hello')
    })
  }
}

test('basic POST with string, async await support', async (t) => {
  t = tspl(t, { plan: 5 })

  const expected = readFileSync(__filename, 'utf8')

  const server = createServer({ joinDuplicateHeaders: true }, postServer(t, expected))
  after(() => server.close())

  server.listen(0, async () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.on('disconnect', () => {
      if (!client.closed && !client.destroyed) {
        t.fail('unexpected disconnect')
      }
    })

    try {
      const { statusCode, body } = await client.request({ path: '/', method: 'POST', body: expected })
      t.strictEqual(statusCode, 200)
      const bufs = []
      body.on('data', (buf) => {
        bufs.push(buf)
      })
      body.on('end', () => {
        t.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
      })
    } catch (err) {
      t.fail(err)
    }
  })

  await t.completed
})

test('basic POST with Buffer, async await support', async (t) => {
  t = tspl(t, { plan: 5 })

  const expected = readFileSync(__filename)

  const server = createServer({ joinDuplicateHeaders: true }, postServer(t, expected.toString()))
  after(() => server.close())

  server.listen(0, async () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.on('disconnect', () => {
      if (!client.closed && !client.destroyed) {
        t.fail('unexpected disconnect')
      }
    })

    try {
      const { statusCode, body } = await client.request({ path: '/', method: 'POST', body: expected })
      t.strictEqual(statusCode, 200)
      const bufs = []
      body.on('data', (buf) => {
        bufs.push(buf)
      })
      body.on('end', () => {
        t.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
      })
    } catch (err) {
      t.fail(err)
    }
  })

  await t.completed
})

test('basic POST with stream, async await support', async (t) => {
  t = tspl(t, { plan: 5 })

  const expected = readFileSync(__filename, 'utf8')

  const server = createServer({ joinDuplicateHeaders: true }, postServer(t, expected))
  after(() => server.close())

  server.listen(0, async () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.on('disconnect', () => {
      if (!client.closed && !client.destroyed) {
        t.fail('unexpected disconnect')
      }
    })

    try {
      const { statusCode, body } = await client.request({
        path: '/',
        method: 'POST',
        headers: {
          'content-length': Buffer.byteLength(expected)
        },
        body: createReadStream(__filename)
      })
      t.strictEqual(statusCode, 200)
      const bufs = []
      body.on('data', (buf) => {
        bufs.push(buf)
      })
      body.on('end', () => {
        t.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
      })
    } catch (err) {
      t.fail(err)
    }
  })

  await t.completed
})

test('basic POST with async-iterator, async await support', async (t) => {
  t = tspl(t, { plan: 5 })

  const expected = readFileSync(__filename, 'utf8')

  const server = createServer({ joinDuplicateHeaders: true }, postServer(t, expected))
  after(() => server.close())

  server.listen(0, async () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.on('disconnect', () => {
      if (!client.closed && !client.destroyed) {
        t.fail('unexpected disconnect')
      }
    })

    try {
      const { statusCode, body } = await client.request({
        path: '/',
        method: 'POST',
        headers: {
          'content-length': Buffer.byteLength(expected)
        },
        body: wrapWithAsyncIterable(createReadStream(__filename))
      })
      t.strictEqual(statusCode, 200)
      const bufs = []
      body.on('data', (buf) => {
        bufs.push(buf)
      })
      body.on('end', () => {
        t.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
      })
    } catch (err) {
      t.fail(err)
    }
  })

  await t.completed
})

test('20 times GET with pipelining 10, async await support', async (t) => {
  const num = 20
  t = tspl(t, { plan: 2 * num + 1 })

  const sleep = ms => new Promise((resolve, reject) => {
    setTimeout(resolve, ms)
  })

  let count = 0
  let countGreaterThanOne = false
  const server = createServer({ joinDuplicateHeaders: true }, async (req, res) => {
    count++
    await sleep(10)
    countGreaterThanOne = countGreaterThanOne || count > 1
    res.end(req.url)
  })
  after(() => server.close())

  // needed to check for a warning on the maxListeners on the socket
  function onWarning (warning) {
    if (warning.name === 'MaxListenersExceededWarning') {
      t.fail()
    }
  }
  process.on('warning', onWarning)
  after(() => {
    process.removeListener('warning', onWarning)
  })

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`, {
      pipelining: 10
    })
    after(() => client.close())

    client.on('disconnect', () => {
      if (!client.closed && !client.destroyed) {
        t.fail('unexpected disconnect')
      }
    })

    for (let i = 0; i < num; i++) {
      makeRequest(i)
    }

    async function makeRequest (i) {
      await makeRequestAndExpectUrl(client, i, t)
      count--
      if (i === num - 1) {
        t.ok(countGreaterThanOne, 'seen more than one parallel request')
      }
    }
  })

  await t.completed
})

async function makeRequestAndExpectUrl (client, i, t) {
  try {
    const { statusCode, body } = await client.request({ path: '/' + i, method: 'GET', blocking: false })
    t.strictEqual(statusCode, 200)
    const bufs = []
    body.on('data', (buf) => {
      bufs.push(buf)
    })
    body.on('end', () => {
      t.strictEqual('/' + i, Buffer.concat(bufs).toString('utf8'))
    })
  } catch (err) {
    t.fail(err)
  }
  return true
}

test('pool, async await support', async (t) => {
  t = tspl(t, { plan: 5 })

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
    t.strictEqual('/', req.url)
    t.strictEqual('GET', req.method)
    res.setHeader('content-type', 'text/plain')
    res.end('hello')
  })
  after(() => server.close())

  server.listen(0, async () => {
    const client = new Pool(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.on('disconnect', () => {
      if (!client.closed && !client.destroyed) {
        t.fail('unexpected disconnect')
      }
    })

    try {
      const { statusCode, headers, body } = await client.request({ path: '/', method: 'GET' })
      t.strictEqual(statusCode, 200)
      t.strictEqual(headers['content-type'], 'text/plain')
      const bufs = []
      body.on('data', (buf) => {
        bufs.push(buf)
      })
      body.on('end', () => {
        t.strictEqual('hello', Buffer.concat(bufs).toString('utf8'))
      })
    } catch (err) {
      t.fail(err)
    }
  })

  await t.completed
})
