const db = require('./db')
const { searchEngineClient, ENTRY_INDEX } = require('./search-engine')
const { intoDbArray, getInstanceSetting, removeHtmlTags } = require('./helpers')
const { prepareEntryForIndexing } = require('./helpers/dictionary')

const Entry = {}

// Create a new dictionary entry in DB.
Entry.create = async (userId, dictionaryId, entry) => {
  const pickedLinks = intoDbArray(entry.links, 'always')
  const pickedType = intoDbArray(entry.type, 'always')
  const links = pickedLinks.map((link, index) => ({
    link,
    type: pickedType[index]
  }))
  const foreign = intoDbArray(entry.foreign, 'always')
  const foreignLanguageContent = foreign.reduce((agg, row) => {
    if (row.term || row.definition || row.synonym) {
      agg.push({
        language: row.code,
        terms: intoDbArray(row.term, 'undefined'),
        definition: row.definition || null,
        synonyms: intoDbArray(row.synonym, 'undefined')
      })
    }
    return agg
  }, [])

  const isValid =
    !!entry.term &&
    (!!entry.definition || foreignLanguageContent.some(el => el.terms))

  const values = [
    dictionaryId,
    isValid,
    entry.status,
    entry.term || null,
    userId,
    entry.homonymSort || null,
    entry.wordforms || null,
    entry.accent || null,
    entry.pronunciation,
    intoDbArray(entry.domainLabels, 'always'),
    entry.label || null,
    entry.definition || null,
    intoDbArray(entry.synonyms),
    links,
    entry.other || null,
    foreignLanguageContent,
    intoDbArray(entry.image),
    intoDbArray(entry.audio),
    intoDbArray(entry.video)
  ]
  const text = `SELECT entry_new (${db.genParamStr(values)})`

  const {
    rows: [{ entry_new: entryId }]
  } = await db.query(text, values)

  return entryId
}

// // Fetch all entry terms of a single dictionary from DB.
// Entry.fetchAll = async dictionaryId => {
//   const text = `
//     SELECT
//       e.id,
//       e.is_valid as valid,
//       e.is_published as published,
//       e.term as term,
//       MAX(ef.term) as fterm,
//       CASE
//         WHEN NOW() - e.time_most_recent_comment < INTERVAL '1 week' THEN 'T'
//         WHEN NOW() - e.time_most_recent_comment < INTERVAL '1 month' THEN 'M'
//         WHEN NOW() - e.time_most_recent_comment < INTERVAL '1 year' THEN 'L'
//       ELSE ''
//       END comment_age
//     FROM entry e
//     LEFT JOIN entry_foreign ef ON e.id = ef.entry_id
//     WHERE dictionary_id = $1
//     GROUP BY id, is_valid, is_published, e.term, comment_age
//     ORDER BY e.term`
//   const value = [dictionaryId]

//   const { rows: fetchedTerms } = await db.query(text, value)
//   return fetchedTerms
// }

// Metoda za poizvedbo demo podatkov za določeno stran.
// Entry.fetchPaginated = async (resultsPerPage, page) => {
//   const {
//     rows: [{ result }]
//   } = await db.query(
//     `
//       SELECT jsonb_build_object(
//         'pages_total', (
//           SELECT CEIL(COUNT(*) / $1::float)
//           FROM demo_paginacija
//         ),
//         'results', ARRAY(
//           SELECT jsonb_build_object(
//             dictionary_id,
//             term,
//             is_published,
//             is_terminology_reviewed,
//             is_language_reviewed,
//             status,
//             label,
//             definition,
//             synonym,
//             other,
//             image,
//             audio,
//             video
//           )
//           FROM entry
//           LIMIT $1
//           OFFSET $2
//         )
//       ) result
//     `,
//     [resultsPerPage, resultsPerPage * (page - 1)]
//   )

//   return result
// }

// Fetch all data, related to single entry from DB.
Entry.fetchFull = async entryId => {
  const text = `
    SELECT
      jsonb_strip_nulls(
        jsonb_build_object(
          'dictionary_id', e.dictionary_id,
          'is_valid', e.is_valid,
          'is_published', e.is_published,
          'is_terminology_reviewed', e.is_terminology_reviewed,
          'is_language_reviewed', e.is_language_reviewed,
          'status', e.status,
          'term', e.term,
          'version', e.version,
          'version_author', (
            SELECT username
            FROM "user" u
            LEFT JOIN entry e ON e.version_author = u.id
            WHERE e.id = $1
          ),
          'homonym_sort', e.homonym_sort,
          'label', e.label,
          'definition', e.definition,
          'synonyms', e.synonym,
          'other', e.other,
          'image', e.image,
          'audio', e.audio,
          'video', e.video,
          'time_modified', e.time_modified,
          'domain_labels', ARRAY(
            SELECT name
            FROM entry_domain_label edl
            LEFT JOIN domain_label dl ON dl.id = edl.domain_label_id
            WHERE entry_id = e.id
          ),
          'links', ARRAY(
            SELECT jsonb_build_object(
              'type', type,
              'link', link
            )
            FROM entry_link
            WHERE entry_id = e.id
          ),
          'foreign_entries', ARRAY(
            SELECT jsonb_strip_nulls(
              jsonb_build_object(
                'language_id', language_id,
                'term', term,
                'definition', definition,
                'synonym', synonym
              )
            )
            FROM entry_foreign
            WHERE entry_id = e.id
          ),
          'versions', ARRAY(
            SELECT jsonb_strip_nulls(
             jsonb_build_object(
                'version', version,
                'version_time', version_time
              )
            )
            FROM entry_version_history
            WHERE entry_id = e.id
          )
        )
      ) entry
      FROM entry e
      WHERE e.id = $1`
  const value = [entryId]
  const {
    rows: [{ entry }]
  } = await db.query(text, value)

  return entry
}

// Fetch all data, related to single entry from DB with ordered foreign languages.
Entry.fetchFullWithOrderedForeignLanguages = async entryId => {
  const text = `
    SELECT
      jsonb_strip_nulls(
        jsonb_build_object(
          'dictionary_id', e.dictionary_id,
          'is_valid', e.is_valid,
          'is_published', e.is_published,
          'is_terminology_reviewed', e.is_terminology_reviewed,
          'is_language_reviewed', e.is_language_reviewed,
          'status', e.status,
          'term', e.term,
          'synonym', e.synonym,
          'version', e.version,
          'version_author', (
            SELECT username
            FROM "user" u
            LEFT JOIN entry e ON e.version_author = u.id
            WHERE e.id = $1
          ),
          'homonym_sort', e.homonym_sort,
          'label', e.label,
          'definition', e.definition,
          'synonyms', e.synonym,
          'other', e.other,
          'image', e.image,
          'audio', e.audio,
          'video', e.video,
          'time_modified', e.time_modified,
          'domain_labels', ARRAY(
            SELECT name
            FROM entry_domain_label edl
            LEFT JOIN domain_label dl ON dl.id = edl.domain_label_id
            WHERE entry_id = e.id
          ),
          'links', ARRAY(
            SELECT jsonb_build_object(
              'type', type,
              'link', link
            )
            FROM entry_link
            WHERE entry_id = e.id
          ),
          'foreign_entries', ARRAY(
            SELECT jsonb_strip_nulls(
              jsonb_build_object(
                'language_id', ef.language_id,
                'term', ef.term,
                'definition', ef.definition,
                'synonym', ef.synonym
              )
            )
            FROM entry e
            LEFT JOIN entry_foreign ef on ef.entry_id = e.id
            LEFT JOIN dictionary_language dl on dl.dictionary_id = e.dictionary_id AND ef.language_id = dl.language_id
            LEFT JOIN language l on l.id = ef.language_id
            WHERE entry_id = $1
            ORDER BY dl.selection_order
          ),
          'versions', ARRAY(
            SELECT jsonb_strip_nulls(
             jsonb_build_object(
                'version', version,
                'version_time', version_time
              )
            )
            FROM entry_version_history
            WHERE entry_id = e.id
          )
        )
      ) entry
      FROM entry e
      WHERE e.id = $1`
  const value = [entryId]
  const {
    rows: [{ entry }]
  } = await db.query(text, value)

  return entry
}

// Fetch single entry data from DB.
Entry.fetch = async entryId => {
  const text = `
    SELECT
      dictionary_id,
      term,
      is_published,
      is_terminology_reviewed,
      is_language_reviewed,
      homonym_sort,
      status,
      label,
      definition,
      synonym,
      other,
      image,
      audio,
      video
    FROM entry
    WHERE id = $1`
  const value = [entryId]
  const { rows: fetchedEntryData } = await db.query(text, value)
  return fetchedEntryData[0]
}

// Fetch domain labels associated with a single entry from DB.
Entry.fetchDomainLabels = async entryId => {
  const text = `
    SELECT
      dl.id,
      dl.dictionary_id,
      dl.name	
    FROM entry e
    INNER JOIN entry_domain_label edl ON e.id = edl.entry_id
    INNER JOIN domain_label dl ON edl.domain_label_id = dl.id
    WHERE e.id = $1`
  const value = [entryId]
  const { rows: fetchedDomainLabels } = await db.query(text, value)
  return fetchedDomainLabels
}

// Fetch foreign content associated with a single entry from DB.
Entry.fetchForeign = async entryId => {
  const text = `
    SELECT term, language_id, definition, synonym
    FROM entry_foreign
    WHERE entry_id=$1`
  const value = [entryId]
  const { rows: fetchedEntries } = await db.query(text, value)
  return fetchedEntries
}

// Redundant. Use getInstanceSetting function instead.
// Entry.fetchEditingPhase = async () => {
//   const text = `
//     SELECT
//       name, value
//     FROM
//       instance_settings
//     WHERE
//       name = 'can_publish_entries_in_edit'
//   `
//   const { rows: fetchedPhase } = await db.query(text)
//   const aggregatedSettings = aggregateSettings(fetchedPhase)
//   const deserializedSettings = deserialize.dictSettings(aggregatedSettings)
//   return deserializedSettings
// }

// Delete foreign content of a single entry from DB.
Entry.deleteForeign = async entryId => {
  const text = 'DELETE FROM entry_foreign WHERE entry_id = $1'
  const value = [entryId]
  await db.query(text, value)
}

// Delete domain label associations with a single entry from DB.
Entry.deleteDomainLabels = async entryId => {
  const text = 'DELETE FROM entry_domain_label WHERE entry_id = $1'
  const value = [entryId]
  await db.query(text, value)
}

// Delete links of a single entry from DB.
Entry.deleteLinks = async entryId => {
  const text = 'DELETE FROM entry_link WHERE entry_id = $1'
  const value = [entryId]
  await db.query(text, value)
}

// Delete single entry from DB.
Entry.delete = async entryId => {
  const text = 'DELETE FROM entry WHERE id = $1 RETURNING dictionary_id'
  const value = [entryId]
  const {
    rows: [{ dictionary_id: dictionaryId }]
  } = await db.query(text, value)
  return dictionaryId
}

// Delete foreign content for selected dictionary from DB.
Entry.deleteAllForeign = async dictionaryId => {
  const text = `
    DELETE FROM entry_foreign 
    WHERE entry_id
    IN (SELECT id
        FROM entry
        WHERE dictionary_id = $1)`
  const value = [dictionaryId]
  await db.query(text, value)
}

// Delete all domain label associations for selected dictionary from DB.
Entry.deleteAllDomainLabels = async dictionaryId => {
  const text = `
    DELETE FROM entry_domain_label 
    WHERE entry_id
    IN (SELECT id
        FROM entry
        WHERE dictionary_id = $1)`
  const value = [dictionaryId]
  await db.query(text, value)
}

// Delete all links for selected dictionary.
Entry.deleteAllLinks = async dictionaryId => {
  const text = `
    DELETE FROM entry_link 
    WHERE entry_id
    IN (SELECT id
        FROM entry
        WHERE dictionary_id = $1)`
  const value = [dictionaryId]
  await db.query(text, value)
}

// (Re)index specific entry into entry search index.
Entry.indexIntoSearchEngine = async (entryId, shouldWait) => {
  const values = [entryId]
  const text = `
    SELECT
      jsonb_strip_nulls(
        jsonb_build_object(
          'id', e.id,
          'is_valid', e.is_valid,
          'is_published', e.is_published,
          'is_terminology_reviewed', e.is_terminology_reviewed,
          'is_language_reviewed', e.is_language_reviewed,
          'status', e.status,
          'term', e.term,
          'homonym_sort', e.homonym_sort,
          'label', e.label,
          'definition', e.definition,
          'synonyms', e.synonym,
          'other', e.other,
          'time_most_recent_comment', e.time_most_recent_comment,
          'domain_labels', ARRAY(
            SELECT name
            FROM entry_domain_label edl
            LEFT JOIN domain_label dl ON dl.id = edl.domain_label_id
            WHERE entry_id = e.id
          ),
          'links', ARRAY(
            SELECT link
            FROM entry_link
            WHERE entry_id = e.id
          ),
          'foreign_entries', ARRAY(
            SELECT jsonb_strip_nulls(
              jsonb_build_object(
                'lang', jsonb_build_object(
                  'id', l.id,
                  'code', l.code,
                  'nameSl', l.name_sl,
                  'nameEn', l.name_en
                ),
                'terms', ef.term,
                'definition', ef.definition,
                'synonyms', ef.synonym
              )
            )
            FROM entry_foreign ef
            LEFT JOIN LANGUAGE l ON l.id = ef.language_id
            WHERE entry_id = e.id
          )
        )
      ) entry,
      jsonb_strip_nulls(
        jsonb_build_object(
          'id', d.id,
          'nameSl', d.name_sl,
          'nameSlShort', d.name_sl_short,
          'nameEn', d.name_en,
          'status', d.status
        )
      ) "dictionary",
      jsonb_build_object(
        'id', dp.id,
        'nameSl', dp.name_sl,
        'nameEn', dp.name_en
      ) primary_domain
    FROM entry e
    JOIN dictionary d ON d.id = e.dictionary_id
    JOIN domain_primary dp ON dp.id = d.domain_primary_id
    WHERE e.id = $1`

  const {
    rows: [dataToIndex]
  } = await db.query(text, values)

  const { dictionary, primary_domain: primaryDomain } = dataToIndex
  let { entry } = dataToIndex
  // TODO Luka: I expect "source" needing a rework once linked portals and dictionaries start working.
  const source = {
    code: await getInstanceSetting('portal_code'),
    name: await getInstanceSetting('portal_name')
  }

  entry = prepareEntryForIndexing(entry)
  entry.primaryDomain = primaryDomain
  entry.dictionary = dictionary
  entry.source = source
  entry = removeHtmlTags(JSON.stringify(entry))

  await searchEngineClient.index({
    id: entryId,
    index: ENTRY_INDEX,
    body: entry,
    refresh: shouldWait ? 'wait_for' : false
  })
}

// Delete all entries for selected dictionary from search index.
Entry.deleteAllFromIndex = async dictionaryId => {
  await searchEngineClient.deleteByQuery({
    index: ENTRY_INDEX,
    body: { query: { match: { 'dictionary.id': dictionaryId } } }
  })
}

// Delete all entries for selected dictionary from DB.
Entry.deleteAll = async dictionaryId => {
  const text = 'DELETE FROM entry WHERE dictionary_id = $1'
  const value = [dictionaryId]
  await db.query(text, value)
}

// Publish all entries for selected dictionary from DB that match the criteria.
Entry.publishAllQualified = async dictionaryId => {
  const text = `
    UPDATE entry
    SET is_published = TRUE
    WHERE
      dictionary_id = $1
      AND is_valid = TRUE
      AND status = ANY(
        CASE (SELECT value FROM instance_settings WHERE name = 'can_publish_entries_in_edit')
          WHEN 'T' THEN ARRAY ['complete', 'in_edit']::entry_status[]
          ELSE ARRAY ['complete']::entry_status[]
        END
      )
  `
  const value = [dictionaryId]

  await db.query(text, value)
}

// Update single entry in DB.
Entry.update = async (userId, entry) => {
  // Copied from Entry.create and modified.
  const pickedLinks = intoDbArray(entry.links, 'always')
  const pickedType = intoDbArray(entry.type, 'always')
  const links = pickedLinks.map((link, index) => ({
    link,
    type: pickedType[index]
  }))
  const foreign = intoDbArray(entry.foreign, 'always')
  const foreignLanguageContent = foreign.reduce((agg, row) => {
    if (row.term || row.definition || row.synonym) {
      agg.push({
        language: row.code,
        terms: intoDbArray(row.term, 'undefined'),
        definition: row.definition || null,
        synonyms: intoDbArray(row.synonym, 'undefined')
      })
    }
    return agg
  }, [])
  const isValid =
    !!entry.term &&
    (!!entry.definition || foreignLanguageContent.some(el => el.terms))

  const values = [
    entry.entryId,
    isValid,
    !!entry.isPublished,
    !!entry.isTerminologyReviewed,
    !!entry.isLanguageReviewed,
    entry.status,
    entry.term || null,
    userId,
    entry.homonymSort || null,
    intoDbArray(entry.domainLabels, 'always'),
    entry.label || null,
    entry.definition || null,
    intoDbArray(entry.synonyms),
    links,
    entry.other || null,
    foreignLanguageContent,
    intoDbArray(entry.image),
    intoDbArray(entry.audio),
    intoDbArray(entry.video)
  ]
  const text = `SELECT entry_update (${db.genParamStr(values)})`

  const {
    rows: [{ entry_update: dictionaryId }]
  } = await db.query(text, values)

  return dictionaryId
}

// // Fetch all versions (with timestamps) of a single entry from DB.
// Entry.fetchVersions = async entryId => {
//   const { rows: historyVersions } = await db.query(
//     'SELECT version, version_time FROM entry_version_history WHERE entry_id = $1',
//     [entryId]
//   )

//   return historyVersions
// }

// Fetch a single version snapshot of a single entry from DB.
Entry.fetchVersionSnapshot = async (entryId, version) => {
  const {
    rows: [{ version_snapshot: historySnapshot }]
  } = await db.query(
    'SELECT version_snapshot FROM entry_version_history WHERE entry_id = $1 and version = $2',
    [entryId, version]
  )

  return historySnapshot
}

/* Fetch by language and entry Id. Note that this version includes the language name */
Entry.fetchForeignEntryById = async entryId => {
  const text = `
    SELECT
      entry_id,
      language_id,
      code,
      name_sl,
      name_en,
      term,
      definition,
      synonym
    FROM entry_foreign ef
    INNER JOIN language lang ON ef.language_id = lang.id
    WHERE ef.entry_id = $1`
  const value = [entryId]
  const { rows: fetchedDomainLabels } = await db.query(text, value)
  return fetchedDomainLabels
}

module.exports = Entry
