const { rm } = require('fs/promises')
const user = require('../../../middleware/user')
const Dictionary = require('../../../models/dictionary')
const Entry = require('../../../models/entry')
const Extraction = require('../../../models/extraction')
const {
  searchEntryIndex,
  deleteEntryFromIndex,
  deleteDictionaryEntriesFromIndex
} = require('../../../models/search-engine')
const genEditorAllQuery = require('../../../models/helpers/search/generate-query/editor/all')
const { prepareEditorEntries } = require('../../../models/helpers/search')
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
const { minEntriesRequirementCheckAndAct } = require('../../helpers/dictionary')
const { getExportFilesPath } = require('../../../models/helpers/dictionary')

const dictionary = {}

dictionary.getEntry = async (req, res) => {
  const entryId = req.query.id
  const [entry, domainLabels] = await Promise.all([
    Entry.fetchFull(entryId),
    Dictionary.fetchDomainLabelsFromEntryId(entryId)
  ])

  const data = {
    entry,
    allDomainLabels: domainLabels
  }
  res.send(data)
}

dictionary.createEntry = async (req, res) => {
  const { body } = req
  const { dictionaryId } = body

  const entryId = await Entry.create(req.user.id, dictionaryId, body)
  await Promise.all([
    Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
    Entry.indexIntoSearchEngine(entryId, true)
  ])

  res.send({ entryId })
}

dictionary.updateEntry = async (req, res) => {
  const { entryId } = req.body
  const dictionaryId = await Entry.update(req.user.id, req.body)
  await Promise.all([
    Dictionary.updateMetadataAfterModifyingEntry(entryId),
    Entry.indexIntoSearchEngine(entryId, true),
    minEntriesRequirementCheckAndAct.onUpdate(dictionaryId)
  ])
  res.end()
}

dictionary.fetchEntries = async (req, res) => {
  const dictionaryId = req.query.id
  const hitsQuery = genEditorAllQuery(dictionaryId)
  const hits = await searchEntryIndex(hitsQuery)

  const terms = prepareEditorEntries(hits)
  res.send(terms)
}

// This function deletes selected entry.
dictionary.deleteEntry = async (req, res) => {
  const { entryId } = req.query
  const dictionaryId = await Entry.delete(entryId)
  await Promise.all([
    Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
    deleteEntryFromIndex(entryId, true),
    minEntriesRequirementCheckAndAct.onDelete(
      dictionaryId,
      req.app,
      req.determinedLanguage
    )
  ])

  res.end()
}

// This function deletes all entries in seleceted dictionary.
dictionary.deleteAllEntries = async (req, res) => {
  // TODO This method executes all delete operations before sending the response,
  // TODO after which the client tells the user it might take a few minutes.
  const dictionaryId = +req.params.dictionaryId
  await Entry.deleteAll(dictionaryId)
  await Promise.all([
    Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
    deleteDictionaryEntriesFromIndex(dictionaryId),
    minEntriesRequirementCheckAndAct.onDelete(
      dictionaryId,
      req.app,
      req.determinedLanguage
    )
  ])

  res.end()
}

dictionary.publishAllEntries = async (req, res) => {
  // // TODO This method executes all operations before sending the response,
  // // TODO after which the client tells the user it might take a few minutes.
  // // TODO Also, the message is generic and the same as with deleting a dictionary or all of its entries.

  const dictionaryId = +req.params.dictionaryId

  await Entry.publishAllQualified(dictionaryId)

  // TODO Consider reducing the following two index operations into a single one.
  await Promise.all([
    Dictionary.updateMetadataAfterModifyingEntries(dictionaryId),
    deleteDictionaryEntriesFromIndex(dictionaryId)
  ])

  await Dictionary.indexIntoSearchEngine(dictionaryId)

  res.end()
}

dictionary.delete = async (req, res) => {
  // TODO This method executes all operations before sending the response,
  // TODO after which the client tells the user it might take a few minutes.
  // TODO It also keeps the user on a page which shouldn't exist anymore.
  // TODO In fact, any value is apparently valid as dictionaryId URL parameter (no validation yet).
  const dictionaryId = +req.params.dictionaryId
  await Dictionary.delete(dictionaryId)
  await deleteDictionaryEntriesFromIndex(dictionaryId)
  const exportFilesPath = getExportFilesPath(dictionaryId)
  await rm(exportFilesPath, { recursive: true, force: true })

  res.end()
}

dictionary.updateDomainLabels = [
  (req, res, next) => {
    req.dictionaryId = req.body.params.dictionaryId
    next()
  },
  user.canAdministrateDictionary,
  async (req, res) => {
    const { dictionaryId, payload } = req.body.params

    await Dictionary.updateDomainLabel(dictionaryId, payload)
    res.end()
  }
]

dictionary.renovateSecondaryDomains = async (req, res) => {
  const data = req.body.params.payload

  await Dictionary.renovateSecondaryDomains(data)
  res.end()
}

dictionary.getEntryVersionSnapshot = async (req, res) => {
  const { entryId, version } = req.params

  const historySnapshot = await Entry.fetchVersionSnapshot(entryId, version)

  res.send(historySnapshot)
}

dictionary.listDictionaries = async (req, res) => {
  const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE

  const page = +req.query.p > 0 ? +req.query.p : 1

  const { pages_total: numberOfAllPages, results } =
    await Dictionary.fetchAllAdminDictionaries(
      req.determinedLanguage,
      resultsPerPage,
      page
    )

  res.send({ page, numberOfAllPages, results })
}

dictionary.listDomainLabels = async (req, res) => {
  const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
  const { dictionaryId } = req.params
  const page = +req.query.p > 0 ? +req.query.p : 1

  const { pages_total: numberOfAllPages, results } =
    await Dictionary.fetchPaginationDomainLabels(
      dictionaryId,
      resultsPerPage,
      page
    )

  res.send({ page, numberOfAllPages, results })
}

dictionary.listFilteredDomainLabels = async (req, res) => {
  const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
  const { dictionaryId } = req.params
  const page = +req.query.p > 0 ? +req.query.p : 1

  let { q } = req.query

  if (!q) {
    q = ''
  }

  const { pages_total: numberOfAllPages, results } =
    await Dictionary.fetchFilteredPaginationDomainLabels(
      dictionaryId,
      q,
      resultsPerPage,
      page
    )

  res.append('page', page)
  res.append('number-of-all-pages', numberOfAllPages)
  res.render('utilities/response-pug-wrapper/domainLabelLister', {
    dictionary: { id: dictionaryId },
    numberOfAllPages,
    results
  })
}

dictionary.listSecondaryDomainData = async (req, res) => {
  const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
  let { q } = req.query
  const page = +req.query.p > 0 ? +req.query.p : 1

  if (!q) {
    q = ''
  }

  const { pages_total: numberOfAllPages, results } =
    await Dictionary.fetchFilteredSecondaryDomains(q, resultsPerPage, page)

  res.append('page', page)
  res.append('number-of-all-pages', numberOfAllPages)

  res.render('utilities/response-pug-wrapper/secondaryDomainLister', {
    page,
    numberOfAllPages,
    results
  })
}

dictionary.showImportFromFileForm = async (req, res) => {
  const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
  const { dictionaryId } = req.params
  const page = +req.query.p > 0 ? +req.query.p : 1
  const { pages_total: numberOfAllPages, results } =
    await Dictionary.fetchAllImports(dictionaryId, resultsPerPage, page)

  res.send({ page, numberOfAllPages, results })
}

dictionary.showExportToFileForm = async (req, res) => {
  const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
  const { dictionaryId } = req.params
  const page = +req.query.p > 0 ? +req.query.p : 1
  const { pages_total: numberOfAllPages, results } =
    await Dictionary.fetchExports(dictionaryId, resultsPerPage, page)

  res.send({ page, numberOfAllPages, results })
}

dictionary.importFromExtraction = async (req, res) => {
  const { dictionaryId, extractionId } = req.params
  const { from, to } = req.body
  const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
  const toIndex = Number.isInteger(+(to === '' ? undefined : to))
    ? Math.abs(to)
    : undefined

  // TODO Authentication, authorization, validation.

  const termCandidatesToImport = await Extraction.fetchTermCandidatesSlice(
    extractionId,
    fromIndex,
    toIndex
  )

  await Dictionary.importFromExtraction(
    dictionaryId,
    req.user.id,
    termCandidatesToImport
  )

  await Dictionary.indexIntoSearchEngine(dictionaryId)

  res.end()
}

dictionary.exportBegin = async (req, res) => {
  const { dictionaryId } = req.params
  const exportParams = {
    isValidFilter:
      req.body.isValidFilter === 'on' ? undefined : req.body.isValidFilter,
    isPublishedFilter:
      req.body.isPublishedFilter === 'on'
        ? undefined
        : req.body.isPublishedFilter,
    statusFilter:
      req.body.statusFilter === 'complete'
        ? 'complete'
        : req.body.statusFilter === 'inEdit'
        ? 'in_edit'
        : undefined,
    isTerminologyReviewedFilter:
      req.body.isTerminologyReviewedFilter === 'on'
        ? undefined
        : req.body.isTerminologyReviewedFilter,
    isLanguageReviewedFilter:
      req.body.isLanguageReviewedFilter === 'on'
        ? undefined
        : req.body.isLanguageReviewedFilter,
    exportFileFormat: req.body.exportFileFormat
  }

  const exportId = await Dictionary.beginExport(dictionaryId, exportParams)

  res.end()

  // Explicitcly catch any errors after the response has been sent
  // since the the final error handler won't be able to send another.
  try {
    // TODO Consider delegating processing of export to a seperate process or at least a seperate thread (much like importing, extraction, ...).
    await Dictionary.processExport(exportId)
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error(error)
  }
}

module.exports = dictionary
