import Journaux from '../models/journaux'
import { create } from 'jsondiffpatch'
import Objection, { Model, PartialModelGraph } from 'objection'
import { journauxQueryModify } from './permissions/journaux'
import { ITitreEtape } from '../../types'
import graphBuild from './graph/build'
import { fieldsFormat } from './graph/fields-format'
import options, { FieldId } from './_options'
import { User, UtilisateurId } from 'camino-common/src/roles'
import { TitreId } from 'camino-common/src/validators/titres'
import { JournauxQueryParams } from 'camino-common/src/journaux'
import TitresEtapes from '../models/titres-etapes'
import { EtapeId } from 'camino-common/src/etape'
import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined, isNullOrUndefinedOrEmpty } from 'camino-common/src/typescript-tools'

const diffPatcher = create({
  // on filtre certaines proprietés qu’on ne souhaite pas voir apparaitre dans les journaux
  propertyFilter: (name: string) => !['slug', 'ordre', 'demarche', 'heritageProps'].includes(name),
})

export const journauxGet = async (params: JournauxQueryParams, { fields }: { fields?: FieldId }, user: User): Promise<Objection.Page<Journaux>> => {
  const graph = fields ? graphBuild(fields, 'journaux', fieldsFormat) : options.journaux.graph

  const q = Journaux.query().withGraphFetched(graph)
  q.modify(journauxQueryModify, user) // eslint-disable-line @typescript-eslint/no-misused-promises

  if (isNotNullNorUndefined(params.recherche)) {
    q.leftJoinRelated('titre as titreRecherche')
    q.whereRaw(`lower(??) like ?`, ['titreRecherche.nom', `%${params.recherche.toLowerCase()}%`])
  }

  if (isNotNullNorUndefinedNorEmpty(params.titresIds)) {
    q.whereIn('titreId', params.titresIds)
  }

  q.orderBy('date', 'desc')

  return q.page(params.page - 1, 10)
}

export const createJournalCreate = async (id: string, userId: string, titreId: TitreId): Promise<void> => {
  await Journaux.query().insert({
    elementId: id,
    operation: 'create',
    utilisateurId: userId,
    titreId,
  })
}

export const patchJournalCreate = async (id: EtapeId, partialEntity: Partial<ITitreEtape>, userId: string, titreId: TitreId): Promise<TitresEtapes> => {
  const oldValue = await TitresEtapes.query().findById(id)

  const oldPartialValue = (Object.keys(partialEntity) as Array<keyof Model>).reduce((result, key) => {
    result[key] = oldValue![key]

    return result
  }, {} as any)

  const result = await TitresEtapes.query().patchAndFetchById(id, {
    ...partialEntity,
    id,
  })

  const differences = diffPatcher.diff(oldPartialValue, partialEntity)

  if (differences) {
    await Journaux.query().insert({
      elementId: id,
      utilisateurId: userId,
      operation: 'update',
      differences,
      titreId,
    })
  }

  return result
}

export const upsertJournalCreate = async (id: EtapeId | undefined, entity: PartialModelGraph<ITitreEtape>, userId: UtilisateurId, titreId: TitreId): Promise<ITitreEtape | undefined> => {
  const relations = '[]'
  const oldValue = isNotNullNorUndefined(id) ? await TitresEtapes.query().findById(id).withGraphFetched(relations).returning('*') : undefined

  let newModelId: EtapeId | undefined = oldValue?.id

  if (isNullOrUndefined(newModelId)) {
    newModelId = (await TitresEtapes.query().insert(entity).returning('id')).id
  } else {
    await TitresEtapes.query().update(entity).where('id', newModelId)
  }

  const newValue = await TitresEtapes.query().findById(newModelId).withGraphFetched(relations).returning('*')

  let differences: any
  let operation: 'create' | 'update' = 'create'

  if (isNotNullNorUndefined(oldValue)) {
    differences = diffPatcher.diff(oldValue, newValue)

    // si il n’y a pas de différences, alors on ne journal plus cette modification
    if (isNullOrUndefined(differences) || isNullOrUndefinedOrEmpty(Object.keys(differences))) {
      return newValue
    }
    operation = 'update'
  }

  if (isNotNullNorUndefined(newValue)) {
    await Journaux.query().insert({
      elementId: newValue.id,
      utilisateurId: userId,
      operation,
      differences,
      titreId,
    })
  }

  return newValue
}
