const { EventEmitter } = require('events')
const typeforce = require('typeforce')
const inherits = require('inherits')
const { TYPE } = require('@tradle/constants')
const validateResource = require('@tradle/validate-resource')
const { omitVirtual, getRef, parseStub } = validateResource.utils
const buildResource = require('@tradle/build-resource')
// const ModelManger = require('./models')
const baseModels = require('./base-models')
const createStateMutater = require('./state')
const applicationMixin = require('./application-mixin')
const {
  APPLICATION,
  PRODUCT_REQUEST,
  MODELS_PACK
} = require('./types')

const {
  co,
  bindAll,
  getModelsPacks
} = require('./utils')

const notNull = val => !!val

exports = module.exports = opts => new Client(opts)

function Client () {
  EventEmitter.call(this)
  bindAll(this)
  applicationMixin(this)

  // this.models = new ModelManger({ namespace })
  this.state = createStateMutater({ models: baseModels })
}

inherits(Client, EventEmitter)
const proto = Client.prototype

proto.install = function (bot) {
  this.bot = bot
  this._promiseIdentityPermalink = this.bot.getMyIdentity()
    .then(identity => buildResource.permalink(identity))

  this.uninstall = this.bot.hook('messagestream:post', this.processMessages)
  this.emit('bot', bot)
  return this
}

proto.getModelsPacks = function ({ from, to }) {
  return getModelsPacks({ db: this.bot.db, from, to })
}

proto.processMessages = co(function* ({ messages }) {
  // TODO: handle inbound...though that couples it to the provider implementation
  const outbound = messages.filter(({ _inbound }) => !_inbound)
  if (!outbound.length) return

  // outbound.sort(byTime)
  // const minDate = outbound[0]._time
  // const maxDate = outbound[outbound.length - 1]
  // const modelsPacks = yield this.getModelsPacks({
  //   from: minDate,
  //   to: maxDate
  // })

  // ignore those without contexts
  const byContext = groupBy(outbound, 'context')
  const drafts = Object.keys(byContext).map(context => {
    const messages = byContext[context]
    const draft = {
      [TYPE]: APPLICATION,
      context,
      forms: []
    }

    for (const message of messages) {
      const payload = message.object
      if (message._payloadType === PRODUCT_REQUEST) {
        draft.requestFor = payload.requestFor
      } else {
        draft.forms.push(stubWithoutModel(payload))
      }
    }

    if (draft.requestFor || draft.forms.length) {
      return draft
    }
  }).filter(notNull)

  if (!drafts.length) return

  const contexts = drafts.map(({ context }) => context)
  const existingApplications = yield this.bot.db.find({
    EQ: {
      [TYPE]: APPLICATION,
      _author: yield this._promiseIdentityPermalink
    },
    IN: {
      context: contexts
    }
  })

  existingApplications.forEach(application => {
    const { context } = application
    const idx = drafts.findIndex(app => app.context == context)
    const draft = drafts[idx]
    drafts.splice(idx, 1)

    // recent first
    application.forms = draft.forms.concat(application.forms || [])
    application.forms = uniqBy(application.forms, getPermalinkFromStub)
    return application
  })

  const saveNewApplications = drafts
    .map(application => this.saveApplication({ application }))

  const saveUpdatedApplications = existingApplications
    .map(application => this.saveNewVersionOfApplication({ application }))

  yield Promise.all(saveNewApplications.concat(saveUpdatedApplications))
})

const groupBy = (arr, prop) => {
  const groups = {}
  for (const el of arr) {
    const group = el[prop]
    if (!group) continue

    if (!groups[group]) {
      groups[group] = []
    }

    groups[group].push(el)
  }

  return groups
}

const byTime = (a, b) => a._time - b._time

const flatten = arr => {
  return arr.reduce((all, some) => all.concat(some), [])
}

const stubWithoutModel = (resource) => {
  return {
    id: buildResource.id({
      resource
    }),
    title: resource[TYPE]
  }
}

const uniqBy = (arr, getUniqueId) => {
  const have = {}
  return arr.filter(item => {
    const id = getUniqueId(item)
    if (!have[id]) {
      have[id] = true
      return true
    }
  })
}

const getPermalinkFromStub = stub => parseStub(stub).permalink