import { test } from '@japa/runner'
import {
  HeadersArray,
  HttpRequest,
  HttpRequestKind,
  IHttpRequest,
  ISafePayload,
  PayloadSerializer,
  Property,
} from '../../../src/index.js'

test.group('Default request initialization', () => {
  test('initializes a default project', ({ assert }) => {
    const result = new HttpRequest()
    assert.equal(result.kind, HttpRequestKind, 'sets the kind property')
    assert.equal(result.url, '', 'sets the url property')
    assert.equal(result.method, 'GET', 'sets the method property')
    assert.isUndefined(result.headers, 'has no headers property')
    assert.isUndefined(result.payload, 'has no payload property')
    assert.isUndefined(result.headersModel, 'has no headersModel property')
  })
})

test.group('From schema initialization', (group) => {
  let base: IHttpRequest
  group.each.setup(() => {
    base = {
      kind: HttpRequestKind,
      url: '',
      method: '',
    }
  })

  test('sets the kind', ({ assert }) => {
    const init: IHttpRequest = { ...base }
    const request = new HttpRequest(init)
    assert.equal(request.kind, HttpRequestKind)
  })

  test('sets the url', ({ assert }) => {
    const init: IHttpRequest = { ...base, ...{ url: 'test.com' } }
    const request = new HttpRequest(init)
    assert.equal(request.url, 'test.com')
  })

  test('sets the method', ({ assert }) => {
    const init: IHttpRequest = { ...base, ...{ method: 'POST' } }
    const request = new HttpRequest(init)
    assert.equal(request.method, 'POST')
  })

  test('sets the headers', ({ assert }) => {
    const init: IHttpRequest = { ...base, ...{ headers: 'content-type: test' } }
    const request = new HttpRequest(init)
    assert.equal(request.headers, 'content-type: test')
  })

  test('sets the payload', ({ assert }) => {
    const init: IHttpRequest = { ...base, ...{ payload: 'test' } }
    const request = new HttpRequest(init)
    assert.equal(request.payload, 'test')
  })

  test('sets the values form serialized schema', ({ assert }) => {
    const init: IHttpRequest = {
      ...base,
      ...{
        url: 'test.com',
        method: 'POST',
        headers: 'content-type: test',
        payload: 'test',
      },
    }
    const request = new HttpRequest(JSON.stringify(init))
    assert.equal(request.kind, HttpRequestKind, 'has the kind')
    assert.equal(request.url, 'test.com', 'has the url')
    assert.equal(request.method, 'POST', 'has the method')
    assert.equal(request.headers, 'content-type: test', 'has the headers')
    assert.equal(request.payload, 'test', 'has the payload')
  })

  test('sets the headersModel property', ({ assert }) => {
    const init: IHttpRequest = {
      ...base,
      ...{
        headersModel: [Property.String('a', 'b', true).toJSON()],
      },
    }
    const request = new HttpRequest(JSON.stringify(init))
    assert.typeOf(request.headersModel, 'array')
    assert.lengthOf(request.headersModel!, 1)
    assert.instanceOf(request.headersModel![0], Property)
  })

  test('re-sets the headersModel property', ({ assert }) => {
    const init: IHttpRequest = {
      ...base,
      ...{
        headersModel: [Property.String('a', 'b', true).toJSON()],
      },
    }
    const request = new HttpRequest(JSON.stringify(init))
    delete init.headersModel
    request.new(init)
    assert.isUndefined(request.headersModel)
  })

  test('sets the payloadOptions property', ({ assert }) => {
    const init: IHttpRequest = {
      ...base,
      ...{
        payloadOptions: { mime: 'test/text' },
      },
    }
    const request = new HttpRequest(JSON.stringify(init))
    assert.ok(request.payloadOptions, 'has the payloadOptions')
    assert.equal(request.payloadOptions!.mime, 'test/text')
  })

  test('re-sets the headersModel property', ({ assert }) => {
    const init: IHttpRequest = {
      ...base,
      ...{
        payloadOptions: { mime: 'test/text' },
      },
    }
    const request = new HttpRequest(JSON.stringify(init))
    delete init.payloadOptions
    request.new(init)
    assert.isUndefined(request.payloadOptions)
  })
})

test.group('toJSON()', () => {
  test('serializes the kind', ({ assert }) => {
    const request = new HttpRequest()
    const result = request.toJSON()
    assert.equal(result.kind, HttpRequestKind)
  })

  test('serializes the url', ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: 'https://api.com', method: '' })
    const result = request.toJSON()
    assert.equal(result.url, 'https://api.com')
  })

  test('serializes the method', ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: 'PATCH' })
    const result = request.toJSON()
    assert.equal(result.method, 'PATCH')
  })

  test('serializes the headers', ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: '', headers: 'test' })
    const result = request.toJSON()
    assert.equal(result.headers, 'test')
  })

  test('serializes the payload', ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: '', payload: 'test' })
    const result = request.toJSON()
    assert.equal(result.payload, 'test')
  })

  test('serializes the headersModel when present', ({ assert }) => {
    const prop = Property.String('a', 'b', true).toJSON()
    const init: IHttpRequest = {
      url: '',
      method: '',
      headersModel: [prop],
    }
    const request = new HttpRequest(JSON.stringify(init))
    const result = request.toJSON()
    assert.deepEqual(result.headersModel![0], prop)
  })

  test('ignores the headersModel when not set', ({ assert }) => {
    const init: IHttpRequest = {
      url: '',
      method: '',
    }
    const request = new HttpRequest(JSON.stringify(init))
    const result = request.toJSON()
    assert.isUndefined(result.headersModel)
  })

  test('serializes the payloadOptions when present', ({ assert }) => {
    const init: IHttpRequest = {
      url: '',
      method: '',
      payloadOptions: { mime: 'test/text' },
    }
    const request = new HttpRequest(JSON.stringify(init))
    const result = request.toJSON()
    assert.deepEqual(result.payloadOptions, { mime: 'test/text' })
  })

  test('ignores the payloadOptions when not set', ({ assert }) => {
    const init: IHttpRequest = {
      url: '',
      method: '',
    }
    const request = new HttpRequest(JSON.stringify(init))
    const result = request.toJSON()
    assert.isUndefined(result.payloadOptions)
  })
})

test.group('Reading the payload', () => {
  test('reads the payload as string', async ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: '', payload: 'test' })
    const result = await request.readPayload()
    assert.equal(result, 'test')
  })

  test('reads the Blob body', async ({ assert }) => {
    const data = 'data:text/plain;base64,Kioq'
    const request = HttpRequest.fromBaseValues({ url: '', method: '', payload: { type: 'blob', data } })
    const result = await request.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 request = HttpRequest.fromBaseValues({ url: '', method: '', payload: { type: 'formdata', data } })
    const result = await request.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 request = HttpRequest.fromBaseValues({ url: '', method: '', payload })
    const result = await request.readPayload()
    const typed = result as ArrayBuffer
    assert.typeOf(typed, 'ArrayBuffer')
  })

  test('returns undefined when no payload', async ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: '' })
    const result = await request.readPayload()
    assert.isUndefined(result)
  })
})

test.group('Writing the payload', () => {
  test('writes the payload as string', async ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: '' })
    await request.writePayload('test')
    assert.equal(request.payload, 'test')
  })

  test('writes the payload as Blob', async ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: '' })
    const message = new Blob(['***** ***'], { type: 'text/plain' })
    await request.writePayload(message)
    const typed = request.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 request = HttpRequest.fromBaseValues({ url: '', method: '' })
    const message = new File(['***** ***'], 'foo.txt', { type: 'text/plain' })
    await request.writePayload(message)
    const typed = request.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 request = HttpRequest.fromBaseValues({ url: '', method: '' })
    const encoder = new TextEncoder()
    const view = encoder.encode('test')
    await request.writePayload(Uint8Array.from(view).buffer)
    const typed = request.payload as ISafePayload

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

  test('writes the payload as FormData', async ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: '' })
    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 request.writePayload(fd)
    const typed = request.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 request = HttpRequest.fromBaseValues({ url: '', method: '', payload: 'test' })
    await request.writePayload(undefined)
    assert.isUndefined(request.payload)
  })

  test('sets undefined when null data', async ({ assert }) => {
    const request = HttpRequest.fromBaseValues({ url: '', method: '', payload: 'test' })
    await request.writePayload(null)
    assert.isUndefined(request.payload)
  })
})

test.group('HttpRequest.isHttpRequest()', () => {
  test('returns false when no input', ({ assert }) => {
    const result = HttpRequest.isHttpRequest(undefined)
    assert.isFalse(result)
  })

  test('returns false when invalid type', ({ assert }) => {
    const instance = Property.String('abc')
    const result = HttpRequest.isHttpRequest(instance)
    assert.isFalse(result)
  })

  test('returns true when HttpRequest type', ({ assert }) => {
    const instance = new HttpRequest()
    const result = HttpRequest.isHttpRequest(instance)
    assert.isTrue(result)
  })
})

test.group('toHeadersString()', () => {
  test('returns an empty string by default', ({ assert }) => {
    const instance = new HttpRequest()
    const result = instance.toHeadersString()
    assert.equal(result, '')
  })

  test('returns headers string instead of the model string', ({ assert }) => {
    const instance = new HttpRequest()
    const prop = Property.String('a', 'b')
    instance.headersModel = HeadersArray.from([prop.toJSON()])
    instance.headers = 'c: d'
    const result = instance.toHeadersString()
    assert.equal(result, 'c: d')
  })

  test('returns headers string read from the model', ({ assert }) => {
    const instance = new HttpRequest()
    const prop = Property.String('a', 'b')
    instance.headersModel = HeadersArray.from([prop.toJSON()])
    const result = instance.toHeadersString()
    assert.equal(result, 'a: b')
  })
})
