//
// Copyright © 2022 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
//

module.exports.command = 'doctor'

module.exports.describe = 'Diagnose potential issues with your installation.'

module.exports.builder = function(yargs) {
  return yargs
}

module.exports.handler = function() {
  var cp = require('child_process')
  var os = require('os')
  var util = require('util')

  var semver = require('semver')
  var Promise = require('bluebird')

  var pkg = require('../../../package')
  var log = require('../../util/logger').createLogger('cli:doctor')

  function CheckError() {
    Error.captureStackTrace(this, this.constructor)
    this.name = 'CheckError'
    this.message = util.format.apply(util, arguments)
  }

  util.inherits(CheckError, Error)

  function call(command, args, options) {
    return new Promise(function(resolve, reject) {
      var proc = cp.spawn(command, args, options)
      var stdout = []

      proc.stdout.on('data', function(data) {
        stdout.push(data)
      })

      proc.on('error', reject)

      proc.on('close', function(code, signal) {
        if (signal) {
          reject(new CheckError('Exited with signal %s', signal))
        }
        else if (code === 0) {
          resolve(Buffer.concat(stdout).toString())
        }
        else {
          reject(new CheckError('Exited with status %s', code))
        }
      })
    })
  }

  function check(label, fn) {
    function Check() {
    }

    Check.prototype.call = function(command, args, options) {
      return call(command, args, options).catch(function(err) {
        if (err.code === 'ENOENT') {
          throw new CheckError(
            '%s is not installed (`%s` is missing)'
          , label
          , command
          )
        }

        throw err
      })
    }

    Check.prototype.extract = function(what, re) {
      return function(input) {
        return Promise.try(function() {
          var match = re.exec(input)
          if (!match) {
            throw new CheckError(util.format('%s %s cannot be detected', label, what))
          }
          return match[1]
        })
      }
    }

    Check.prototype.version = function(wantedVersion) {
      return function(currentVersion) {
        return Promise.try(function() {
          log.info('Using %s %s', label, currentVersion)
          var sanitizedVersion = currentVersion.replace(/~.*/, '')
          return semver.satisfies(sanitizedVersion, wantedVersion)
        })
        .then(function(satisfied) {
          if (!satisfied) {
            throw new CheckError(
              '%s is currently %s but needs to be %s'
            , label
            , currentVersion
            , wantedVersion
            )
          }
        })
      }
    }

    return Promise.try(function() {
        return fn(new Check())
      })
      .catch(CheckError, function(err) {
        log.error(err.message)
      })
      .catch(function(err) {
        log.error('Unexpected error checking %s: %s', label, err)
      })
  }

  function checkOSArch() {
    log.info('OS Arch: %s', os.arch())
  }

  function checkOSPlatform() {
    log.info('OS Platform: %s', os.platform())
    if (os.platform() === 'win32') {
      log.warn('STF has never been tested on Windows. Contributions are welcome!')
    }
  }

  function checkOSRelease() {
    log.info('OS Platform: %s', os.release())
  }

  function checkNodeVersion() {
    log.info('Using Node %s', process.versions.node)
  }

  function checkLocalRethinkDBVersion() {
    return check('RethinkDB', function(checker) {
      return checker.call('rethinkdb', ['--version'])
        .then(checker.extract('version', /rethinkdb ([^\s]+)/))
        .then(checker.version(pkg.externalDependencies.rethinkdb))
    })
  }

  function checkGraphicsMagick() {
    return check('GraphicsMagick', function(checker) {
      return checker.call('gm', ['-version'])
        .then(checker.extract('version', /GraphicsMagick ([^\s]+)/))
        .then(checker.version(pkg.externalDependencies.gm))
    })
  }

  function checkZeroMQ() {
    return check('ZeroMQ', function(checker) {
      var zmq = require('zeromq')
      return checker.version(pkg.externalDependencies.zeromq)(zmq.version)
    })
  }

  function checkProtoBuf() {
    return check('ProtoBuf', function(checker) {
      return checker.call('protoc', ['--version'])
        .then(checker.extract('version', /libprotoc ([^\s]+)/))
        .then(checker.version(pkg.externalDependencies.protobuf))
    })
  }

  function checkADB() {
    return check('ADB', function(checker) {
      return checker.call('adb', ['version'])
        .then(checker.extract('version', /Android Debug Bridge version ([^\s]+)/))
        .then(checker.version(pkg.externalDependencies.adb))
    })
  }

  return Promise.all([
    checkOSArch()
  , checkOSPlatform()
  , checkOSRelease()
  , checkNodeVersion()
  , checkLocalRethinkDBVersion()
  , checkGraphicsMagick()
  , checkZeroMQ()
  , checkProtoBuf()
  , checkADB()
  ])
}
