'use strict'

const { test } = require('tap')
const { Client } = require('..')
const timers = require('../lib/timers')
const { kConnect } = require('../lib/core/symbols')
const { createServer } = require('net')
const http = require('http')
const FakeTimers = require('@sinonjs/fake-timers')

test('keep-alive header', (t) => {
  t.plan(2)

  const server = createServer((socket) => {
    socket.write('HTTP/1.1 200 OK\r\n')
    socket.write('Content-Length: 0\r\n')
    socket.write('Keep-Alive: timeout=2s\r\n')
    socket.write('Connection: keep-alive\r\n')
    socket.write('\r\n\r\n')
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    t.teardown(client.destroy.bind(client))

    client.request({
      path: '/',
      method: 'GET'
    }, (err, { body }) => {
      t.error(err)
      body.on('end', () => {
        const timeout = setTimeout(() => {
          t.fail()
        }, 3e3)
        client.on('disconnect', () => {
          t.pass()
          clearTimeout(timeout)
        })
      }).resume()
    })
  })
})

test('keep-alive header 0', (t) => {
  t.plan(2)

  const clock = FakeTimers.install()
  t.teardown(clock.uninstall.bind(clock))

  const orgTimers = { ...timers }
  Object.assign(timers, { setTimeout, clearTimeout })
  t.teardown(() => {
    Object.assign(timers, orgTimers)
  })

  const server = createServer((socket) => {
    socket.write('HTTP/1.1 200 OK\r\n')
    socket.write('Content-Length: 0\r\n')
    socket.write('Keep-Alive: timeout=1s\r\n')
    socket.write('Connection: keep-alive\r\n')
    socket.write('\r\n\r\n')
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`, {
      keepAliveTimeoutThreshold: 500
    })
    t.teardown(client.destroy.bind(client))

    client.request({
      path: '/',
      method: 'GET'
    }, (err, { body }) => {
      t.error(err)
      body.on('end', () => {
        client.on('disconnect', () => {
          t.pass()
        })
        clock.tick(600)
      }).resume()
    })
  })
})

test('keep-alive header 1', (t) => {
  t.plan(2)

  const server = createServer((socket) => {
    socket.write('HTTP/1.1 200 OK\r\n')
    socket.write('Content-Length: 0\r\n')
    socket.write('Keep-Alive: timeout=1s\r\n')
    socket.write('Connection: keep-alive\r\n')
    socket.write('\r\n\r\n')
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    t.teardown(client.destroy.bind(client))

    client.request({
      path: '/',
      method: 'GET'
    }, (err, { body }) => {
      t.error(err)
      body.on('end', () => {
        const timeout = setTimeout(() => {
          t.fail()
        }, 0)
        client.on('disconnect', () => {
          t.pass()
          clearTimeout(timeout)
        })
      }).resume()
    })
  })
})

test('keep-alive header no postfix', (t) => {
  t.plan(2)

  const server = createServer((socket) => {
    socket.write('HTTP/1.1 200 OK\r\n')
    socket.write('Content-Length: 0\r\n')
    socket.write('Keep-Alive: timeout=2\r\n')
    socket.write('Connection: keep-alive\r\n')
    socket.write('\r\n\r\n')
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`)
    t.teardown(client.destroy.bind(client))

    client.request({
      path: '/',
      method: 'GET'
    }, (err, { body }) => {
      t.error(err)
      body.on('end', () => {
        const timeout = setTimeout(() => {
          t.fail()
        }, 3e3)
        client.on('disconnect', () => {
          t.pass()
          clearTimeout(timeout)
        })
      }).resume()
    })
  })
})

test('keep-alive not timeout', (t) => {
  t.plan(2)

  const server = createServer((socket) => {
    socket.write('HTTP/1.1 200 OK\r\n')
    socket.write('Content-Length: 0\r\n')
    socket.write('Keep-Alive: timeoutasdasd=1s\r\n')
    socket.write('Connection: keep-alive\r\n')
    socket.write('\r\n\r\n')
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`, {
      keepAliveTimeout: 1e3
    })
    t.teardown(client.destroy.bind(client))

    client.request({
      path: '/',
      method: 'GET'
    }, (err, { body }) => {
      t.error(err)
      body.on('end', () => {
        const timeout = setTimeout(() => {
          t.fail()
        }, 3e3)
        client.on('disconnect', () => {
          t.pass()
          clearTimeout(timeout)
        })
      }).resume()
    })
  })
})

test('keep-alive threshold', (t) => {
  t.plan(2)

  const server = createServer((socket) => {
    socket.write('HTTP/1.1 200 OK\r\n')
    socket.write('Content-Length: 0\r\n')
    socket.write('Keep-Alive: timeout=30s\r\n')
    socket.write('Connection: keep-alive\r\n')
    socket.write('\r\n\r\n')
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`, {
      keepAliveTimeout: 30e3,
      keepAliveTimeoutThreshold: 29e3
    })
    t.teardown(client.destroy.bind(client))

    client.request({
      path: '/',
      method: 'GET'
    }, (err, { body }) => {
      t.error(err)
      body.on('end', () => {
        const timeout = setTimeout(() => {
          t.fail()
        }, 3e3)
        client.on('disconnect', () => {
          t.pass()
          clearTimeout(timeout)
        })
      }).resume()
    })
  })
})

test('keep-alive max keepalive', (t) => {
  t.plan(2)

  const server = createServer((socket) => {
    socket.write('HTTP/1.1 200 OK\r\n')
    socket.write('Content-Length: 0\r\n')
    socket.write('Keep-Alive: timeout=30s\r\n')
    socket.write('Connection: keep-alive\r\n')
    socket.write('\r\n\r\n')
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`, {
      keepAliveTimeout: 30e3,
      keepAliveMaxTimeout: 1e3
    })
    t.teardown(client.destroy.bind(client))

    client.request({
      path: '/',
      method: 'GET'
    }, (err, { body }) => {
      t.error(err)
      body.on('end', () => {
        const timeout = setTimeout(() => {
          t.fail()
        }, 3e3)
        client.on('disconnect', () => {
          t.pass()
          clearTimeout(timeout)
        })
      }).resume()
    })
  })
})

test('connection close', (t) => {
  t.plan(4)

  let close = false
  const server = createServer((socket) => {
    if (close) {
      return
    }
    close = true
    socket.write('HTTP/1.1 200 OK\r\n')
    socket.write('Content-Length: 0\r\n')
    socket.write('Connection: close\r\n')
    socket.write('\r\n\r\n')
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`, {
      pipelining: 2
    })
    t.teardown(client.destroy.bind(client))

    client[kConnect](() => {
      client.request({
        path: '/',
        method: 'GET'
      }, (err, { body }) => {
        t.error(err)
        body.on('end', () => {
          const timeout = setTimeout(() => {
            t.fail()
          }, 3e3)
          client.once('disconnect', () => {
            close = false
            t.pass()
            clearTimeout(timeout)
          })
        }).resume()
      })

      client.request({
        path: '/',
        method: 'GET'
      }, (err, { body }) => {
        t.error(err)
        body.on('end', () => {
          const timeout = setTimeout(() => {
            t.fail()
          }, 3e3)
          client.once('disconnect', () => {
            t.pass()
            clearTimeout(timeout)
          })
        }).resume()
      })
    })
  })
})

test('Disable keep alive', (t) => {
  t.plan(7)

  const ports = []
  const server = http.createServer((req, res) => {
    t.notOk(ports.includes(req.socket.remotePort))
    ports.push(req.socket.remotePort)
    t.match(req.headers, { connection: 'close' })
    res.writeHead(200, { connection: 'close' })
    res.end()
  })
  t.teardown(server.close.bind(server))

  server.listen(0, () => {
    const client = new Client(`http://localhost:${server.address().port}`, { pipelining: 0 })
    t.teardown(client.destroy.bind(client))

    client.request({
      path: '/',
      method: 'GET'
    }, (err, { body }) => {
      t.error(err)
      body.on('end', () => {
        client.request({
          path: '/',
          method: 'GET'
        }, (err, { body }) => {
          t.error(err)
          body.on('end', () => {
            t.pass()
          }).resume()
        })
      }).resume()
    })
  })
})
