import { test } from '@japa/runner'
import {
  IResponse,
  Response,
  createLogger,
  CoreEngine,
  IHttpRequest,
  HttpEngineOptions,
  HostRuleKind,
  IHostRule,
} from '../../../../src/index.js'

const logger = createLogger()

test.group('BaseRequest uri', (group) => {
  let request: IHttpRequest

  group.each.setup(() => {
    request = {
      url: 'https://domain.com',
      method: 'GET',
    }
  })

  test('parses the URL', ({ assert }) => {
    const engine = new CoreEngine(request)
    assert.typeOf(engine.uri, 'URL')
    assert.equal(engine.uri.hostname, 'domain.com')
  })

  test('changes uri', ({ assert }) => {
    const engine = new CoreEngine(request)
    const result = engine.readUrl('http://other.com')
    assert.typeOf(result, 'URL')
    assert.equal(result.hostname, 'other.com')
  })

  test('applies host rules', ({ assert }) => {
    const hosts: IHostRule[] = [{ from: 'domain.com', to: 'other.com', kind: HostRuleKind, key: '1' }]
    const engine = new CoreEngine(request, {
      hosts,
    })
    assert.equal(engine.uri.hostname, 'other.com')
  })
})

test.group('Sending request parameters', () => {
  test('sends query parameters to the server', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/query-params?a=b&c=d`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const payloadString = payload.toString()
    const body = JSON.parse(payloadString)
    assert.deepEqual(body.params.query, {
      a: 'b',
      c: 'd',
    })
  })

  test('sends headers to the server', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/headers`,
      method: 'GET',
      headers: 'x-test: true\naccept: application/json',
    }
    const opts: HttpEngineOptions = {
      logger,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const payloadString = payload.toString()
    const body = JSON.parse(payloadString)
    assert.deepEqual(body.headers, {
      'accept': 'application/json',
      'host': `localhost:${httpConfig.httpPort}`,
      'x-test': 'true',
    })
  })
})

test.group('query parameters processing', (group) => {
  let requestData: IHttpRequest
  const opts: HttpEngineOptions = {
    logger,
  }

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

  test('sends a query parameter', async ({ assert }) => {
    const r: IHttpRequest = { ...requestData }
    r.url += '?a=b'
    const request = new CoreEngine(r, opts)
    const log = await request.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.deepEqual(body, { params: { query: { a: 'b' } } })
  })

  test('sends a multiple query parameters', async ({ assert }) => {
    const r = { ...requestData }
    r.url += '?a=b&c=1&d=true'
    const request = new CoreEngine(r, opts)
    const log = await request.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.deepEqual(body, {
      params: {
        query: {
          a: 'b',
          c: '1',
          d: 'true',
        },
      },
    })
  })

  test('sends an array query parameters', async ({ assert }) => {
    const r = { ...requestData }
    r.url += '?a=b&a=c&a=d'
    const request = new CoreEngine(r, opts)
    const log = await request.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.deepEqual(body, {
      params: {
        query: {
          a: ['b', 'c', 'd'],
        },
      },
    })
  })

  test('sends an array query parameters with brackets', async ({ assert }) => {
    const r = { ...requestData }
    r.url += '?a[]=b&a[]=c&a[]=d'
    const request = new CoreEngine(r, opts)
    const log = await request.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.deepEqual(body, {
      params: {
        query: {
          a: ['b', 'c', 'd'],
        },
      },
    })
  })

  test('sends mixed query parameters', async ({ assert }) => {
    const r = { ...requestData }
    r.url += '?a[]=b&a[]=c&b=a&b=b&c=d'
    const request = new CoreEngine(r, opts)
    const log = await request.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.deepEqual(body, {
      params: {
        query: {
          a: ['b', 'c'],
          b: ['a', 'b'],
          c: 'd',
        },
      },
    })
  })
})

test.group('Sending request parameters', (group) => {
  const jsonBody = JSON.stringify({ test: true, body: 'some value' })
  let opts: HttpEngineOptions

  group.each.setup(() => {
    opts = {
      timeout: 9500,
      followRedirects: false,
      logger,
      hosts: [
        {
          kind: HostRuleKind,
          from: 'domain.com',
          to: 'test.com',
          key: '1',
        },
      ],
    }
  })

  test('sends query parameters to the server', async ({ assert, httpConfig }) => {
    const info = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get?a=b&c=d`,
      method: 'GET',
      headers: 'x-test: true\naccept: application/json',
    }
    const request = new CoreEngine(info, opts)

    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const payloadString = payload.toString()
    const body = JSON.parse(payloadString)
    assert.deepEqual(body.query, { a: 'b', c: 'd' })
  })

  test('sends headers to the server', async ({ assert, httpConfig }) => {
    const info = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get?a=b&c=d`,
      method: 'GET',
      headers: 'x-test: true\naccept: application/json',
    }
    const request = new CoreEngine(info, opts)

    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const { headers } = JSON.parse(payload.toString('utf8'))
    assert.deepEqual(headers, {
      'x-test': 'true',
      'accept': 'application/json',
      'host': `localhost:${httpConfig.httpPort}`,
      // 'connection': 'keep-alive',
    })
  })

  test('sends the default headers', async ({ assert, httpConfig }) => {
    const options = { ...opts }
    options.defaultHeaders = true
    const info = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get`,
      method: 'GET',
    }
    const request = new CoreEngine(info, options)
    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const { headers } = JSON.parse(payload.toString('utf8'))
    assert.deepEqual(headers, {
      'user-agent': 'api client',
      'accept': '*/*',
      'host': `localhost:${httpConfig.httpPort}`,
      // 'connection': 'keep-alive',
    })
  })

  test('adds passed accept header value', async ({ assert, httpConfig }) => {
    const options = { ...opts }
    options.defaultHeaders = true
    options.defaultAccept = 'application/json'
    const info = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get`,
      method: 'GET',
    }
    const request = new CoreEngine(info, options)

    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const { headers } = JSON.parse(payload.toString('utf8'))
    assert.equal(headers.accept, 'application/json')
  })

  test('adds passed user-agent header value', async ({ assert, httpConfig }) => {
    const options = { ...opts }
    options.defaultHeaders = true
    options.defaultUserAgent = 'test-run'
    const info = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get`,
      method: 'GET',
    }
    const request = new CoreEngine(info, options)

    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const { headers } = JSON.parse(payload.toString('utf8'))
    assert.equal(headers['user-agent'], 'test-run')
  })

  test('adds content-length header when body is send', async ({ assert, httpConfig }) => {
    const info: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/post`,
      method: 'POST',
      headers: 'content-type: application/json',
      payload: jsonBody,
    }
    const request = new CoreEngine(info, opts)

    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const { headers } = JSON.parse(payload.toString('utf8'))
    assert.equal(headers['content-length'], '33')
  })

  test('sends the string body with the request', async ({ assert, httpConfig }) => {
    const info: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/post`,
      method: 'POST',
      headers: 'content-type: application/json',
      payload: jsonBody,
    }
    const request = new CoreEngine(info, opts)

    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const { body } = JSON.parse(payload.toString('utf8'))
    assert.deepEqual(body, JSON.parse(jsonBody))
  })

  test('sends the buffer body with the request', async ({ assert, httpConfig }) => {
    const info: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/post`,
      method: 'POST',
      headers: 'content-type: application/json',
      payload: {
        type: 'buffer',
        data: [...Buffer.from(jsonBody)],
      },
    }
    const request = new CoreEngine(info, opts)

    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer

    const { body } = JSON.parse(payload.toString('utf8'))
    assert.deepEqual(body, JSON.parse(jsonBody))
  })

  test('sends the FormData body with the request', async ({ assert, httpConfig }) => {
    const info: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/post`,
      method: 'POST',
      headers: 'content-type: application/json',
      payload: {
        type: 'formdata',
        data: [
          {
            isFile: false,
            name: 'a',
            value: 'b',
          },
        ],
      },
    }

    const request = new CoreEngine(info, opts)

    const log = await request.send()
    assert.ok(log.response, 'has the response')
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayload('buffer')) as Buffer
    const { body } = JSON.parse(payload.toString('utf8'))
    assert.include(body, 'name="a"')
  })
})
