'use strict'

const { tspl } = require('@matteo.collina/tspl')
const { test, after } = require('node:test')
const { Client, errors } = require('..')
const { createServer } = require('node:http')

test('handle headers as array', async (t) => {
  t = tspl(t, { plan: 3 })
  const headers = ['a', '1', 'b', '2', 'c', '3']

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
    t.strictEqual(req.headers.a, '1')
    t.strictEqual(req.headers.b, '2')
    t.strictEqual(req.headers.c, '3')
    res.end()
  })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

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

    client.request({
      path: '/',
      method: 'GET',
      headers
    }, () => { })
  })

  await t.completed
})

test('handle multi-valued headers as array', async (t) => {
  t = tspl(t, { plan: 4 })
  const headers = ['a', '1', 'b', '2', 'c', '3', 'd', '4', 'd', '5']

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
    t.strictEqual(req.headers.a, '1')
    t.strictEqual(req.headers.b, '2')
    t.strictEqual(req.headers.c, '3')
    t.strictEqual(req.headers.d, '4, 5')
    res.end()
  })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

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

    client.request({
      path: '/',
      method: 'GET',
      headers
    }, () => { })
  })

  await t.completed
})

test('handle headers with array', async (t) => {
  t = tspl(t, { plan: 4 })
  const headers = { a: '1', b: '2', c: '3', d: ['4'] }

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
    t.strictEqual(req.headers.a, '1')
    t.strictEqual(req.headers.b, '2')
    t.strictEqual(req.headers.c, '3')
    t.strictEqual(req.headers.d, '4')
    res.end()
  })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

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

    client.request({
      path: '/',
      method: 'GET',
      headers
    }, () => { })
  })

  await t.completed
})

test('handle multi-valued headers', async (t) => {
  t = tspl(t, { plan: 4 })
  const headers = { a: '1', b: '2', c: '3', d: ['4', '5'] }

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
    t.strictEqual(req.headers.a, '1')
    t.strictEqual(req.headers.b, '2')
    t.strictEqual(req.headers.c, '3')
    t.strictEqual(req.headers.d, '4, 5')
    res.end()
  })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

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

    client.request({
      path: '/',
      method: 'GET',
      headers
    }, () => { })
  })

  await t.completed
})

test('fail if headers array is odd', async (t) => {
  t = tspl(t, { plan: 2 })
  const headers = ['a', '1', 'b', '2', 'c', '3', 'd']

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { res.end() })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

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

    client.request({
      path: '/',
      method: 'GET',
      headers
    }, (err) => {
      t.ok(err instanceof errors.InvalidArgumentError)
      t.strictEqual(err.message, 'headers array must be even')
    })
  })

  await t.completed
})

test('fail if headers is not an object or an array', async (t) => {
  t = tspl(t, { plan: 2 })
  const headers = 'not an object or an array'

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { res.end() })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

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

    client.request({
      path: '/',
      method: 'GET',
      headers
    }, (err) => {
      t.ok(err instanceof errors.InvalidArgumentError)
      t.strictEqual(err.message, 'headers must be an object or an array')
    })
  })

  await t.completed
})

test('fail if duplicate content-length headers (different case)', async (t) => {
  t = tspl(t, { plan: 2 })
  const headers = ['Content-Length', '5', 'content-length', '0']

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { res.end() })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.request({
      path: '/',
      method: 'POST',
      headers,
      body: 'hello'
    }, (err) => {
      t.ok(err instanceof errors.InvalidArgumentError)
      t.strictEqual(err.message, 'duplicate content-length header')
    })
  })

  await t.completed
})

test('fail if duplicate content-length headers (same case)', async (t) => {
  t = tspl(t, { plan: 2 })
  const headers = ['content-length', '5', 'content-length', '0']

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { res.end() })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.request({
      path: '/',
      method: 'POST',
      headers,
      body: 'hello'
    }, (err) => {
      t.ok(err instanceof errors.InvalidArgumentError)
      t.strictEqual(err.message, 'duplicate content-length header')
    })
  })

  await t.completed
})

test('fail if duplicate host headers (different case)', async (t) => {
  t = tspl(t, { plan: 2 })
  const headers = ['Host', 'example.com', 'host', 'evil.com']

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { res.end() })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.request({
      path: '/',
      method: 'GET',
      headers
    }, (err) => {
      t.ok(err instanceof errors.InvalidArgumentError)
      t.strictEqual(err.message, 'duplicate host header')
    })
  })

  await t.completed
})

test('fail if duplicate host headers (same case)', async (t) => {
  t = tspl(t, { plan: 2 })
  const headers = ['host', 'example.com', 'host', 'evil.com']

  const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { res.end() })
  after(() => server.close())
  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    after(() => client.close())

    client.request({
      path: '/',
      method: 'GET',
      headers
    }, (err) => {
      t.ok(err instanceof errors.InvalidArgumentError)
      t.strictEqual(err.message, 'duplicate host header')
    })
  })

  await t.completed
})
