import { test } from '@japa/runner'
import { IHttpRequest, Headers, HttpResponse, ResponseRedirect } from '../../../../src/index.js'
import * as RequestUtils from '../../../../src/runtime/http-engine/RequestUtils.js'

const requests: IHttpRequest[] = [
  {
    url: `http://localhost:1234/api/endpoint?query=param`,
    method: 'POST',
    headers: 'content-type: text/plain',
    payload: {
      type: 'buffer',
      data: [...Buffer.from([0x74, 0x65, 0x73, 0x74, 0x0a, 0x74, 0x65, 0x73, 0x74])],
    },
  },
  {
    url: `http://localhost:1234/api/endpoint?query=param`,
    method: 'GET',
    headers: 'Host: test.com',
  },
]

test.group('getPort()', () => {
  test('returns a number when a number is passed', ({ assert }) => {
    const result = RequestUtils.getPort(20)
    assert.equal(result, 20)
  })

  test('returns a number when the argument is a string', ({ assert }) => {
    const result = RequestUtils.getPort('20')
    assert.equal(result, 20)
  })

  test('returns 443 port from the protocol', ({ assert }) => {
    const result = RequestUtils.getPort(0, 'https:')
    assert.equal(result, 443)
  })

  test('returns 80 port from the protocol', ({ assert }) => {
    // @ts-expect-error Used in testing
    const result = RequestUtils.getPort(undefined, 'http:')
    assert.equal(result, 80)
  })
})

test.group('getHostHeader()', () => {
  test('returns host with SSL port', ({ assert }) => {
    const result = RequestUtils.getHostHeader('https://domain.com/path')
    assert.equal(result, 'domain.com')
  })

  test('returns host with http port', ({ assert }) => {
    const result = RequestUtils.getHostHeader('http://domain.com/path')
    assert.equal(result, 'domain.com')
  })

  test('respects an existing port', ({ assert }) => {
    const result = RequestUtils.getHostHeader('https://domain.com:123/path')
    assert.equal(result, 'domain.com:123')
  })
})

test.group('_addContentLength()', (group) => {
  let headers: Headers
  group.each.setup(() => {
    headers = new Headers()
  })

  test('adds the content length header', ({ assert }) => {
    const b = Buffer.from([0x74, 0x65, 0x73, 0x74, 0x0a, 0x74, 0x65, 0x73, 0x74])
    RequestUtils.addContentLength(requests[0].method as string, b, headers)
    assert.equal(headers.get('content-length'), '9')
  })

  test('does nothing for GET requests', ({ assert }) => {
    // @ts-expect-error Used in testing
    RequestUtils.addContentLength(requests[1].method as string, undefined, headers)
    assert.isFalse(headers.has('content-length'))
  })
})

test.group('redirectOptions()', () => {
  ;[300, 304, 305].forEach((code) => {
    test(`does not set redirect flag for ${code}`, ({ assert }) => {
      const result = RequestUtils.redirectOptions(code, 'GET')
      assert.isFalse(result.redirect)
    })

    test(`does not set forceGet flag for ${code}`, ({ assert }) => {
      const result = RequestUtils.redirectOptions(code, 'GET')
      assert.isFalse(result.forceGet)
    })
  })
  ;[301, 302, 307].forEach((code) => {
    test(`Redirects ${code} for HEAD method`, ({ assert }) => {
      const result = RequestUtils.redirectOptions(code, 'HEAD')
      assert.isTrue(result.redirect)
    })

    test(`Redirects ${code} for GET method`, ({ assert }) => {
      const result = RequestUtils.redirectOptions(code, 'GET')
      assert.isTrue(result.redirect)
    })

    test(`Do not redirects ${code} for other method`, ({ assert }) => {
      const result = RequestUtils.redirectOptions(code, 'POST')
      assert.isFalse(result.redirect)
    })

    test(`Do not set forceGet for ${code} status`, ({ assert }) => {
      const result = RequestUtils.redirectOptions(code, 'GET')
      assert.isFalse(result.forceGet)
    })
  })

  test(`Adds redirect location`, ({ assert }) => {
    const result = RequestUtils.redirectOptions(301, 'GET', 'domain.com')
    assert.equal(result.location, 'domain.com')
  })

  test('Sets forceGet for 303 status', ({ assert }) => {
    const result = RequestUtils.redirectOptions(303, 'GET')
    assert.isTrue(result.forceGet)
  })
})

test.group('isRedirectLoop()', () => {
  const url = 'https://domain.com'
  test('returns false when no redirects', ({ assert }) => {
    // @ts-expect-error Used in testing
    const result = RequestUtils.isRedirectLoop(url, undefined)
    assert.isFalse(result)
  })

  test('returns false when redirects is empty', ({ assert }) => {
    const result = RequestUtils.isRedirectLoop(url, [])
    assert.isFalse(result)
  })

  test('returns false when url is not on the list', ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    const redirect = ResponseRedirect.fromValues('other.com', response)
    const result = RequestUtils.isRedirectLoop(url, [redirect])
    assert.isFalse(result)
  })

  test('returns true when url is on the list', ({ assert }) => {
    const response = HttpResponse.fromValues(200)
    const redirect = ResponseRedirect.fromValues(url, response)
    const result = RequestUtils.isRedirectLoop(url, [redirect])
    assert.isTrue(result)
  })
})

test.group('getRedirectLocation()', () => {
  test('returns the same valid url', ({ assert }) => {
    const url = 'https://domain.com/path?a=b'
    const result = RequestUtils.getRedirectLocation(url, '')
    assert.equal(result, url)
  })

  test('Resolves relative URL', ({ assert }) => {
    const url = '/path?a=b'
    const base = 'https://domain.com/other'
    const compare = 'https://domain.com/path?a=b'
    const result = RequestUtils.getRedirectLocation(url, base)
    assert.equal(result, compare)
  })

  test('Returns undefined for unknown state', ({ assert }) => {
    const url = '/path?a=b'
    const base = '/other'
    const result = RequestUtils.getRedirectLocation(url, base)
    assert.isUndefined(result)
  })
})
