const parser = require('./parsing')
const https = require('https')
const url = require('url')
const fs = require('fs')
const path = require('path')
const util = require('util')
const options = require('../options')
const _ = require('lodash/fp')
const logger = require('../logger')

const DOWNLOAD_DIR = path.resolve('/tmp')

function mkdir (path) {
  return new Promise((resolve, reject) => {
    fs.mkdir(path, err => {
      if (!err) return resolve()
      if (err.code === 'EEXIST') return resolve()
      reject(err)
    })
  })
}

const readFile = util.promisify(fs.readFile)
const writeFile = util.promisify(fs.writeFile)
const rename = util.promisify(fs.rename)
const unlink = util.promisify(fs.unlink)

const remove = file => {
  return unlink(file)
}

const promiseGetEtag = (source) => {
  return new Promise((resolve, reject) => {
    const {url: sourceUrl} = source
    const parsed = url.parse(sourceUrl)
    const requestOptions = {
      hostname: parsed.hostname,
      path: parsed.path,
      method: 'HEAD'
    }

    const request = https.request(requestOptions, _.flow(
      _.get(['headers', 'etag']),
      resolve
    ))

    request.on('error', reject)

    request.end()
  })
}

const download = _.curry((dstDir, source) => {
  const {name, url: sourceUrl} = source
  const dstFile = path.join(dstDir, name + '.xml')
  const file = fs.createWriteStream(dstFile)

  return new Promise((resolve, reject) => {
    const request = https.get(sourceUrl, response => {
      response.pipe(file)
      file.on('finish', () => file.close(() => resolve(dstFile)))
    })

    request.on('error', reject)
  })
})

const parseToJson = srcFile => {
  const dstFile = srcFile.replace(/\.xml$/, '.json')
  const writeStream = fs.createWriteStream(dstFile)

  return new Promise((resolve, reject) => {
    parser.parse(srcFile, (err, profile) => {
      if (err) {
        reject(err)
        return
      }

      if (!profile) {
        writeStream.end()
        return
      }

      const json = JSON.stringify(profile)
      writeStream.write(json + '\n', 'utf-8')
    })

    writeStream.on('error', reject)
    writeStream.on('finish', () => resolve(dstFile))
  })
}

const moveToSourcesDir = (srcFile, ofacSourcesDir) => {
  const name = path.basename(srcFile)
  const dstFile = path.join(ofacSourcesDir, name)
  return rename(srcFile, dstFile)
}

function update () {
  const OFAC_DATA_DIR = options.ofacDataDir

  if (!OFAC_DATA_DIR) {
    throw new Error('ofacDataDir must be defined in lamassu.json')
  }

  if (!options.ofacSources) {
    logger.error('ofacSources must be defined in lamassu.json')
  }

  const OFAC_SOURCES_DIR = path.join(OFAC_DATA_DIR, 'sources')
  const OFAC_ETAGS_FILE = path.join(OFAC_DATA_DIR, 'etags.json')

  return mkdir(OFAC_DATA_DIR)
    .then(() => mkdir(OFAC_SOURCES_DIR))
    .then(() => writeFile(OFAC_ETAGS_FILE, '{}', {encoding: 'utf-8', flag: 'wx'}))
    .catch(err => {
      if (err.code === 'EEXIST') return
      throw err
    })
    .then(() => {
      const promiseOldEtags = readFile(OFAC_ETAGS_FILE, {encoding: 'utf-8'})
      .then(json => JSON.parse(json))
      .catch(_ => {
        logger.error('Can\'t parse etags.json, getting new data...')
        return {}
      })

      const promiseNewEtags = Promise.resolve(options.ofacSources || [])
        .then(sources => Promise.all(_.map(promiseGetEtag, sources))
          .then(etags => _.map(
            ([source, etag]) => ({...source, etag}),
            _.zip(sources, etags)
          ))
        )

      return Promise.all([promiseOldEtags, promiseNewEtags])
        .then(([oldEtags, newEtags]) => {
          const hasNotChanged = ({name, etag}) => oldEtags[name] === etag

          const downloads = _.flow(
            _.reject(hasNotChanged),
            _.map(file => download(DOWNLOAD_DIR, file).then(parseToJson))
          )(newEtags)

          const oldFileNames = _.keys(oldEtags)
          const newFileNames = _.map(_.get('name'), newEtags)
          const missingFileNames = _.difference(oldFileNames, newFileNames)
          const resolve = name => path.join(OFAC_SOURCES_DIR, name + '.json')
          const missing = _.map(resolve, missingFileNames)

          const etagsJson = _.flow(
            _.map(source => [source.name, source.etag]),
            _.fromPairs,
            obj => JSON.stringify(obj, null, 4)
          )(newEtags)

          return Promise.all(downloads)
            .then(parsed => {
              const moves = _.map(src => moveToSourcesDir(src, OFAC_SOURCES_DIR), parsed)
              const deletions = _.map(remove, missing)
              const updateEtags = writeFile(OFAC_ETAGS_FILE, etagsJson)

              return Promise.all([updateEtags, ...moves, ...deletions])
            })
        })
    })
}

module.exports = {update}
