import { Eltro as t, assert} from 'eltro'
import fs from 'fs/promises'
import http from 'http'
import Util from '../core/util.mjs'
import { request } from '../core/client.mjs'

const util = new Util(import.meta.url)
const testTargetFile = util.getPathFromRoot('./filetest.html')
const port = 61412
const defaultHandler = function(req, res) {
  res.statusCode = 200
  res.end('{"a":1}');
}
let server = null
let prefix = `http://localhost:${port}/`
let handler = defaultHandler

t.before(function(cb) {
  server = http.createServer(function(req, res) {
    req.on('error', function(err) {
      console.log('error', err)
    })
    res.on('error', function(err) {
      console.log('error', err)
    })
    handler(req, res)
  })
  server.listen(port, cb)
})

t.after(function() {
  return fs.rm(testTargetFile)
    .catch(function() { })
})

t.describe('Basics', function() {
  t.beforeEach(function() {
    handler = defaultHandler
  })

  t.test('should require valid config', async function() {
    function checkError(err) {
      assert.match(err.message, /config/i)
    }

    await assert.isRejected(request(prefix)).then(checkError)
    await assert.isRejected(request('', prefix)).then(checkError)
    await assert.isRejected(request([], prefix)).then(checkError)
    await assert.isRejected(request(123, prefix)).then(checkError)
    await assert.isRejected(request(0, prefix)).then(checkError)
  })

  t.test('should fail if url is invalid', async function() {
    function checkError(check, err) {
      assert.match(err.message, /invalid/i)
      assert.match(err.message, /url/i)
      if (check) {
        assert.match(err.message, new RegExp(check))
      }
    }
    
    await assert.isRejected(request({}, 123)).then(checkError.bind(this, null))
    await assert.isRejected(request({}, [])).then(checkError.bind(this, null))
    await assert.isRejected(request({}, {})).then(checkError.bind(this, null))
    await assert.isRejected(request({}, '')).then(checkError.bind(this, null))
    await assert.isRejected(request({}, 'asdf')).then(checkError.bind(this, 'asdf'))
    await assert.isRejected(request({}, 'httpppp')).then(checkError.bind(this, 'httpppp'))
  })
})

t.describe('Request', function() {
  t.test('should work normally', async function() {
    let res = await request({}, prefix)
    assert.deepEqual(res.body, {a:1})
  })

  t.test('should follow redirects', async function() {
    let counter = 0
    handler = function(req, res) {
      if (counter < 3) {
        res.statusCode = 302
        res.setHeader('Location', encodeURI(prefix))
        res.end();
        counter++
        return
      }
      assert.strictEqual(req.url, '/')
      res.statusCode = 200
      res.end('{"a":1}');
      return
    }
    let res = await request({}, prefix)
    assert.deepEqual(res.body, {a:1})
    assert.strictEqual(counter, 3)
  })

  t.test('should fail gracefully if HTML is received', async function() {
    handler = function(req, res) {
      res.statusCode = 200
      res.end(`<!DOCTYPE html>
      <html lang="en">
      <head>
      </head>
      <body>
      </body>
      </html>`);
    }
    let err = await assert.isRejected(request({}, prefix))
    assert.match(err.message, /html/i)
    assert.notMatch(err.message, /Unexpected token/i)

    handler = function(req, res) {
      res.statusCode = 200
      res.end(`<html>
      <head>
      </head>
      <body>
      </body>
      </html>`);
    }
    err = await assert.isRejected(request({}, prefix))
    assert.match(err.message, /html/i)
    assert.notMatch(err.message, /Unexpected token/i)
  })

  t.test('should fail if infinite redirect', async function() {
    const assertRelativeLocation = 'some/text/here'
    const assertLocation = prefix + assertRelativeLocation
    let counter = 0
    handler = function(req, res) {
      res.statusCode = 302
      res.setHeader('Location', encodeURI(assertLocation))
      if (counter === 0) {
        assert.strictEqual(req.url, '/')
      } else {
        assert.strictEqual(req.url, '/' + assertRelativeLocation)
      }
      res.end();
      counter++
    }

    let err = await assert.isRejected(request({}, prefix))
    assert.strictEqual(counter, 6)
    assert.match(err.message, /redirect/i)
    assert.match(err.message, new RegExp(assertRelativeLocation))
  })

  t.test('should fail if redirect is missing location', async function() {
    let counter = 0
    handler = function(req, res) {
      res.statusCode = 302
      res.end();
      counter++
    }

    let err = await assert.isRejected(request({}, prefix))
    assert.strictEqual(counter, 1)
    assert.match(err.message, /redirect/i)
    assert.match(err.message, /location/i)
  })

  t.test('should follow relative redirects', async function() {
    const assertUrl = 'asdf1234'
    let counter = 0
    let url = ''
    handler = function(req, res) {
      if (counter < 1) {
        res.statusCode = 302
        res.setHeader('Location', encodeURI(assertUrl))
        res.end();
        counter++
        return
      }
      url = req.url
      res.statusCode = 200
      res.end('{"a":1}');
      return
    }
    let res = await request({}, prefix)
    assert.deepEqual(res.body, {a:1})
    assert.strictEqual(counter, 1)
    assert.strictEqual(url, '/' + assertUrl)

    counter = 0
    res = await request({}, prefix + 'some/url/here')
    assert.deepEqual(res.body, {a:1})
    assert.strictEqual(counter, 1)
    assert.strictEqual(url, '/some/url/' + assertUrl)
  })

  t.timeout(30).test('should support timeout on invalid url', async function() {
    // blocked off port, should time out
    let err = await assert.isRejected(request({ timeout: 15 }, 'http://git.nfp.is:8080'))
    assert.match(err.message, /timed out/i)
    assert.match(err.message, /15/i)
  })
})

t.describe('Request download', function() {
  const assertBody = `<!DOCTYPE html>
  <html lang="en">
  <head>
  </head>
  <body>
  </body>
  </html>`

  t.beforeEach(function() {
    handler = defaultHandler
  })

  t.afterEach(function() {
    return fs.rm(testTargetFile)
      .catch(function() { })
  })

  t.test('should support downloading file successfully', async function() {
    handler = function(req, res) {
      res.statusCode = 200
      res.end(assertBody);
    }
    await request({}, prefix, testTargetFile)
    
    let stat = await fs.stat(testTargetFile)
    assert.notStrictEqual(stat.size, 0)
    assert.strictEqual(stat.size, assertBody.length)
    let contents = await (await fs.readFile(testTargetFile)).toString()
    assert.strictEqual(contents, assertBody)
  })

  t.test('should support downloading file after redirect successfully', async function() {
    let counter = 0
    handler = function(req, res) {
      if (counter < 3) {
        res.statusCode = 302
        res.setHeader('Location', encodeURI(prefix))
        res.end();
        counter++
        return
      }
      res.statusCode = 200
      res.end(assertBody);
    }
    await request({}, prefix, testTargetFile)
    
    assert.strictEqual(counter, 3)
    let stat = await fs.stat(testTargetFile)
    assert.notStrictEqual(stat.size, 0)
    assert.strictEqual(stat.size, assertBody.length)
    let contents = await (await fs.readFile(testTargetFile)).toString()
    assert.strictEqual(contents, assertBody)
  })

  t.test('should not create file if download redirects too many times', async function() {
    handler = function(req, res) {
      res.statusCode = 302
      res.setHeader('Location', encodeURI(prefix))
      res.end(assertBody);
    }
    let err = await assert.isRejected(request({}, prefix, testTargetFile))
    
    assert.match(err.message, /redirects/)
    let errStat = await assert.isRejected(fs.stat(testTargetFile))
    assert.strictEqual(errStat.code, 'ENOENT')
  })
  
  t.test('should not create file if download fails', async function() {
    handler = function(req, res) {
      res.statusCode = 404
      res.end('{}');
    }
    let err = await assert.isRejected(request({}, prefix, testTargetFile))
    
    assert.match(err.message, /HTTP/)
    assert.match(err.message, /404/)

    let errStat = await assert.isRejected(fs.stat(testTargetFile))
    assert.strictEqual(errStat.code, 'ENOENT')
  })
})

t.after(function(cb) {
  server.close(cb)
})
