import { test } from '@japa/runner'
import sinon from 'sinon'
import {
  IResponse,
  Response,
  DummyLogger,
  CoreEngine,
  IHttpRequest,
  HttpEngineOptions,
  Headers,
  PayloadSerializer,
  HeadersReceivedDetail,
} from '../../../../src/index.js'

const logger = new DummyLogger()

test.group('_parseHeaders()', (group) => {
  let engine: CoreEngine
  let headersStr: string
  let headersBuf: Buffer
  group.each.setup(() => {
    const payloadBuffer = Buffer.from([0x74, 0x65, 0x73, 0x74, 0x0a, 0x74, 0x65, 0x73, 0x74])
    const request: IHttpRequest = {
      url: `http://localhost/v1/tests`,
      method: 'POST',
      headers: 'content-type: text/plain',
      payload: PayloadSerializer.stringifyBuffer(payloadBuffer),
    }
    const opts: HttpEngineOptions = {
      logger,
    }

    engine = new CoreEngine(request, opts)
    const response = Response.fromValues(0)
    response.loadingTime = 0
    engine.currentResponse = response
    headersStr = 'Content-Type: application/test\r\n'
    headersStr += 'Content-Length: 123\r\n'
    headersStr += 'Transfer-Encoding: chunked\r\n'
    headersBuf = Buffer.from(headersStr)
  })

  test('sets the headers property', ({ assert }) => {
    engine._parseHeaders(headersBuf)
    assert.typeOf(engine.currentResponse!.headers, 'string')
  })

  test('contains all headers', ({ assert }) => {
    engine._parseHeaders(headersBuf)
    const list: Record<string, string> = {}
    engine.currentHeaders!.forEach((value, name) => {
      list[name] = value
    })
    assert.lengthOf(Object.keys(list), 3)
  })

  test('sets the responseInfo.contentLength property', ({ assert }) => {
    engine._parseHeaders(headersBuf)
    assert.equal(engine.responseInfo!.contentLength, 123)
  })

  test('sets the responseInfo.chunked property', ({ assert }) => {
    engine._parseHeaders(headersBuf)
    assert.isTrue(engine.responseInfo!.chunked)
  })

  test('dispatches the headersreceived event', ({ assert }) => {
    const spy = sinon.spy()
    engine.once('headersreceived', spy)
    engine._parseHeaders(headersBuf)
    assert.isTrue(spy.calledOnce)
  })

  test('has the returnValue on the headersreceived event', ({ assert }) => {
    const spy = sinon.spy()
    engine.once('headersreceived', spy)
    engine._parseHeaders(headersBuf)
    const info: HeadersReceivedDetail = spy.args[0][0]
    assert.isTrue(info.returnValue)
  })

  test('has the value property on the headersreceived event', ({ assert }) => {
    const spy = sinon.spy()
    engine.once('headersreceived', spy)
    engine._parseHeaders(headersBuf)
    const info: HeadersReceivedDetail = spy.args[0][0]
    assert.ok(info.value)
    assert.typeOf(info.value, 'string')
  })

  test('aborts the request when the event is canceled', ({ assert }) => {
    engine.once('headersreceived', (detail) => {
      detail.returnValue = false
    })
    engine._parseHeaders(headersBuf)
    assert.isTrue(engine.aborted)
  })
})

test.group('prepareHeaders()', (group) => {
  let request: IHttpRequest
  let opts: HttpEngineOptions

  group.each.setup(() => {
    request = {
      url: 'https://api.domain.com',
      method: 'GET',
      headers: '',
    }
    opts = {
      defaultHeaders: true,
      logger,
    }
  })

  test('adds default user-agent', ({ assert }) => {
    const base = new CoreEngine(request, opts)
    const headers = new Headers()
    base.prepareHeaders(headers)
    assert.equal(headers.get('user-agent'), 'api client')
  })

  test('adds default accept', ({ assert }) => {
    const base = new CoreEngine(request, opts)
    const headers = new Headers()
    base.prepareHeaders(headers)
    assert.equal(headers.get('accept'), '*/*')
  })

  test('adds configured user-agent', ({ assert }) => {
    opts.defaultUserAgent = 'test'
    const base = new CoreEngine(request, opts)
    const headers = new Headers()
    base.prepareHeaders(headers)
    assert.equal(headers.get('user-agent'), 'test')
  })

  test('adds configured accept', ({ assert }) => {
    opts.defaultAccept = 'test'
    const base = new CoreEngine(request, opts)
    const headers = new Headers()
    base.prepareHeaders(headers)
    assert.equal(headers.get('accept'), 'test')
  })

  test('ignores adding headers when no config option', ({ assert }) => {
    opts.defaultHeaders = false
    const base = new CoreEngine(request, opts)
    const headers = new Headers()
    base.prepareHeaders(headers)
    assert.isFalse(headers.has('user-agent'), 'user-agent is not set')
    assert.isFalse(headers.has('accept'), 'accept is not set')
  })

  test('skips when user-agent header is set', ({ assert }) => {
    const base = new CoreEngine(request, opts)
    const headers = new Headers({
      'user-agent': 'test',
    })
    base.prepareHeaders(headers)
    assert.equal(headers.get('user-agent'), 'test')
  })

  test('skips when accept header is set', ({ assert }) => {
    const base = new CoreEngine(request, opts)
    const headers = new Headers({
      accept: 'test',
    })
    base.prepareHeaders(headers)
    assert.equal(headers.get('accept'), 'test')
  })

  test('adds the content-type when missing and defined in options', ({ assert }) => {
    const base = new CoreEngine({ ...request, method: 'POST', payloadOptions: { mime: 'test/text' } }, opts)
    const headers = new Headers()
    base.prepareHeaders(headers, true)
    assert.equal(headers.get('content-type'), 'test/text')
  })

  test('ignores adding the content-type when header set and defined in options', ({ assert }) => {
    const base = new CoreEngine({ ...request, method: 'POST', payloadOptions: { mime: 'test/text' } }, opts)
    const headers = new Headers({ 'content-type': 'test/other' })
    base.prepareHeaders(headers, true)
    assert.equal(headers.get('content-type'), 'test/other')
  })

  test('ignores adding the content-type when no options', ({ assert }) => {
    const base = new CoreEngine({ ...request, method: 'POST' }, opts)
    const headers = new Headers()
    base.prepareHeaders(headers, true)
    assert.isFalse(headers.has('content-type'))
  })
})

test.group('headers processing', (group) => {
  let request: IHttpRequest
  const opts: HttpEngineOptions = {
    logger,
  }

  group.each.setup((ctx) => {
    request = {
      url: `http://localhost:${ctx.context.httpConfig.httpPort}/v1/headers/`,
      method: 'GET',
      headers: '',
    }
  })

  test('sends a header', async ({ assert }) => {
    const r = { ...request }
    r.headers = 'x-test-header: true'
    const er = new CoreEngine(r, opts)
    const log = await er.send()
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer
    const body = JSON.parse(payload.toString('utf8'))
    assert.equal(body.headers['x-test-header'], 'true')
  })

  test('sends multiple headers', async ({ assert }) => {
    const r = { ...request }
    r.headers = 'x-test-header: true\nAccept-CH: DPR, Viewport-Width'
    const er = new CoreEngine(r, opts)
    const log = await er.send()
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer
    const body = JSON.parse(payload.toString('utf8'))
    assert.equal(body.headers['x-test-header'], 'true')
    assert.equal(body.headers['accept-ch'], 'DPR, Viewport-Width')
  })

  test('sends array headers', async ({ assert }) => {
    const r = { ...request }
    r.headers = 'x-test-header: true, x-value'
    const er = new CoreEngine(r, opts)
    const log = await er.send()
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer
    const body = JSON.parse(payload.toString('utf8'))
    assert.equal(body.headers['x-test-header'], 'true, x-value')
  })
})
