const { get, remove, findLast, pick, chain, omit } = require('lodash')

const config = require('config')
const User = require('./User')

const { recommendations: configRecommendations } = config

class Fragment {
  constructor({ fragment }) {
    this.fragment = fragment
  }

  set _fragment(newFragment) {
    this.fragment = newFragment
  }

  static setFragmentOwners(fragment = {}, author = {}) {
    const { owners = [] } = fragment
    if (author.isSubmitting) {
      const authorAlreadyOwner = owners.includes(author.id)
      if (!authorAlreadyOwner) {
        return [author.id, ...owners]
      }
    }
    return owners
  }

  async getFragmentData({ handlingEditor } = {}) {
    const { fragment: { metadata = {}, recommendations = [], id } } = this
    let heRecommendation

    if (handlingEditor) {
      heRecommendation = recommendations.find(
        rec => rec.userId === handlingEditor.id,
      )
    }

    let { title = '', abstract = '' } = metadata
    const { type } = metadata
    title = title.replace(/<(.|\n)*?>/g, '')
    abstract = abstract ? abstract.replace(/<(.|\n)*?>/g, '') : ''

    return {
      id,
      type,
      title,
      abstract,
      recommendations,
      heRecommendation,
    }
  }

  async addAuthor({ user, isSubmitting, isCorresponding }) {
    const { fragment } = this
    fragment.authors = fragment.authors || []
    const author = {
      id: user.id,
      firstName: user.firstName || '',
      lastName: user.lastName || '',
      email: user.email,
      title: user.title || '',
      affiliation: user.affiliation || '',
      country: user.country || '',
      isSubmitting,
      isCorresponding,
    }
    fragment.authors.push(author)
    fragment.owners = this.constructor.setFragmentOwners(fragment, author)
    await fragment.save()

    return author
  }

  async getAuthorData({ UserModel }) {
    const { fragment: { authors = [] } } = this
    const submittingAuthorData = authors.find(author => author.isSubmitting)

    try {
      const submittingAuthor = await UserModel.find(
        get(submittingAuthorData, 'id'),
      )

      const userHelper = new User({ UserModel })
      const activeAuthors = await userHelper.getActiveAuthors({
        fragmentAuthors: authors,
      })

      return {
        activeAuthors,
        submittingAuthor,
      }
    } catch (e) {
      throw e
    }
  }

  getInvitations({ isAccepted = true, role = 'reviewer', type }) {
    const { fragment: { invitations = [], recommendations = [] } } = this
    const filteredInvitations = isAccepted
      ? invitations.filter(
          inv => inv.role === role && inv.hasAnswer && inv.isAccepted,
        )
      : invitations.filter(inv => inv.role === role && !inv.hasAnswer)

    if (type === 'submitted') {
      return filteredInvitations.filter(inv =>
        recommendations.find(
          rec =>
            rec.recommendationType === 'review' &&
            rec.submittedOn &&
            inv.userId === rec.userId,
        ),
      )
    } else if (type === 'accepted') {
      recommendations.forEach(rec => {
        if (rec.recommendationType === 'review' && rec.submittedOn) {
          remove(filteredInvitations, inv => inv.userId === rec.userId)
        }
      })
    }

    return filteredInvitations
  }

  getLatestHERequestToRevision() {
    const { fragment: { recommendations = [] } } = this
    return findLast(
      recommendations,
      rec =>
        rec.recommendationType === 'editorRecommendation' &&
        (rec.recommendation === 'minor' || rec.recommendation === 'major'),
    )
  }

  async getReviewers({ UserModel, type }) {
    const isAccepted = type !== 'pending'
    const invitations = await this.getInvitations({ isAccepted, type })

    return (await Promise.all(
      invitations.map(inv => UserModel.find(inv.userId)),
    ))
      .filter(rev => rev.isActive)
      .filter(rev => get(rev, 'notifications.email.user'))
  }

  hasReviewReport() {
    const { fragment: { recommendations = [] } } = this
    return recommendations.some(
      rec => rec.recommendationType === 'review' && rec.submittedOn,
    )
  }

  canHEMakeAnotherRecommendation(lastHERecommendation) {
    const { fragment: { recommendations = [] } } = this

    const returnToHERecommendation = findLast(
      recommendations,
      r => r.recommendation === 'return-to-handling-editor',
    )

    if (!returnToHERecommendation) return false
    return returnToHERecommendation.createdOn > lastHERecommendation.createdOn
  }

  async getReviewersAndEditorsData({ collection, UserModel }) {
    const {
      invitations = [],
      recommendations = [],
      submitted = Date.now(),
    } = this.fragment

    const revAndEditorData = await Promise.all(
      recommendations
        .filter(
          rec =>
            rec.recommendationType === configRecommendations.type.editor ||
            (rec.recommendationType === configRecommendations.type.review &&
              rec.submittedOn),
        )
        .map(async rec => {
          const user = await UserModel.find(rec.userId)
          let assignmentDate, isReviewer

          if (rec.recommendationType === configRecommendations.type.editor) {
            if (!collection.handlingEditor) {
              throw new Error(
                `Collection ${collection.id} does not have a Handling Editor`,
              )
            }
            if (user.id === collection.handlingEditor.id) {
              const editorInvitation = collection.invitations.find(
                inv => inv.userId === user.id,
              )
              assignmentDate = editorInvitation.respondedOn
            } else {
              assignmentDate = submitted
            }

            isReviewer = false
          } else {
            const reviewerInvitation = invitations.find(
              inv => inv.userId === user.id,
            )
            assignmentDate = reviewerInvitation.respondedOn
            isReviewer = true
          }

          return {
            isReviewer,
            assignmentDate,
            email: user.email,
            title: user.title,
            recommendation: rec,
            country: user.country,
            lastName: user.lastName,
            firstName: user.firstName,
            affiliation: user.affiliation,
            submissionDate: rec.createdOn,
          }
        }),
    )

    return revAndEditorData
  }

  async addRecommendation(newRecommendation) {
    this.fragment.recommendations = this.fragment.recommendations || []

    this.fragment.recommendations.push(newRecommendation)
    await this.fragment.save()
  }

  async addRevision() {
    this.fragment.revision = pick(this.fragment, [
      'authors',
      'files',
      'metadata',
    ])
    await this.fragment.save()
  }

  hasReviewers() {
    const { fragment: invitations = [] } = this
    return invitations.length > 0
  }

  getLatestUserRecommendation(userId) {
    return findLast(this.fragment.recommendations, r => r.userId === userId)
  }

  getLatestRecommendation() {
    return chain(this.fragment)
      .get('recommendations', [])
      .last()
      .value()
  }

  async createFragmentFromRevision(FragmentModel) {
    const newFragmentBody = {
      ...omit(this.fragment, ['revision', 'recommendations', 'id']),
      ...this.fragment.revision,
      invitations: this.getInvitations({
        isAccepted: true,
        type: 'submitted',
      }),
      version: this.fragment.version + 1,
      created: new Date(),
    }

    let newFragment = new FragmentModel(newFragmentBody)
    newFragment = await newFragment.save()

    return newFragment
  }

  async removeRevision() {
    delete this.fragment.revision
    await this.fragment.save()
  }

  getLatestEiCRequestToRevision() {
    const { fragment: { recommendations = [] } } = this
    return findLast(
      recommendations,
      rec =>
        rec.recommendationType === 'editorRecommendation' &&
        rec.recommendation === 'revision',
    )
  }
}

module.exports = Fragment