import { test } from '@japa/runner'
import {
  ISentRequest,
  IResponse,
  Response,
  createLogger,
  CoreEngine,
  IHttpRequest,
  HttpEngineOptions,
  ResponseRedirect,
  IResponseRedirect,
  HttpResponse,
} from '../../../../src/index.js'

const logger = createLogger()

test.group('Absolute redirects', (group) => {
  let opts: HttpEngineOptions
  let baseRequest: IHttpRequest

  group.each.setup((ctx) => {
    opts = {
      logger,
    }
    baseRequest = {
      url: `http://localhost:${ctx.context.httpConfig.httpPort}/v1/redirect/absolute/2?test=true`,
      method: 'GET',
    }
  })

  test('redirects to an absolute URL', async ({ assert }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()
    assert.typeOf(log.redirects!, 'array', 'has the redirects')
    assert.lengthOf(log.redirects!, 2, 'has both redirects')
  })

  test('has the redirects data', async ({ assert, httpConfig }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()
    const redirects = log.redirects!.map((i) => new ResponseRedirect(i))
    const rdr1 = redirects[0]!

    const location = `http://localhost:${httpConfig.httpPort}/v1/redirect/absolute/1?test=true`
    assert.equal(rdr1.url, location, 'has the redirect URL')
    assert.typeOf(rdr1.startTime, 'number', 'has the startTime property')
    assert.typeOf(rdr1.endTime, 'number', 'has the endTime property')
    assert.typeOf(rdr1.timings, 'object', 'has the timings property')
    assert.typeOf(rdr1.response, 'object', 'has the response property')

    const rsp = rdr1.response!
    assert.equal(rsp.status, 302, 'has the status code')
    assert.equal(rsp.statusText, 'Found', 'has the status text')
    assert.typeOf(rsp.headers, 'string', 'has the headers')

    const body = (await rsp.readPayload('buffer')) as Buffer
    const parsed = JSON.parse(body.toString('utf8'))
    assert.equal(parsed.location, location, 'has the payload')
  })

  test('has the final response', async ({ assert, httpConfig }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()

    const transport = log.request as ISentRequest
    const response = log.response as IResponse

    const location = `http://localhost:${httpConfig.httpPort}/v1/get?test=true`
    assert.equal(transport.url, location, 'transport request has the final URL')
    assert.equal(response.status, 200, 'has the status code')
  })
})

test.group('Relative redirects - relative path', (group) => {
  let baseRequest: IHttpRequest
  let opts: HttpEngineOptions

  group.each.setup((ctx) => {
    opts = {
      logger,
    }
    baseRequest = {
      url: `http://localhost:${ctx.context.httpConfig.httpPort}/v1/redirect/relative/2?test=true`,
      method: 'GET',
    }
  })

  test('redirects to a relative URL', async ({ assert }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()
    assert.typeOf(log.redirects!, 'array', 'has the redirects')
    assert.lengthOf(log.redirects!, 2, 'has both redirects')
  })

  test('has the redirects data', async ({ assert, httpConfig }) => {
    const request = new CoreEngine(baseRequest, opts)

    const log = await request.send()
    const redirects = log.redirects!.map((i) => new ResponseRedirect(i))
    const rdr1 = redirects[0]!

    const location = `http://localhost:${httpConfig.httpPort}/v1/redirect/relative/1?test=true`
    assert.equal(rdr1.url, location, 'has the redirect URL')
    assert.typeOf(rdr1.startTime, 'number', 'has the startTime property')
    assert.typeOf(rdr1.endTime, 'number', 'has the endTime property')
    assert.typeOf(rdr1.timings, 'object', 'has the timings property')
    assert.typeOf(rdr1.response, 'object', 'has the response property')

    const rsp = rdr1.response!
    assert.equal(rsp.status, 302, 'has the status code')
    assert.equal(rsp.statusText, 'Found', 'has the status text')
    assert.typeOf(rsp.headers, 'string', 'has the headers')

    const body = (await rsp.readPayload('buffer')) as Buffer
    const parsed = JSON.parse(body.toString('utf8'))
    assert.equal(parsed.location, '../relative/1?test=true', 'has the payload')
  })

  test('has the final response', async ({ assert, httpConfig }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()

    const transport = log.request as ISentRequest
    const response = log.response as IResponse

    const location = `http://localhost:${httpConfig.httpPort}/v1/get?test=true`
    assert.equal(transport.url, location, 'transport request has the final URL')
    assert.equal(response.status, 200, 'has the status code')
  })
})

test.group('Relative redirects - root path', (group) => {
  let baseRequest: IHttpRequest
  let opts: HttpEngineOptions

  group.each.setup((ctx) => {
    opts = {
      logger,
    }
    baseRequest = {
      url: `http://localhost:${ctx.context.httpConfig.httpPort}/v1/redirect/relative-root/2?test=true`,
      method: 'GET',
    }
  })

  test('redirects to a relative URL', async ({ assert }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()
    assert.typeOf(log.redirects!, 'array', 'has the redirects')
    assert.lengthOf(log.redirects!, 2, 'has both redirects')
  })

  test('has the redirects data', async ({ assert, httpConfig }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()
    const redirects = log.redirects!.map((i) => new ResponseRedirect(i))
    const rdr1 = redirects[0]!

    const location = `http://localhost:${httpConfig.httpPort}/v1/redirect/relative/1?test=true`
    assert.equal(rdr1.url, location, 'has the redirect URL')
    assert.typeOf(rdr1.startTime, 'number', 'has the startTime property')
    assert.typeOf(rdr1.endTime, 'number', 'has the endTime property')
    assert.typeOf(rdr1.timings, 'object', 'has the timings property')
    assert.typeOf(rdr1.response, 'object', 'has the response property')

    const rsp = rdr1.response!
    assert.equal(rsp.status, 302, 'has the status code')
    assert.equal(rsp.statusText, 'Found', 'has the status text')
    assert.typeOf(rsp.headers, 'string', 'has the headers')

    const body = (await rsp.readPayload('buffer')) as Buffer
    const parsed = JSON.parse(body.toString('utf8'))
    assert.equal(parsed.location, '/v1/redirect/relative/1?test=true', 'has the payload')
  })

  test('has the final response', async ({ assert, httpConfig }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()

    const transport = log.request as ISentRequest
    const response = log.response as IResponse

    const location = `http://localhost:${httpConfig.httpPort}/v1/get?test=true`
    assert.equal(transport.url, location, 'transport request has the final URL')
    assert.equal(response.status, 200, 'has the status code')
  })
})

test.group('Cookies', (group) => {
  let baseRequest: IHttpRequest
  let opts: HttpEngineOptions

  group.each.setup((ctx) => {
    opts = {
      logger,
    }
    baseRequest = {
      url: `http://localhost:${ctx.context.httpConfig.httpPort}/v1/redirect/cookies/2`,
      method: 'GET',
    }
  })

  test('supports cookies between redirects', async ({ assert }) => {
    const request = new CoreEngine(baseRequest, opts)
    const log = await request.send()
    const list = log.redirects as IResponseRedirect[]
    const redirects = list.map((i) => new ResponseRedirect(i))
    const response = new Response(log.response as IResponse)
    const payload = (await response.readPayloadAsString()) as string
    const body = JSON.parse(payload)

    assert.equal(body.headers.cookie, 'redirect-1=true', 'the final response has a cookie set with the last redirect')

    const [, r2] = redirects
    const rPayload = (await (r2.response as HttpResponse).readPayloadAsString()) as string
    const rBody = JSON.parse(rPayload)
    assert.equal(rBody.headers.cookie, 'redirect-1=true', 'the redirect #2 has the cookie')
  })
})
