'use strict'
/* eslint node/no-deprecated-api: "off" */
const fs = require('fs')
const path = require('path')
const t = require('tap')
const simple = require('simple-get')
const Fastify = require('fastify')
const fastifyStatic = require('..')
const helper = {
  arrange: function (t, options, f) {
    return helper.arrangeModule(t, options, fastifyStatic, f)
  },
  arrangeModule: function (t, options, mock, f) {
    const fastify = Fastify()
    fastify.register(mock, options)
    t.teardown(fastify.close.bind(fastify))
    fastify.listen(0, err => {
      t.error(err)
      fastify.server.unref()
      f('http://localhost:' + fastify.server.address().port)
    })
    return f
  }
}
try {
  fs.mkdirSync(path.join(__dirname, 'static/shallow/empty'))
} catch (error) {}
t.test('dir list wrong options', t => {
  t.plan(3)
  const cases = [
    {
      options: {
        root: path.join(__dirname, '/static'),
        prefix: '/public',
        list: {
          format: 'no-json,no-html'
        }
      },
      error: new TypeError('The `list.format` option must be json or html')
    },
    {
      options: {
        root: path.join(__dirname, '/static'),
        list: {
          format: 'html'
          // no render function
        }
      },
      error: new TypeError('The `list.render` option must be a function and is required with html format')
    },
    {
      options: {
        root: path.join(__dirname, '/static'),
        list: {
          names: 'not-an-array'
        }
      },
      error: new TypeError('The `list.names` option must be an array')
    }
  ]
  for (const case_ of cases) {
    const fastify = Fastify()
    fastify.register(fastifyStatic, case_.options)
    fastify.listen(0, err => {
      t.equal(err.message, case_.error.message)
      fastify.server.unref()
    })
  }
})
t.test('dir list default options', t => {
  t.plan(2)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    list: true
  }
  const route = '/public/shallow'
  const content = { dirs: ['empty'], files: ['sample.jpg'] }
  helper.arrange(t, options, (url) => {
    t.test(route, t => {
      t.plan(3)
      simple.concat({
        method: 'GET',
        url: url + route
      }, (err, response, body) => {
        t.error(err)
        t.equal(response.statusCode, 200)
        t.equal(body.toString(), JSON.stringify(content))
      })
    })
  })
})
t.test('dir list, custom options', t => {
  t.plan(2)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    index: false,
    list: true
  }
  const route = '/public/'
  const content = { dirs: ['deep', 'shallow'], files: ['.example', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
  helper.arrange(t, options, (url) => {
    t.test(route, t => {
      t.plan(3)
      simple.concat({
        method: 'GET',
        url: url + route
      }, (err, response, body) => {
        t.error(err)
        t.equal(response.statusCode, 200)
        t.equal(body.toString(), JSON.stringify(content))
      })
    })
  })
})
t.test('dir list html format', t => {
  t.plan(6)
  // render html in 2 ways: one with handlebars and one with template string
  const Handlebars = require('handlebars')
  const source = `
`
  const handlebarTemplate = Handlebars.compile(source)
  const templates = [
    {
      render: (dirs, files) => {
        return handlebarTemplate({ dirs, files })
      },
      output: `
`
    },
    {
      render: (dirs, files) => {
        return `
`
      },
      output: `
`
    }
  ]
  for (const template of templates) {
    const options = {
      root: path.join(__dirname, '/static'),
      prefix: '/public',
      index: false,
      list: {
        format: 'html',
        names: ['index', 'index.htm'],
        render: template.render
      }
    }
    const routes = ['/public/index.htm', '/public/index']
    // check all routes by names
    helper.arrange(t, options, (url) => {
      for (const route of routes) {
        t.test(route, t => {
          t.plan(3)
          simple.concat({
            method: 'GET',
            url: url + route
          }, (err, response, body) => {
            t.error(err)
            t.equal(response.statusCode, 200)
            t.equal(body.toString(), template.output)
          })
        })
      }
    })
  }
})
t.test('dir list href nested structure', t => {
  t.plan(6)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    index: false,
    list: {
      format: 'html',
      names: ['index', 'index.htm'],
      render (dirs, files) {
        return dirs[0].href
      }
    }
  }
  const routes = [
    { path: '/public/', response: '/public/deep' },
    { path: '/public/index', response: '/public/deep' },
    { path: '/public/deep/', response: '/public/deep/path' },
    { path: '/public/deep/index.htm', response: '/public/deep/path' },
    { path: '/public/deep/path/', response: '/public/deep/path/for' }
  ]
  helper.arrange(t, options, (url) => {
    for (const route of routes) {
      t.test(route.path, t => {
        t.plan(5)
        simple.concat({
          method: 'GET',
          url: url + route.path
        }, (err, response, body) => {
          t.error(err)
          t.equal(response.statusCode, 200)
          t.equal(body.toString(), route.response)
          simple.concat({
            method: 'GET',
            url: url + body.toString()
          }, (err, response, body) => {
            t.error(err)
            t.equal(response.statusCode, 200)
          })
        })
      })
    }
  })
})
t.test('dir list html format - stats', t => {
  t.plan(7)
  const options1 = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    index: false,
    list: {
      format: 'html',
      render (dirs, files) {
        t.ok(dirs.length > 0)
        t.ok(files.length > 0)
        t.ok(dirs.every(every))
        t.ok(files.every(every))
        function every (value) {
          return value.stats &&
            value.stats.atime &&
            !value.extendedInfo
        }
      }
    }
  }
  const route = '/public/'
  helper.arrange(t, options1, (url) => {
    simple.concat({
      method: 'GET',
      url: url + route
    }, (err, response, body) => {
      t.error(err)
      t.equal(response.statusCode, 200)
    })
  })
})
t.test('dir list html format - extended info', t => {
  t.plan(4)
  const route = '/public/'
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    index: false,
    list: {
      format: 'html',
      extendedFolderInfo: true,
      render (dirs, files) {
        t.test('dirs', t => {
          t.plan(dirs.length * 7)
          for (const value of dirs) {
            t.ok(value.extendedInfo)
            t.equal(typeof value.extendedInfo.fileCount, 'number')
            t.equal(typeof value.extendedInfo.totalFileCount, 'number')
            t.equal(typeof value.extendedInfo.folderCount, 'number')
            t.equal(typeof value.extendedInfo.totalFolderCount, 'number')
            t.equal(typeof value.extendedInfo.totalSize, 'number')
            t.equal(typeof value.extendedInfo.lastModified, 'number')
          }
        })
      }
    }
  }
  helper.arrange(t, options, (url) => {
    simple.concat({
      method: 'GET',
      url: url + route
    }, (err, response, body) => {
      t.error(err)
      t.equal(response.statusCode, 200)
    })
  })
})
t.test('dir list json format', t => {
  t.plan(2)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    prefixAvoidTrailingSlash: true,
    list: {
      format: 'json',
      names: ['index', 'index.json', '/']
    }
  }
  // const routes = ['/public/shallow', 'public/shallow/', '/public/shallow/index.json', '/public/shallow/index']
  const routes = ['/public/shallow/']
  const content = { dirs: ['empty'], files: ['sample.jpg'] }
  helper.arrange(t, options, (url) => {
    for (const route of routes) {
      t.test(route, t => {
        t.plan(3)
        simple.concat({
          method: 'GET',
          url: url + route
        }, (err, response, body) => {
          t.error(err)
          t.equal(response.statusCode, 200)
          t.equal(body.toString(), JSON.stringify(content))
        })
      })
    }
  })
})
t.test('dir list json format - extended info', t => {
  t.plan(2)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    prefixAvoidTrailingSlash: true,
    list: {
      format: 'json',
      names: ['index', 'index.json', '/'],
      extendedFolderInfo: true,
      jsonFormat: 'extended'
    }
  }
  const routes = ['/public/shallow/']
  helper.arrange(t, options, (url) => {
    for (const route of routes) {
      t.test(route, t => {
        t.plan(5)
        simple.concat({
          method: 'GET',
          url: url + route
        }, (err, response, body) => {
          t.error(err)
          t.equal(response.statusCode, 200)
          const bodyObject = JSON.parse(body.toString())
          t.equal(bodyObject.dirs[0].name, 'empty')
          t.equal(typeof bodyObject.dirs[0].stats.atime, 'string')
          t.equal(typeof bodyObject.dirs[0].extendedInfo.totalSize, 'number')
        })
      })
    }
  })
})
t.test('dir list - url parameter format', t => {
  t.plan(13)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    index: false,
    list: {
      format: 'html',
      render (dirs, files) {
        return 'html'
      }
    }
  }
  const route = '/public/'
  helper.arrange(t, options, (url) => {
    simple.concat({
      method: 'GET',
      url: url + route
    }, (err, response, body) => {
      t.error(err)
      t.equal(response.statusCode, 200)
      t.equal(body.toString(), 'html')
      t.ok(response.headers['content-type'].includes('text/html'))
    })
    simple.concat({
      method: 'GET',
      url: url + route + '?format=html'
    }, (err, response, body) => {
      t.error(err)
      t.equal(response.statusCode, 200)
      t.equal(body.toString(), 'html')
      t.ok(response.headers['content-type'].includes('text/html'))
    })
    simple.concat({
      method: 'GET',
      url: url + route + '?format=json'
    }, (err, response, body) => {
      t.error(err)
      t.equal(response.statusCode, 200)
      t.ok(body.toString())
      t.ok(response.headers['content-type'].includes('application/json'))
    })
  })
})
t.test('dir list on empty dir', t => {
  t.plan(2)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    list: true
  }
  const route = '/public/shallow/empty'
  const content = { dirs: [], files: [] }
  helper.arrange(t, options, (url) => {
    t.test(route, t => {
      t.plan(3)
      simple.concat({
        method: 'GET',
        url: url + route
      }, (err, response, body) => {
        t.error(err)
        t.equal(response.statusCode, 200)
        t.equal(body.toString(), JSON.stringify(content))
      })
    })
  })
})
t.test('dir list serve index.html on index option', t => {
  t.plan(2)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    index: false,
    list: {
      format: 'html',
      names: ['index', 'index.html'],
      render: () => 'dir list index'
    }
  }
  helper.arrange(t, options, (url) => {
    t.test('serve index.html from fs', t => {
      t.plan(6)
      let route = '/public/index.html'
      simple.concat({
        method: 'GET',
        url: url + route
      }, (err, response, body) => {
        t.error(err)
        t.equal(response.statusCode, 200)
        t.equal(body.toString(), '\n  \n    the body\n  \n\n')
      })
      route = '/public/index'
      simple.concat({
        method: 'GET',
        url: url + route
      }, (err, response, body) => {
        t.error(err)
        t.equal(response.statusCode, 200)
        t.equal(body.toString(), 'dir list index')
      })
    })
  })
})
t.test('serve a non existent dir and get error', t => {
  t.plan(2)
  const options = {
    root: '/none',
    prefix: '/public',
    list: true
  }
  const route = '/public/'
  helper.arrange(t, options, (url) => {
    t.test(route, t => {
      t.plan(2)
      simple.concat({
        method: 'GET',
        url: url + route
      }, (err, response, body) => {
        t.error(err)
        t.equal(response.statusCode, 404)
      })
    })
  })
})
t.test('serve a non existent dir and get error', t => {
  t.plan(2)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    list: {
      names: ['index']
    }
  }
  const route = '/public/none/index'
  helper.arrange(t, options, (url) => {
    t.test(route, t => {
      t.plan(2)
      simple.concat({
        method: 'GET',
        url: url + route
      }, (err, response, body) => {
        t.error(err)
        t.equal(response.statusCode, 404)
      })
    })
  })
})
t.test('dir list error', t => {
  t.plan(7)
  const options = {
    root: path.join(__dirname, '/static'),
    prefix: '/public',
    prefixAvoidTrailingSlash: true,
    index: false,
    list: {
      format: 'html',
      names: ['index', 'index.htm'],
      render: () => ''
    }
  }
  const errorMessage = 'mocking send'
  const dirList = require('../lib/dirList')
  dirList.send = async () => { throw new Error(errorMessage) }
  const mock = t.mock('..', {
    '../lib/dirList.js': dirList
  })
  const routes = ['/public/', '/public/index.htm']
  helper.arrangeModule(t, options, mock, (url) => {
    for (const route of routes) {
      simple.concat({
        method: 'GET',
        url: url + route
      }, (err, response, body) => {
        t.error(err)
        t.equal(JSON.parse(body.toString()).message, errorMessage)
        t.equal(response.statusCode, 500)
      })
    }
  })
})