let TeamInvitesHandler
const logger = require('logger-sharelatex')
const crypto = require('crypto')
const async = require('async')

const settings = require('@overleaf/settings')
const { ObjectId } = require('mongodb')

const { Subscription } = require('../../models/Subscription')

const UserGetter = require('../User/UserGetter')
const SubscriptionLocator = require('./SubscriptionLocator')
const SubscriptionUpdater = require('./SubscriptionUpdater')
const LimitationsManager = require('./LimitationsManager')

const EmailHandler = require('../Email/EmailHandler')
const EmailHelper = require('../Helpers/EmailHelper')

const Errors = require('../Errors/Errors')

module.exports = TeamInvitesHandler = {
  getInvite(token, callback) {
    return Subscription.findOne(
      { 'teamInvites.token': token },
      function (err, subscription) {
        if (err) {
          return callback(err)
        }
        if (!subscription) {
          return callback(new Errors.NotFoundError('team not found'))
        }

        const invite = subscription.teamInvites.find(i => i.token === token)
        callback(null, invite, subscription)
      }
    )
  },

  createInvite(teamManagerId, subscription, email, callback) {
    email = EmailHelper.parseEmail(email)
    if (!email) {
      return callback(new Error('invalid email'))
    }
    return UserGetter.getUser(teamManagerId, function (error, teamManager) {
      if (error) {
        return callback(error)
      }

      removeLegacyInvite(subscription.id, email, function (error) {
        if (error) {
          return callback(error)
        }
        createInvite(subscription, email, teamManager, callback)
      })
    })
  },

  importInvite(subscription, inviterName, email, token, sentAt, callback) {
    checkIfInviteIsPossible(
      subscription,
      email,
      function (error, possible, reason) {
        if (error) {
          return callback(error)
        }
        if (!possible) {
          return callback(reason)
        }

        subscription.teamInvites.push({
          email,
          inviterName,
          token,
          sentAt,
        })

        subscription.save(callback)
      }
    )
  },

  acceptInvite(token, userId, callback) {
    TeamInvitesHandler.getInvite(token, function (err, invite, subscription) {
      if (err) {
        return callback(err)
      }
      if (!invite) {
        return callback(new Errors.NotFoundError('invite not found'))
      }

      SubscriptionUpdater.addUserToGroup(
        subscription._id,
        userId,
        function (err) {
          if (err) {
            return callback(err)
          }

          removeInviteFromTeam(subscription.id, invite.email, callback)
        }
      )
    })
  },

  revokeInvite(teamManagerId, subscription, email, callback) {
    email = EmailHelper.parseEmail(email)
    if (!email) {
      return callback(new Error('invalid email'))
    }
    removeInviteFromTeam(subscription.id, email, callback)
  },

  // Legacy method to allow a user to receive a confirmation email if their
  // email is in Subscription.invited_emails when they join. We'll remove this
  // after a short while.
  createTeamInvitesForLegacyInvitedEmail(email, callback) {
    SubscriptionLocator.getGroupsWithEmailInvite(email, function (err, teams) {
      if (err) {
        return callback(err)
      }

      async.map(
        teams,
        (team, cb) =>
          TeamInvitesHandler.createInvite(team.admin_id, team, email, cb),
        callback
      )
    })
  },
}

var createInvite = function (subscription, email, inviter, callback) {
  checkIfInviteIsPossible(
    subscription,
    email,
    function (error, possible, reason) {
      if (error) {
        return callback(error)
      }
      if (!possible) {
        return callback(reason)
      }

      // don't send invites when inviting self; add user directly to the group
      const isInvitingSelf = inviter.emails.some(
        emailData => emailData.email === email
      )
      if (isInvitingSelf) {
        return SubscriptionUpdater.addUserToGroup(
          subscription._id,
          inviter._id,
          err => {
            if (err) {
              return callback(err)
            }

            // legacy: remove any invite that might have been created in the past
            removeInviteFromTeam(subscription._id, email, error => {
              const inviteUserData = {
                email: inviter.email,
                first_name: inviter.first_name,
                last_name: inviter.last_name,
                invite: false,
              }
              callback(error, inviteUserData)
            })
          }
        )
      }

      const inviterName = getInviterName(inviter)
      let invite = subscription.teamInvites.find(
        invite => invite.email === email
      )

      if (invite) {
        invite.sentAt = new Date()
      } else {
        invite = {
          email,
          inviterName,
          token: crypto.randomBytes(32).toString('hex'),
          sentAt: new Date(),
        }
        subscription.teamInvites.push(invite)
      }

      subscription.save(function (error) {
        if (error) {
          return callback(error)
        }

        const opts = {
          to: email,
          inviter,
          acceptInviteUrl: `${settings.siteUrl}/subscription/invites/${invite.token}/`,
          appName: settings.appName,
        }
        EmailHandler.sendEmail('verifyEmailToJoinTeam', opts, error => {
          Object.assign(invite, { invite: true })
          callback(error, invite)
        })
      })
    }
  )
}

var removeInviteFromTeam = function (subscriptionId, email, callback) {
  const searchConditions = { _id: new ObjectId(subscriptionId.toString()) }
  const removeInvite = { $pull: { teamInvites: { email } } }

  async.series(
    [
      cb => Subscription.updateOne(searchConditions, removeInvite, cb),
      cb => removeLegacyInvite(subscriptionId, email, cb),
    ],
    callback
  )
}

var removeLegacyInvite = (subscriptionId, email, callback) =>
  Subscription.updateOne(
    {
      _id: new ObjectId(subscriptionId.toString()),
    },
    {
      $pull: {
        invited_emails: email,
      },
    },
    callback
  )

var checkIfInviteIsPossible = function (subscription, email, callback) {
  if (!subscription.groupPlan) {
    logger.log(
      { subscriptionId: subscription.id },
      'can not add members to a subscription that is not in a group plan'
    )
    return callback(null, false, { wrongPlan: true })
  }

  if (LimitationsManager.teamHasReachedMemberLimit(subscription)) {
    logger.log(
      { subscriptionId: subscription.id },
      'team has reached member limit'
    )
    return callback(null, false, { limitReached: true })
  }

  UserGetter.getUserByAnyEmail(email, function (error, existingUser) {
    if (error) {
      return callback(error)
    }
    if (!existingUser) {
      return callback(null, true)
    }

    const existingMember = subscription.member_ids.find(
      memberId => memberId.toString() === existingUser._id.toString()
    )

    if (existingMember) {
      logger.log(
        { subscriptionId: subscription.id, email },
        'user already in team'
      )
      callback(null, false, { alreadyInTeam: true })
    } else {
      callback(null, true)
    }
  })
}

var getInviterName = function (inviter) {
  let inviterName
  if (inviter.first_name && inviter.last_name) {
    inviterName = `${inviter.first_name} ${inviter.last_name} (${inviter.email})`
  } else {
    inviterName = inviter.email
  }

  return inviterName
}
