import { test } from '@japa/runner'
import { HttpResponse, HttpResponseKind, IHttpResponse, ISafePayload, PayloadSerializer } from '../../../src/index.js'

test.group('Default response initialization', () => {
  test('initializes a default project', ({ assert }) => {
    const result = new HttpResponse()
    assert.equal(result.kind, HttpResponseKind, 'sets the kind property')
    assert.equal(result.status, 0, 'sets the status property')
    assert.isUndefined(result.statusText, 'has no statusText property')
    assert.isUndefined(result.headers, 'has no headers property')
    assert.isUndefined(result.payload, 'has no payload property')
  })
})

test.group('From schema initialization', (group) => {
  let base: IHttpResponse
  group.each.setup(() => {
    base = {
      kind: HttpResponseKind,
      status: 0,
    }
  })

  test('sets the kind', ({ assert }) => {
    const init: IHttpResponse = { ...base }
    const response = new HttpResponse(init)
    assert.equal(response.kind, HttpResponseKind)
  })

  test('sets the status', ({ assert }) => {
    const init: IHttpResponse = { ...base, ...{ status: 200 } }
    const response = new HttpResponse(init)
    assert.equal(response.status, 200)
  })

  test('sets the statusText', ({ assert }) => {
    const init: IHttpResponse = { ...base, ...{ statusText: 'hello' } }
    const response = new HttpResponse(init)
    assert.equal(response.statusText, 'hello')
  })

  test('sets the headers', ({ assert }) => {
    const init: IHttpResponse = { ...base, ...{ headers: 'content-type: test' } }
    const response = new HttpResponse(init)
    assert.equal(response.headers, 'content-type: test')
  })

  test('sets the payload', ({ assert }) => {
    const init: IHttpResponse = { ...base, ...{ payload: 'test' } }
    const response = new HttpResponse(init)
    assert.equal(response.payload, 'test')
  })

  test('sets the values form serialized schema', ({ assert }) => {
    const init: IHttpResponse = {
      ...base,
      ...{
        status: 200,
        statusText: 'hello',
        headers: 'content-type: test',
        payload: 'test',
      },
    }
    const response = new HttpResponse(JSON.stringify(init))
    assert.equal(response.kind, HttpResponseKind, 'has the kind')
    assert.equal(response.status, 200, 'has the status')
    assert.equal(response.statusText, 'hello', 'has the statusText')
    assert.equal(response.headers, 'content-type: test', 'has the headers')
    assert.equal(response.payload, 'test', 'has the payload')
  })
})

test.group('toJSON()', () => {
  test('serializes the kind', ({ assert }) => {
    const response = new HttpResponse()
    const result = response.toJSON()
    assert.equal(result.kind, HttpResponseKind)
  })

  test('serializes the status', ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    const result = response.toJSON()
    assert.equal(result.status, 200)
  })

  test('serializes the statusText', ({ assert }) => {
    const response = HttpResponse.fromValues(200, 'hello')
    const result = response.toJSON()
    assert.equal(result.statusText, 'hello')
  })

  test('serializes the headers', ({ assert }) => {
    const response = HttpResponse.fromValues(200, 'hello', 'test')
    const result = response.toJSON()
    assert.equal(result.headers, 'test')
  })
})

test.group('Reading the payload', () => {
  test('reads the payload as string', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    response.payload = 'test'
    const result = await response.readPayload()
    assert.equal(result, 'test')
  })

  test('reads the Blob body', async ({ assert }) => {
    const data = 'data:text/plain;base64,Kioq'
    const response = HttpResponse.fromValues(200)
    response.payload = { type: 'blob', data }
    const result = await response.readPayload()
    const typed = result as Blob
    assert.typeOf(typed, 'blob')
    assert.equal(typed.type, 'text/plain')
    assert.equal(typed.size, 3)
  })

  test('reads the FormData body', async ({ assert }) => {
    const data = [
      {
        isFile: false,
        name: 'test-name',
        value: 'test-value',
      },
    ]
    const response = HttpResponse.fromValues(200)
    response.payload = { type: 'formdata', data }
    const result = await response.readPayload()
    const typed = result as FormData
    assert.typeOf(typed, 'formdata')
    const param = typed.get('test-name')
    assert.equal(param, 'test-value')
  })

  test('reads the ArrayBuffer body', async ({ assert }) => {
    const encoder = new TextEncoder()
    const view = encoder.encode('test-ab')
    const payload = PayloadSerializer.stringifyArrayBuffer(Uint8Array.from(view).buffer)

    const response = HttpResponse.fromValues(200)
    response.payload = payload
    const result = await response.readPayload()
    const typed = result as ArrayBuffer
    assert.typeOf(typed, 'ArrayBuffer')
  })

  test('returns undefined when no payload', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    const result = await response.readPayload()
    assert.isUndefined(result)
  })
})

test.group('Writing the payload', () => {
  test('writes the payload as string', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    await response.writePayload('test')
    assert.equal(response.payload, 'test')
  })

  test('writes the payload as Blob', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    const message = new Blob(['***** ***'], { type: 'text/plain' })
    await response.writePayload(message)
    const typed = response.payload as ISafePayload

    assert.equal(typed.type, 'blob')
    assert.typeOf(typed.data, 'array')
    assert.deepEqual(typed.data, [42, 42, 42, 42, 42, 32, 42, 42, 42])
    assert.equal(typed.meta!.mime, 'text/plain')
  })

  test('writes the payload as File', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    const message = new File(['***** ***'], 'foo.txt', { type: 'text/plain' })
    await response.writePayload(message)
    const typed = response.payload as ISafePayload

    assert.equal(typed.type, 'file')
    assert.typeOf(typed.data, 'array')
    assert.deepEqual(typed.data, [42, 42, 42, 42, 42, 32, 42, 42, 42])
    assert.equal(typed.meta!.mime, 'text/plain')
  })

  test('writes the payload as ArrayBuffer', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    const encoder = new TextEncoder()
    const view = encoder.encode('test')
    await response.writePayload(Uint8Array.from(view).buffer)
    const typed = response.payload as ISafePayload

    assert.equal(typed.type, 'arraybuffer')
    assert.typeOf(typed.data, 'array')
  })

  test('writes the payload as FormData', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    const b = new Blob(['***'], { type: 'text/plain' })
    const fd = new FormData()
    fd.append('file', b, 'file-name')
    fd.append('text', 'abcd')
    fd.append('text-part', b, 'text-part')
    await response.writePayload(fd)
    const typed = response.payload as ISafePayload

    assert.equal(typed.type, 'formdata')
    assert.typeOf(typed.data, 'array')
    assert.lengthOf(typed.data, 3)
  })

  test('sets undefined when no data', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    response.payload = 'test'
    await response.writePayload(undefined)
    assert.isUndefined(response.payload)
  })

  test('sets undefined when null data', async ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    response.payload = 'test'
    await response.writePayload(null)
    assert.isUndefined(response.payload)
  })
})

test.group('fromLegacy()', () => {
  test('sets the status', async ({ assert }) => {
    const response = await HttpResponse.fromLegacy({
      status: 200,
    })
    assert.equal(response.status, 200)
  })

  test('sets the statusText', async ({ assert }) => {
    const response = await HttpResponse.fromLegacy({
      status: 200,
      statusText: 'test',
    })
    assert.equal(response.statusText, 'test')
  })

  test('sets the headers', async ({ assert }) => {
    const response = await HttpResponse.fromLegacy({
      status: 200,
      headers: 'test',
    })
    assert.equal(response.headers, 'test')
  })

  test('sets the payload from a string', async ({ assert }) => {
    const response = await HttpResponse.fromLegacy({
      status: 200,
      payload: 'test',
    })
    assert.equal(response.payload, 'test')
  })

  test('sets the payload from an ArrayBuffer', async ({ assert }) => {
    const encoder = new TextEncoder()
    const view = encoder.encode('test')

    const response = await HttpResponse.fromLegacy({
      status: 200,
      payload: Uint8Array.from(view).buffer,
    })
    assert.ok(response.payload, 'has the payload')
    const payload = response.payload as ISafePayload
    assert.equal(payload.type, 'arraybuffer')
    assert.deepEqual(payload.data, [116, 101, 115, 116])
  })

  test('sets the payload from an ArrayBuffer as LegacyTransformedPayload', async ({ assert }) => {
    const encoder = new TextEncoder()
    const view = encoder.encode('test')

    const response = await HttpResponse.fromLegacy({
      status: 200,
      payload: {
        type: 'ArrayBuffer',
        data: [...view],
      },
    })
    assert.ok(response.payload, 'has the payload')
    const payload = response.payload as ISafePayload
    assert.equal(payload.type, 'arraybuffer')
    assert.deepEqual(payload.data, [116, 101, 115, 116])
  })
})
