import { CaminoRequest, CustomResponse } from './express-type'
import { HTTP_STATUS } from 'camino-common/src/http'
import { Pool } from 'pg'
import { Activite, activiteDocumentIdValidator, ActiviteId, activiteIdOrSlugValidator, activiteIdValidator, ActiviteSuper } from 'camino-common/src/activite'
import {
  Contenu,
  administrationsLocalesByActiviteId,
  deleteActiviteDocument,
  entreprisesTitulairesOuAmoditairesByActiviteId,
  getActiviteById,
  GetActiviteByIdErrors,
  getActiviteDocumentsByActiviteId,
  getLargeobjectIdByActiviteDocumentId,
  insertActiviteDocument,
  titreTypeIdByActiviteId,
  updateActiviteQuery,
  DbActivite,
  activiteDeleteQuery,
  UpdateActiviteQueryErrors,
  DeleteActiviteDocumentErrors,
  getActivitesSuper,
  resetActiviteStatusQuery,
  ResetActiviteStatusQueryErrors,
} from './activites.queries'
import { NewDownload } from './fichiers'
import { SimplePromiseFn, isNonEmptyArray, memoize } from 'camino-common/src/typescript-tools'
import { canEditActivite, isActiviteDeposable } from 'camino-common/src/permissions/activites'
import { SectionWithValue } from 'camino-common/src/sections'
import { Section, getSectionsWithValue } from 'camino-common/src/static/titresTypes_demarchesTypes_etapesTypes/sections'
import { newActiviteDocumentId } from '../../database/models/_format/id-create'
import { ACTIVITES_STATUTS_IDS } from 'camino-common/src/static/activitesStatuts'
import { Unites } from 'camino-common/src/static/unites'
import { isSuper, User } from 'camino-common/src/roles'
import { TitreTypeId } from 'camino-common/src/static/titresTypes'
import { AdministrationId } from 'camino-common/src/static/administrations'
import { EntrepriseId } from 'camino-common/src/entreprise'
import { getCurrent } from 'camino-common/src/date'
import { createLargeObject, CreateLargeObjectError } from '../../database/largeobjects'
import { callAndExit } from '../../tools/fp-tools'
import { RestNewGetCall, RestNewPutCall } from '../../server/rest'
import { CaminoApiError } from '../../types'
import { Effect, Match, Option } from 'effect'
import { EffectDbQueryAndValidateErrors } from '../../pg-database'

const extractContenuFromSectionWithValue = (sections: Section[], sectionsWithValue: SectionWithValue[]): Contenu => {
  const contenu: Contenu = {}
  sections.forEach(section => {
    const currentContent: Record<string, unknown> = {}
    section.elements.forEach(element => {
      const newSection = sectionsWithValue.find(newSection => newSection.id === section.id)
      if (newSection !== undefined) {
        const newElement = newSection.elements.find(newElement => newElement.id === element.id)
        if (newElement !== undefined) {
          let value = newElement.value
          if (section.id === 'substancesFiscales' && 'uniteId' in element && element.uniteId !== undefined && newElement.value !== null) {
            const ratio = Unites[element.uniteId].referenceUniteRatio
            if (ratio !== null) {
              value = (newElement.value as number) * ratio
            }
          }
          currentContent[element.id] = value
        }
      }
    })
    contenu[section.id] = currentContent
  })

  return contenu
}
const activiteNonDeposeeError = 'Seule une activité déposée peut être réinitialisée' as const
type ResetActiviteDepotErrors =
  | GetActiviteByIdErrors
  | typeof canEditActiviteError
  | typeof editionDeLActiviteImpossible
  | typeof activiteNonDeposeeError
  | ResetActiviteStatusQueryErrors
  | EffectDbQueryAndValidateErrors
export const resetActiviteDepot: RestNewPutCall<'/rest/activites/:activiteId/reset-depot'> = (rootPipe): Effect.Effect<{ id: ActiviteId }, CaminoApiError<ResetActiviteDepotErrors>> =>
  rootPipe.pipe(
    Effect.let('titreTypeId', ({ params, pool }) => memoize(() => callAndExit(titreTypeIdByActiviteId(params.activiteId, pool)))),
    Effect.let('administrationsLocales', ({ params, pool }) => memoize(() => administrationsLocalesByActiviteId(params.activiteId, pool))),
    Effect.let('entreprisesTitulairesOuAmodiataires', ({ params, pool }) => memoize(() => entreprisesTitulairesOuAmoditairesByActiviteId(params.activiteId, pool))),
    Effect.bind('result', ({ params, pool, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, user }) =>
      getActiviteById(params.activiteId, pool, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires)
    ),
    Effect.tap(({ user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, result }) =>
      Effect.tryPromise({
        try: () => canEditActivite(user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, result.activite_statut_id),
        catch: e => ({ message: canEditActiviteError, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          canRead => canRead,
          () => ({ message: editionDeLActiviteImpossible })
        )
      )
    ),
    Effect.filterOrFail(
      ({ result }) => result.activite_statut_id === ACTIVITES_STATUTS_IDS.DEPOSE,
      () => ({ message: activiteNonDeposeeError })
    ),
    Effect.tap(({ pool, user, result, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires }) =>
      resetActiviteStatusQuery(pool, user, result.id, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires)
    ),
    Effect.map(({ result }) => ({ id: result.id })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.when("Pas d'activité trouvée", () => ({ ...caminoError, status: HTTP_STATUS.NOT_FOUND })),
        Match.when('Seule une activité déposée peut être réinitialisée', () => ({ ...caminoError, status: HTTP_STATUS.BAD_REQUEST })),
        Match.whenOr("Droit insuffisants pour éditer l'activité", "Interdiction d'éditer une activité", () => ({
          ...caminoError,
          status: HTTP_STATUS.FORBIDDEN,
        })),
        Match.whenOr(
          "Impossible d'exécuter la requête dans la base de données",
          "Impossible de vérifier si on peut éditer l'activité",
          "Lecture de l'activité impossible",
          'Les données en base ne correspondent pas à ce qui est attendu',
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.exhaustive
      )
    )
  )

const canEditActiviteError = "Impossible de vérifier si on peut éditer l'activité" as const
const editionDeLActiviteImpossible = "Droit insuffisants pour éditer l'activité" as const
type UpdateActiviteErrors =
  | GetActiviteByIdErrors
  | typeof canEditActiviteError
  | typeof editionDeLActiviteImpossible
  | UpdateActiviteQueryErrors
  | DeleteActiviteDocumentErrors
  | CreateLargeObjectError
export const updateActivite: RestNewPutCall<'/rest/activites/:activiteId'> = (rootPipe): Effect.Effect<{ id: ActiviteId }, CaminoApiError<UpdateActiviteErrors>> =>
  rootPipe.pipe(
    Effect.let('titreTypeId', ({ params, pool }) => memoize(() => callAndExit(titreTypeIdByActiviteId(params.activiteId, pool)))),
    Effect.let('administrationsLocales', ({ params, pool }) => memoize(() => administrationsLocalesByActiviteId(params.activiteId, pool))),
    Effect.let('entreprisesTitulairesOuAmodiataires', ({ params, pool }) => memoize(() => entreprisesTitulairesOuAmoditairesByActiviteId(params.activiteId, pool))),
    Effect.bind('result', ({ params, pool, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, user }) =>
      getActiviteById(params.activiteId, pool, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires)
    ),
    Effect.tap(({ user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, result }) =>
      Effect.tryPromise({
        try: () => canEditActivite(user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, result.activite_statut_id),
        catch: e => ({ message: canEditActiviteError, extra: e }),
      }).pipe(
        Effect.filterOrFail(
          canRead => canRead,
          () => ({ message: editionDeLActiviteImpossible })
        )
      )
    ),
    Effect.let('contenu', ({ result, body }) => extractContenuFromSectionWithValue(result.sections, body.sectionsWithValue)),
    Effect.tap(({ pool, user, result, contenu, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires }) =>
      updateActiviteQuery(pool, user, result.id, contenu, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires)
    ),
    Effect.bind('oldActiviteDocuments', ({ pool, result }) => getActiviteDocumentsByActiviteId(result.id, pool)),
    Effect.tap(({ body, oldActiviteDocuments, pool, result }) => {
      const alreadyExistingDocumentIds = body.activiteDocumentIds

      if (isNonEmptyArray(oldActiviteDocuments)) {
        return Effect.forEach(oldActiviteDocuments, oldActiviteDocument => {
          const documentId = alreadyExistingDocumentIds.find(id => id === oldActiviteDocument.id)
          if (!documentId) {
            return deleteActiviteDocument(oldActiviteDocument.id, oldActiviteDocument.activite_document_type_id, result.type_id, ACTIVITES_STATUTS_IDS.EN_CONSTRUCTION, pool)
          }
          return Effect.succeed(true)
        })
      }
      return Effect.succeed(Option.none)
    }),
    Effect.tap(({ body, pool, result }) =>
      Effect.forEach(body.newTempDocuments, document => {
        return Effect.Do.pipe(
          Effect.flatMap(() => createLargeObject(pool, document.tempDocumentName)),
          Effect.flatMap(loid => {
            const date = getCurrent()
            return insertActiviteDocument(pool, {
              id: newActiviteDocumentId(date, document.activite_document_type_id),
              activite_document_type_id: document.activite_document_type_id,
              description: document.description ?? '',
              date,
              largeobject_id: loid,
              activite_id: result.id,
            })
          })
        )
      })
    ),
    Effect.map(({ result }) => ({ id: result.id })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.when("Pas d'activité trouvée", () => ({ ...caminoError, status: HTTP_STATUS.NOT_FOUND })),
        Match.whenOr("Droit insuffisants pour éditer l'activité", "Interdiction d'éditer une activité", "droits insuffisants pour supprimer un document d'activité", () => ({
          ...caminoError,
          status: HTTP_STATUS.FORBIDDEN,
        })),
        Match.whenOr(
          "Impossible d'exécuter la requête dans la base de données",
          "Impossible de vérifier si on peut éditer l'activité",
          "Lecture de l'activité impossible",
          'Les données en base ne correspondent pas à ce qui est attendu',
          "impossible d'insérer un fichier en base",
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.exhaustive
      )
    )
  )

const formatActivite = async (
  dbActivite: DbActivite,
  pool: Pool,
  user: User,
  titreTypeId: SimplePromiseFn<TitreTypeId>,
  administrationsLocales: SimplePromiseFn<AdministrationId[]>,
  entreprisesTitulairesOuAmodiataires: SimplePromiseFn<EntrepriseId[]>
): Promise<Activite> => {
  const sectionsWithValue: SectionWithValue[] = getSectionsWithValue(dbActivite.sections, dbActivite.contenu)

  const activiteDocuments = await callAndExit(getActiviteDocumentsByActiviteId(dbActivite.id, pool))
  const deposable = await isActiviteDeposable(
    user,
    titreTypeId,
    administrationsLocales,
    entreprisesTitulairesOuAmodiataires,
    { ...dbActivite, sections_with_value: sectionsWithValue },
    activiteDocuments
  )
  const modification = await canEditActivite(user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, dbActivite.activite_statut_id)

  return {
    id: dbActivite.id,
    slug: dbActivite.slug,
    activite_statut_id: dbActivite.activite_statut_id,
    type_id: dbActivite.type_id,
    annee: dbActivite.annee,
    date_saisie: dbActivite.date_saisie,
    date: dbActivite.date,
    periode_id: dbActivite.periode_id,
    suppression: dbActivite.suppression,
    deposable,
    modification,
    sections_with_value: sectionsWithValue,
    titre: {
      nom: dbActivite.titre_nom,
      slug: dbActivite.titre_slug,
    },
    activite_documents: activiteDocuments,
  }
}
export const getActivite =
  (pool: Pool) =>
  async (req: CaminoRequest, res: CustomResponse<Activite>): Promise<void> => {
    const activiteIdParsed = activiteIdOrSlugValidator.safeParse(req.params.activiteId)
    const user = req.auth

    if (!activiteIdParsed.success) {
      res.sendStatus(HTTP_STATUS.BAD_REQUEST)
    } else {
      try {
        const titreTypeId = memoize(() => callAndExit(titreTypeIdByActiviteId(activiteIdParsed.data, pool)))
        const administrationsLocales = memoize(() => administrationsLocalesByActiviteId(activiteIdParsed.data, pool))
        const entreprisesTitulairesOuAmodiataires = memoize(() => entreprisesTitulairesOuAmoditairesByActiviteId(activiteIdParsed.data, pool))

        const result = await callAndExit(getActiviteById(activiteIdParsed.data, pool, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires))

        const activite = await formatActivite(result, pool, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires)
        res.json(activite)
      } catch (e) {
        res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR)
        console.error(e)
      }
    }
  }

export const deleteActivite =
  (pool: Pool) =>
  async (req: CaminoRequest, res: CustomResponse<void>): Promise<void> => {
    const activiteIdParsed = activiteIdValidator.safeParse(req.params.activiteId)
    if (!activiteIdParsed.success) {
      res.sendStatus(HTTP_STATUS.BAD_REQUEST)
    } else {
      const id = activiteIdParsed.data
      const titreTypeId = memoize(() => callAndExit(titreTypeIdByActiviteId(id, pool)))
      const administrationsLocales = memoize(() => administrationsLocalesByActiviteId(id, pool))
      const entreprisesTitulairesOuAmodiataires = memoize(() => entreprisesTitulairesOuAmoditairesByActiviteId(id, pool))

      const isOk = await callAndExit(activiteDeleteQuery(id, pool, req.auth, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires))
      if (isOk) {
        res.sendStatus(HTTP_STATUS.NO_CONTENT)
      } else {
        res.sendStatus(HTTP_STATUS.NOT_FOUND)
      }
    }
  }

export const activiteDocumentDownload: NewDownload = async (params, user, pool) => {
  const activiteDocumentId = activiteDocumentIdValidator.parse(params.documentId)
  const activiteDocumentLargeObjectId = await getLargeobjectIdByActiviteDocumentId(activiteDocumentId, pool, user)

  return { loid: activiteDocumentLargeObjectId, fileName: activiteDocumentId }
}

const permissionsInsuffisantes = 'Permissions insuffisantes' as const
type GetActivitesSuperErrors = EffectDbQueryAndValidateErrors | typeof permissionsInsuffisantes

export const getActivitesForTDBSuper: RestNewGetCall<'/rest/activitesSuper'> = (rootPipe): Effect.Effect<ActiviteSuper[], CaminoApiError<GetActivitesSuperErrors>> =>
  rootPipe.pipe(
    Effect.filterOrFail(
      ({ user }) => isSuper(user),
      () => ({ message: permissionsInsuffisantes })
    ),
    Effect.flatMap(({ pool }) => getActivitesSuper(pool)),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr('Permissions insuffisantes', () => ({
          ...caminoError,
          status: HTTP_STATUS.FORBIDDEN,
        })),
        Match.whenOr("Impossible d'exécuter la requête dans la base de données", 'Les données en base ne correspondent pas à ce qui est attendu', () => ({
          ...caminoError,
          status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
        })),
        Match.exhaustive
      )
    )
  )
