dbVersion = require('./model/dbVersion').dbVersion
Keystore = require('./model/keystore').Keystore
Client = require('./model/clients').Client
User = require('./model/users').User
Visualizer = require('./model/visualizer').Visualizer
logger = require 'winston'
pem = require 'pem'
Q = require 'q'

dedupName = (name, names, num) ->
  if num
    newName = "#{name} #{num}"
  else
    newName = name
  if newName in names
    if not num
      num = 1
    return dedupName(name, names, ++num)
  else
    return newName

# push new upgrade functions to this array, function must return a promise
# Warning: only add new function below existing functions, order matters!
upgradeFuncs = []

upgradeFuncs.push
  description: "Ensure that all certs have a fingerprint property"
  func: ->
    defer = Q.defer()

    Keystore.findOne (err, keystore) ->
      return defer.resolve() if not keystore

      # convert server cert
      pem.getFingerprint keystore.cert.data, (err, obj) ->
        keystore.cert.fingerprint = obj.fingerprint

        promises = []
        for cert, i in keystore.ca
          caDefer = Q.defer()
          promises.push caDefer.promise
          do (caDefer, i) ->
            pem.getFingerprint cert.data, (err, obj) ->
              keystore.ca[i].fingerprint = obj.fingerprint
              caDefer.resolve()

        Q.all(promises).then ->
          keystore.save (err) ->
            logger.error "Failed to save keystore: #{err}" if err?
            defer.resolve()

    return defer.promise

upgradeFuncs.push
  description: "Convert clients link to certs via their domain to use the cert fingerprint instead"
  func: ->
    defer = Q.defer()

    Client.find (err, clients) ->
      if err?
        logger.error "Couldn't fetch all clients to upgrade db: #{err}"
        return defer.reject()

      Keystore.findOne (err, keystore) ->
        if err?
          logger.error "Couldn't fetch keystore to upgrade db: #{err}"
          return defer.reject()

        promises = []
        for client in clients
          clientDefer = Q.defer()
          promises.push clientDefer.promise

          if keystore?.ca?
            for cert in keystore.ca
              if client.clientDomain is cert.commonName and not client.certFingerprint?
                client.certFingerprint = cert.fingerprint
                break

          do (clientDefer) ->
            client.save (err) ->
              if err?
                logger.error "Couldn't save client #{client.clientID} while upgrading db: #{err}"
                return clientDefer.reject()

              clientDefer.resolve()

        Q.all(promises).then ->
          defer.resolve()

    return defer.promise

# Adapt visualizer from an old version (core 2.0.0, console 1.6.0 and earlier)
#
# We follow the same migration strategy as console:
# https://github.com/jembi/openhim-console/blob/1047b49db2050bafa6b4797e3788fa716d1760b3/app/scripts/controllers/profile.js#L83-L109
adaptOldVisualizerStructure = (visualizer) ->
  visualizer.channels = []
  visualizer.mediators = []
  visualizer.time.minDisplayPeriod = 100

  if visualizer.endpoints
    for endpoint in visualizer.endpoints
      visualizer.channels.push
        eventType: 'channel'
        eventName: endpoint.event.replace 'channel-', ''
        display: endpoint.desc
    delete visualizer.endpoints

  if visualizer.components
    for component in visualizer.components
      split = component.event.split '-'
      if split.length > 1
        component.eventType = split[0]
        component.eventName = split[1]
      else
        component.eventType = 'channel'
        component.eventName = component.event
      component.display = component.desc
      delete component.event
      delete component.desc


upgradeFuncs.push
  description: "Migrate visualizer setting from a user's profile to a shared collection"
  func: ->
    defer = Q.defer()
    User.find (err, users) ->
      if err
        return Q.defer().reject(err)

      visNames = []
      promises = []
      users.forEach (user) ->
        if user.settings?.visualizer?
          vis = user.settings.visualizer
          if vis.components?.length > 0 or vis.mediators?.length > 0 or vis.channels?.length > 0 or vis.endpoints?.length > 0
            userDefer = Q.defer()
            promises.push userDefer.promise

            if vis.endpoints # old version
              adaptOldVisualizerStructure vis

            name = "#{user.firstname} #{user.surname}'s visualizer"
            name = dedupName name, visNames
            vis.name = name
            visNames.push name

            vis = new Visualizer vis
            logger.debug "Migrating visualizer from user profile #{user.email}, using visualizer name '#{name}'"
            vis.save (err, vis) ->
              if err
                logger.error "Error migrating visualizer from user profile #{user.email}: #{err.stack}"
                return userDefer.reject err

              # delete the visualizer settings from this user profile
              user.set 'settings.visualizer', null
              user.save (err, user) ->
                if err then return userDefer.reject err
                return userDefer.resolve()

      Q.all(promises).then ->
        defer.resolve()
      .catch (err) ->
        defer.reject err

    return defer.promise

# add new upgrade functions here ^^

runUpgradeFunc = (i, dbVer) ->
  logger.info "  \u2022 Running update: #{upgradeFuncs[i].description}..."
  defer = Q.defer()
  # run upgrade function
  upgradeFuncs[i].func().then ->
    # update the datbase version
    dbVer.version = i
    dbVer.lastUpdated = new Date()
    dbVer.save (err) ->
      logger.error err if err?
      logger.info "  \u2713 Done."
      defer.resolve()
  .catch (err) ->
    defer.reject err
  return defer.promise

if process.env.NODE_ENV == "test"
  exports.upgradeFuncs = upgradeFuncs
  exports.runUpgradeFunc = runUpgradeFunc
  exports.dedupName = dedupName

exports.upgradeDb = (callback) ->
  dbVersion.findOne (err, dbVer) ->
    if dbVer is null
      dbVer = new dbVersion
        version: -1
        lastUpdated: new Date()

    # check if the database version need to be upgraded
    if dbVer.version < (upgradeFuncs.length - 1)
      logger.info 'Upgrading the database...'
      promise = null
      # call each database upgrade function sequentially
      for i in [(dbVer.version + 1)..(upgradeFuncs.length - 1)]
        do (i) ->
          if not promise?
            promise = runUpgradeFunc(i, dbVer)
          else
            promise = promise.then -> runUpgradeFunc(i, dbVer)

      promise.then ->
        logger.info 'Completed database upgrade'
        callback()
      .catch (err) ->
        logger.error "There was an error upgrading your database, you will need to fix this manually to continue. #{err.stack}"
        process.exit()
    else
      logger.info 'No database upgrades needed'
      callback()

if not module.parent
  exports.upgradeDb(-> process.exit())
