'use strict'

const { test } = require('tap')
const {
  Response
} = require('../../')
const { ReadableStream } = require('stream/web')

test('arg validation', async (t) => {
  // constructor
  t.throws(() => {
    // eslint-disable-next-line
    new Response(null, 0)
  }, TypeError)
  t.throws(() => {
    // eslint-disable-next-line
    new Response(null, {
      status: 99
    })
  }, RangeError)
  t.throws(() => {
    // eslint-disable-next-line
    new Response(null, {
      status: 600
    })
  }, RangeError)
  t.throws(() => {
    // eslint-disable-next-line
    new Response(null, {
      status: '600'
    })
  }, RangeError)
  t.throws(() => {
    // eslint-disable-next-line
    new Response(null, {
      statusText: '\u0000'
    })
  }, TypeError)

  for (const nullStatus of [204, 205, 304]) {
    t.throws(() => {
      // eslint-disable-next-line
      new Response(new ArrayBuffer(16), {
        status: nullStatus
      })
    }, TypeError)
  }

  t.doesNotThrow(() => {
    Response.prototype[Symbol.toStringTag].charAt(0)
  }, TypeError)

  t.throws(() => {
    Response.prototype.type.toString()
  }, TypeError)

  t.throws(() => {
    Response.prototype.url.toString()
  }, TypeError)

  t.throws(() => {
    Response.prototype.redirected.toString()
  }, TypeError)

  t.throws(() => {
    Response.prototype.status.toString()
  }, TypeError)

  t.throws(() => {
    Response.prototype.ok.toString()
  }, TypeError)

  t.throws(() => {
    Response.prototype.statusText.toString()
  }, TypeError)

  t.throws(() => {
    Response.prototype.headers.toString()
  }, TypeError)

  t.throws(() => {
    // eslint-disable-next-line no-unused-expressions
    Response.prototype.body
  }, TypeError)

  t.throws(() => {
    // eslint-disable-next-line no-unused-expressions
    Response.prototype.bodyUsed
  }, TypeError)

  t.throws(() => {
    Response.prototype.clone.call(null)
  }, TypeError)

  await t.rejects(async () => {
    await new Response('http://localhost').text.call({
      blob () {
        return {
          text () {
            return Promise.resolve('emulating response.blob()')
          }
        }
      }
    })
  }, TypeError)

  t.end()
})

test('response clone', (t) => {
  // https://github.com/nodejs/undici/issues/1122
  const response1 = new Response(null, { status: 201 })
  const response2 = new Response(undefined, { status: 201 })

  t.equal(response1.body, response1.clone().body)
  t.equal(response2.body, response2.clone().body)
  t.equal(response2.body, null)
  t.end()
})

test('Symbol.toStringTag', (t) => {
  const resp = new Response()

  t.equal(resp[Symbol.toStringTag], 'Response')
  t.equal(Response.prototype[Symbol.toStringTag], 'Response')
  t.end()
})

test('async iterable body', async (t) => {
  const asyncIterable = {
    async * [Symbol.asyncIterator] () {
      yield 'a'
      yield 'b'
      yield 'c'
    }
  }

  const response = new Response(asyncIterable)
  t.equal(await response.text(), 'abc')
  t.end()
})

// https://github.com/nodejs/node/pull/43752#issuecomment-1179678544
test('Modifying headers using Headers.prototype.set', (t) => {
  const response = new Response('body', {
    headers: {
      'content-type': 'test/test',
      'Content-Encoding': 'hello/world'
    }
  })

  const response2 = response.clone()

  response.headers.set('content-type', 'application/wasm')
  response.headers.set('Content-Encoding', 'world/hello')

  t.equal(response.headers.get('content-type'), 'application/wasm')
  t.equal(response.headers.get('Content-Encoding'), 'world/hello')

  response2.headers.delete('content-type')
  response2.headers.delete('Content-Encoding')

  t.equal(response2.headers.get('content-type'), null)
  t.equal(response2.headers.get('Content-Encoding'), null)

  t.end()
})

// https://github.com/nodejs/node/issues/43838
test('constructing a Response with a ReadableStream body', async (t) => {
  const text = '{"foo":"bar"}'
  const uint8 = new TextEncoder().encode(text)

  t.test('Readable stream with Uint8Array chunks', async (t) => {
    const readable = new ReadableStream({
      start (controller) {
        controller.enqueue(uint8)
        controller.close()
      }
    })

    const response1 = new Response(readable)
    const response2 = response1.clone()
    const response3 = response1.clone()

    t.equal(await response1.text(), text)
    t.same(await response2.arrayBuffer(), uint8.buffer)
    t.same(await response3.json(), JSON.parse(text))

    t.end()
  })

  t.test('Readable stream with non-Uint8Array chunks', async (t) => {
    const readable = new ReadableStream({
      start (controller) {
        controller.enqueue(text) // string
        controller.close()
      }
    })

    const response = new Response(readable)

    await t.rejects(response.text(), TypeError)

    t.end()
  })

  t.test('Readable with ArrayBuffer chunk still throws', async (t) => {
    const readable = new ReadableStream({
      start (controller) {
        controller.enqueue(uint8.buffer)
        controller.close()
      }
    })

    const response1 = new Response(readable)
    const response2 = response1.clone()
    const response3 = response1.clone()
    // const response4 = response1.clone()

    await t.rejects(response1.arrayBuffer(), TypeError)
    await t.rejects(response2.text(), TypeError)
    await t.rejects(response3.json(), TypeError)
    // TODO: on Node v16.8.0, this throws a TypeError
    // because the body is detected as disturbed.
    // await t.rejects(response4.blob(), TypeError)

    t.end()
  })

  t.end()
})
