// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

'use strict'

const assert = require('node:assert')
const http = require('node:http')
const url = require('node:url')

const HttpClient = require('selenium-webdriver/http').HttpClient
const HttpRequest = require('selenium-webdriver/lib/http').Request
const Server = require('../../lib/test/httpserver').Server

describe('HttpClient', function () {
  const server = new Server(function (req, res) {
    // eslint-disable-next-line n/no-deprecated-api
    const parsedUrl = url.parse(req.url)

    if (req.method === 'GET' && req.url === '/echo') {
      res.writeHead(200)
      res.end(JSON.stringify(req.headers))
    } else if (req.method === 'GET' && req.url === '/redirect') {
      res.writeHead(303, { Location: server.url('/hello') })
      res.end()
    } else if (req.method === 'GET' && req.url === '/hello') {
      res.writeHead(200, { 'content-type': 'text/plain' })
      res.end('hello, world!')
    } else if (req.method === 'GET' && req.url === '/chunked') {
      res.writeHead(200, {
        'content-type': 'text/html; charset=utf-8',
        'transfer-encoding': 'chunked',
      })
      res.write('<!DOCTYPE html>')
      setTimeout(() => res.end('<h1>Hello, world!</h1>'), 20)
    } else if (req.method === 'GET' && req.url === '/badredirect') {
      res.writeHead(303, {})
      res.end()
    } else if (req.method === 'GET' && req.url === '/protected') {
      const denyAccess = function () {
        res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="test"' })
        res.end('Access denied')
      }

      // eslint-disable-next-line no-useless-escape
      const basicAuthRegExp = /^\s*basic\s+([a-z0-9\-\._~\+\/]+)=*\s*$/i
      const auth = req.headers.authorization
      const match = basicAuthRegExp.exec(auth || '')
      if (!match) {
        denyAccess()
        return
      }

      const userNameAndPass = Buffer.from(match[1], 'base64').toString()
      const parts = userNameAndPass.split(':', 2)
      if (parts[0] !== 'genie' && parts[1] !== 'bottle') {
        denyAccess()
        return
      }

      res.writeHead(200, { 'content-type': 'text/plain' })
      res.end('Access granted!')
    } else if (req.method === 'GET' && parsedUrl.pathname && parsedUrl.pathname.endsWith('/proxy')) {
      let headers = Object.assign({}, req.headers)
      headers['x-proxy-request-uri'] = req.url
      res.writeHead(200, headers)
      res.end()
    } else if (req.method === 'GET' && parsedUrl.pathname && parsedUrl.pathname.endsWith('/proxy/redirect')) {
      let path = `/proxy${parsedUrl.search || ''}${parsedUrl.hash || ''}`
      res.writeHead(303, { Location: path })
      res.end()
    } else {
      res.writeHead(404, {})
      res.end()
    }
  })

  before(function () {
    return server.start()
  })

  after(function () {
    return server.stop()
  })

  it('can send a basic HTTP request', function () {
    const request = new HttpRequest('GET', '/echo')
    request.headers.set('Foo', 'Bar')

    const agent = new http.Agent()
    agent.maxSockets = 1 // Only making 1 request.

    const client = new HttpClient(server.url(), agent)
    return client.send(request).then(function (response) {
      assert.strictEqual(200, response.status)

      const headers = JSON.parse(response.body)
      assert.strictEqual(headers['content-length'], '0')
      assert.strictEqual(headers['connection'], 'keep-alive')
      assert.strictEqual(headers['host'], server.host())

      const regex = /^selenium\/.* \(js (windows|mac|linux)\)$/
      assert.ok(regex.test(headers['user-agent']), `${headers['user-agent']} does not match ${regex}`)

      assert.strictEqual(request.headers.get('Foo'), 'Bar')
      assert.strictEqual(request.headers.get('Accept'), 'application/json; charset=utf-8')
      assert.strictEqual(agent.keepAlive, false)
    })
  })

  it('can send a basic HTTP request with custom user-agent via client_options', function () {
    const request = new HttpRequest('GET', '/echo')
    request.headers.set('Foo', 'Bar')

    const agent = new http.Agent()
    agent.maxSockets = 1 // Only making 1 request.

    const client = new HttpClient(server.url(), agent, null, {
      'user-agent': 'test',
    })

    return client.send(request).then(function (response) {
      assert.strictEqual(200, response.status)

      const headers = JSON.parse(response.body)
      assert.strictEqual(headers['content-length'], '0')
      assert.strictEqual(headers['connection'], 'keep-alive')
      assert.strictEqual(headers['host'], server.host())
      assert.strictEqual(headers['user-agent'], 'test')

      assert.strictEqual(request.headers.get('Foo'), 'Bar')
      assert.strictEqual(agent.keepAlive, false)
      assert.strictEqual(request.headers.get('Accept'), 'application/json; charset=utf-8')
    })
  })

  it('can send a basic HTTP request with keep-alive being set to true via client_options', function () {
    const request = new HttpRequest('GET', '/echo')
    request.headers.set('Foo', 'Bar')

    const agent = new http.Agent()
    agent.maxSockets = 1 // Only making 1 request.

    const client = new HttpClient(server.url(), agent, null, {
      'keep-alive': 'true',
    })

    return client.send(request).then(function (response) {
      assert.strictEqual(200, response.status)

      const headers = JSON.parse(response.body)
      assert.strictEqual(headers['content-length'], '0')
      assert.strictEqual(headers['connection'], 'keep-alive')
      assert.strictEqual(headers['host'], server.host())

      const regex = /^selenium\/.* \(js (windows|mac|linux)\)$/
      assert.ok(regex.test(headers['user-agent']), `${headers['user-agent']} does not match ${regex}`)

      assert.strictEqual(request.headers.get('Foo'), 'Bar')
      assert.strictEqual(agent.keepAlive, true)
      assert.strictEqual(request.headers.get('Accept'), 'application/json; charset=utf-8')
    })
  })

  it('handles chunked responses', function () {
    let request = new HttpRequest('GET', '/chunked')

    let client = new HttpClient(server.url())
    return client.send(request).then((response) => {
      assert.strictEqual(200, response.status)
      assert.strictEqual(response.body, '<!DOCTYPE html><h1>Hello, world!</h1>')
    })
  })

  it('can use basic auth', function () {
    // eslint-disable-next-line n/no-deprecated-api
    const parsed = url.parse(server.url())
    parsed.auth = 'genie:bottle'

    const client = new HttpClient(url.format(parsed))
    const request = new HttpRequest('GET', '/protected')
    return client.send(request).then(function (response) {
      assert.strictEqual(200, response.status)
      assert.strictEqual(response.headers.get('content-type'), 'text/plain')
      assert.strictEqual(response.body, 'Access granted!')
    })
  })

  it('fails requests missing required basic auth', function () {
    const client = new HttpClient(server.url())
    const request = new HttpRequest('GET', '/protected')
    return client.send(request).then(function (response) {
      assert.strictEqual(401, response.status)
      assert.strictEqual(response.body, 'Access denied')
    })
  })

  it('automatically follows redirects', function () {
    const request = new HttpRequest('GET', '/redirect')
    const client = new HttpClient(server.url())
    return client.send(request).then(function (response) {
      assert.strictEqual(200, response.status)
      assert.strictEqual(response.headers.get('content-type'), 'text/plain')
      assert.strictEqual(response.body, 'hello, world!')
    })
  })

  it('handles malformed redirect responses', function () {
    const request = new HttpRequest('GET', '/badredirect')
    const client = new HttpClient(server.url())
    return client.send(request).then(assert.fail, function (err) {
      assert.ok(/Failed to parse "Location"/.test(err.message), 'Not the expected error: ' + err.message)
    })
  })

  describe('with proxy', function () {
    it('sends request to proxy with absolute URI', function () {
      const request = new HttpRequest('GET', '/proxy')
      const client = new HttpClient('http://another.server.com', undefined, server.url())
      return client.send(request).then(function (response) {
        assert.strictEqual(200, response.status)
        assert.strictEqual(response.headers.get('host'), 'another.server.com')
        assert.strictEqual(response.headers.get('x-proxy-request-uri'), 'http://another.server.com/proxy')
      })
    })

    it('uses proxy when following redirects', function () {
      const request = new HttpRequest('GET', '/proxy/redirect')
      const client = new HttpClient('http://another.server.com', undefined, server.url())
      return client.send(request).then(function (response) {
        assert.strictEqual(200, response.status)
        assert.strictEqual(response.headers.get('host'), 'another.server.com')
        assert.strictEqual(response.headers.get('x-proxy-request-uri'), 'http://another.server.com/proxy')
      })
    })

    it('includes search and hash in redirect URI', function () {
      const request = new HttpRequest('GET', '/proxy/redirect?foo#bar')
      const client = new HttpClient('http://another.server.com', undefined, server.url())
      return client.send(request).then(function (response) {
        assert.strictEqual(200, response.status)
        assert.strictEqual(response.headers.get('host'), 'another.server.com')
        assert.strictEqual(response.headers.get('x-proxy-request-uri'), 'http://another.server.com/proxy?foo#bar')
      })
    })
  })
})
