import { Pool } from 'pg'
import { HTTP_STATUS } from 'camino-common/src/http'
import { CaminoRequest, CustomResponse } from './express-type'
import { DemarcheCreationOutput, demarcheIdOrSlugValidator, DemarcheSlug, GetDemarcheMiseEnConcurrence, GetResultatMiseEnConcurrence } from 'camino-common/src/demarche'
import {
  getDemarcheByIdOrSlug,
  GetDemarcheByIdOrSlugErrors,
  getDemarchePivotEnConcurrence,
  getDemarchesEnConcurrenceQuery,
  getEtapesByDemarcheId,
  getFirstEtapeDateByDemarcheIdOrSlug,
  GetFirstEtapeDateByDemarcheIdOrSlugErrors,
} from './demarches.queries'
import { GetDemarcheByIdOrSlug } from 'camino-common/src/titres'
import { getAdministrationsLocalesByTitreId, getDemarchesByTitreId, getTitreByIdOrSlug, GetTitreByIdOrSlugErrors, getTitulairesAmodiatairesByTitreId } from './titres.queries'
import { isNotNullNorUndefined, isNullOrUndefined, memoize } from 'camino-common/src/typescript-tools'
import { canReadDemarche } from './permissions/demarches'
import { titreDemarcheArchive, titreDemarcheGet } from '../../database/queries/titres-demarches'
import { titreDemarcheUpdateTask } from '../../business/titre-demarche-update'
import { canCreateDemarche, canCreateTravaux, canDeleteDemarche, canPublishResultatMiseEnConcurrence } from 'camino-common/src/permissions/titres-demarches'
import { userSuper } from '../../database/user-super'
import { RestNewGetCall, RestNewPostCall } from '../../server/rest'
import { Effect, Match } from 'effect'
import { isTravaux } from 'camino-common/src/static/demarchesTypes'
import { titreGet } from '../../database/queries/titres'
import { CaminoApiError, ITitreDemarche } from '../../types'
import { EffectDbQueryAndValidateErrors } from '../../pg-database'
import { filterOrFailFromValidWithError } from '../../tools/fp-tools'
import { DBTitre } from '../../database/models/titres'
import { MachineInfo } from 'camino-common/src/machines'
import { createDemarche, CreateDemarcheErrors } from './titre-demande.queries'

const canReadDemarcheError = 'impossible de savoir si on peut lire la démarche' as const

const droitsInsuffisantPourLireDemarche = 'droits insuffisants pour lire la démarche' as const
type GetDemarcheByIdOrSlugApiErrors =
  | typeof canReadDemarcheError
  | GetDemarcheByIdOrSlugErrors
  | typeof droitsInsuffisantPourLireDemarche
  | GetTitreByIdOrSlugErrors
  | GetFirstEtapeDateByDemarcheIdOrSlugErrors
export const getDemarcheByIdOrSlugApi: RestNewGetCall<'/rest/demarches/:demarcheIdOrSlug'> = (rootPipe): Effect.Effect<GetDemarcheByIdOrSlug, CaminoApiError<GetDemarcheByIdOrSlugApiErrors>> =>
  rootPipe.pipe(
    Effect.bind('demarche', ({ pool, params: { demarcheIdOrSlug } }) => getDemarcheByIdOrSlug(pool, demarcheIdOrSlug)),
    Effect.bind('titre', ({ pool, demarche }) => getTitreByIdOrSlug(pool, demarche.titre_id)),
    Effect.let('administrationsLocales', ({ pool, demarche }) => memoize(() => getAdministrationsLocalesByTitreId(pool, demarche.titre_id))),
    Effect.bind('canReadDemarche', ({ demarche, titre, administrationsLocales, pool, user }) =>
      Effect.tryPromise({
        try: () =>
          canReadDemarche(
            { ...demarche, titre_public_lecture: titre.public_lecture },
            user,
            memoize(() => Promise.resolve(titre.titre_type_id)),
            administrationsLocales,
            memoize(() => getTitulairesAmodiatairesByTitreId(pool, demarche.titre_id))
          ),

        catch: e => ({ message: canReadDemarcheError, extra: e }),
      })
    ),
    Effect.filterOrFail(
      ({ canReadDemarche }) => canReadDemarche,
      () => ({ message: droitsInsuffisantPourLireDemarche })
    ),
    Effect.bind('firstEtapeDate', ({ params: { demarcheIdOrSlug }, pool }) => getFirstEtapeDateByDemarcheIdOrSlug(demarcheIdOrSlug, pool)),
    Effect.map(({ demarche, firstEtapeDate }) => ({ ...demarche, first_etape_date: firstEtapeDate })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr(
          'Impossible de trouver la date de la première étape',
          "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',
          'impossible de savoir si on peut lire la démarche',
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.whenOr("La démarche n'existe pas", 'titre non trouvé en base', () => ({ ...caminoError, status: HTTP_STATUS.BAD_REQUEST })),
        Match.whenOr('droits insuffisants pour lire la démarche', () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
        Match.exhaustive
      )
    )
  )

export const demarcheSupprimer =
  (pool: Pool) =>
  async (req: CaminoRequest, res: CustomResponse<void>): Promise<void> => {
    try {
      const demarcheIdOrSlugParsed = demarcheIdOrSlugValidator.safeParse(req.params.demarcheIdOrSlug)
      if (!demarcheIdOrSlugParsed.success) {
        res.status(HTTP_STATUS.BAD_REQUEST)

        return
      }

      const user = req.auth
      const idOrSlug = demarcheIdOrSlugParsed.data
      const demarcheOld = await titreDemarcheGet(idOrSlug, { fields: { titre: { pointsEtape: { id: {} } }, etapes: { id: {} } } }, userSuper)
      if (isNullOrUndefined(demarcheOld)) {
        res.sendStatus(HTTP_STATUS.BAD_REQUEST)

        return
      }

      const etapes = demarcheOld.etapes
      if (isNullOrUndefined(etapes)) throw new Error('les étapes ne sont pas chargées')
      if (isNullOrUndefined(demarcheOld.titre)) throw new Error("le titre n'existe pas")
      if (isNullOrUndefined(demarcheOld.titre.administrationsLocales)) throw new Error('les administrations locales ne sont pas chargées')

      if (!canDeleteDemarche(user, demarcheOld.titre.typeId, demarcheOld.titre.titreStatutId, demarcheOld.titre.administrationsLocales, { etapes })) {
        res.sendStatus(HTTP_STATUS.FORBIDDEN)

        return
      }

      await titreDemarcheArchive(demarcheOld.id)

      await titreDemarcheUpdateTask(pool, null, demarcheOld.titreId)
      res.sendStatus(HTTP_STATUS.NO_CONTENT)
    } catch (e) {
      console.error(e)

      res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR)
    }
  }

const impossibleDeRecupererLeTitre = 'impossible de récupérer le titre' as const
const titreInexistant = "le titre n'existe pas" as const

const administrationsNonChargees = 'Les administrations ne sont pas chargées' as const
const droitsInsuffisantsPourCreerLaDemarche = 'Droits insuffisants pour créer la démarche' as const
const impossibleDeCreerLaDemarche = 'Impossible de créer la démarche' as const
const erreurLorsDeLaMiseAJourDesTaches = 'Une erreur est survenue lors de la mise à jour des taches de la démarche' as const
const impossibleDeRecupererLaDemarcheCree = 'Impossible de récupérer la démarche crée' as const
type DemarcheCreerErrors =
  | typeof impossibleDeRecupererLeTitre
  | typeof titreInexistant
  | typeof administrationsNonChargees
  | typeof droitsInsuffisantsPourCreerLaDemarche
  | typeof impossibleDeCreerLaDemarche
  | typeof erreurLorsDeLaMiseAJourDesTaches
  | typeof impossibleDeRecupererLaDemarcheCree
  | EffectDbQueryAndValidateErrors
  | CreateDemarcheErrors

export const demarcheCreer: RestNewPostCall<'/rest/demarches'> = (rootPipe): Effect.Effect<DemarcheCreationOutput, CaminoApiError<DemarcheCreerErrors>> => {
  return rootPipe.pipe(
    Effect.bind('titre', ({ user, body: demarche }) =>
      Effect.tryPromise({
        try: () => titreGet(demarche.titreId, { fields: { pointsEtape: { id: {} } } }, user),
        catch: error => ({ message: impossibleDeRecupererLeTitre, extra: error }),
      }).pipe(
        Effect.filterOrFail(
          (titre): titre is NonNullable<DBTitre> => isNotNullNorUndefined(titre),
          () => ({ message: titreInexistant })
        ),
        Effect.filterOrFail(
          titre => isNotNullNorUndefined(titre.administrationsLocales),
          () => ({ message: administrationsNonChargees })
        )
      )
    ),
    Effect.bind('demarches', ({ pool, body: demarche }) => getDemarchesByTitreId(pool, demarche.titreId)),
    Effect.filterOrFail(
      ({ body: demarche, user, titre, demarches }) =>
        (isTravaux(demarche.typeId) && canCreateTravaux(user, titre.typeId, titre.administrationsLocales ?? [], demarches)) ||
        (!isTravaux(demarche.typeId) && canCreateDemarche(user, titre.typeId, titre.titreStatutId, titre.administrationsLocales ?? [], demarches)),
      () => ({ message: droitsInsuffisantsPourCreerLaDemarche })
    ),
    Effect.bind('demarcheId', ({ body, pool, titre }) => createDemarche(pool, titre.id, titre.typeId, body.typeId, body.description)),
    Effect.tap(({ pool, demarcheId, titre }) =>
      Effect.tryPromise({
        try: () => titreDemarcheUpdateTask(pool, demarcheId, titre.id),
        catch: error => ({ message: erreurLorsDeLaMiseAJourDesTaches, extra: error }),
      })
    ),
    Effect.bind('demarcheUpdate', ({ demarcheId, user }) =>
      Effect.tryPromise({
        try: () => titreDemarcheGet(demarcheId, { fields: { id: {} } }, user),
        catch: error => ({ message: impossibleDeRecupererLaDemarcheCree, extra: error }),
      }).pipe(
        Effect.filterOrFail(
          (demarcheUpdate): demarcheUpdate is NonNullable<ITitreDemarche & { slug: DemarcheSlug }> => isNotNullNorUndefined(demarcheUpdate?.slug),
          () => ({ message: impossibleDeRecupererLaDemarcheCree })
        )
      )
    ),
    Effect.map(({ demarcheUpdate }) => ({ slug: demarcheUpdate.slug })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr(
          'impossible de récupérer le titre',
          "Impossible d'exécuter la requête dans la base de données",
          'Impossible de récupérer la démarche crée',
          'Les administrations ne sont pas chargées',
          'Les données en base ne correspondent pas à ce qui est attendu',
          'Une erreur est survenue lors de la mise à jour des taches de la démarche',
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.whenOr('Droits insuffisants pour créer la démarche', () => ({ ...caminoError, status: HTTP_STATUS.BAD_REQUEST })),
        Match.whenOr("le titre n'existe pas", () => ({ ...caminoError, status: HTTP_STATUS.NOT_FOUND })),
        Match.exhaustive
      )
    )
  )
}

export const getDemarchesEnConcurrence: RestNewGetCall<'/rest/demarches/:demarcheId/miseEnConcurrence'> = (
  rootPipe
): Effect.Effect<GetDemarcheMiseEnConcurrence[], CaminoApiError<EffectDbQueryAndValidateErrors>> => {
  return rootPipe.pipe(
    Effect.flatMap(({ pool, params, user }) => getDemarchesEnConcurrenceQuery(pool, params.demarcheId, user)),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        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
      )
    )
  )
}

const incoherentMachineInfo = 'Machine associée à la démarche incohérente' as const
const insufficientPermission = 'droits insuffisants' as const
type GetResultatEnConcurrenceErrors =
  | EffectDbQueryAndValidateErrors
  | GetDemarcheByIdOrSlugErrors
  | typeof insufficientPermission
  | typeof incoherentMachineInfo
  | GetFirstEtapeDateByDemarcheIdOrSlugErrors
export const getResultatEnConcurrence: RestNewGetCall<'/rest/demarches/:demarcheId/resultatMiseEnConcurrence'> = (
  rootPipe
): Effect.Effect<GetResultatMiseEnConcurrence, CaminoApiError<GetResultatEnConcurrenceErrors>> => {
  return rootPipe.pipe(
    Effect.bind('etapes', ({ pool, params }) => getEtapesByDemarcheId(pool, params.demarcheId)),
    Effect.bind('demarche', ({ pool, params }) => getDemarcheByIdOrSlug(pool, params.demarcheId)),
    Effect.bind('machineInfo', ({ demarche }) => {
      const machineInfo = MachineInfo.withMachineId(demarche.titre_type_id, demarche.demarche_type_id, demarche.demarche_id, demarche.machine_id)
      if (machineInfo.valid) {
        return Effect.succeed(machineInfo.value)
      }

      return Effect.fail({ message: incoherentMachineInfo, extra: machineInfo.error })
    }),
    Effect.tap(({ etapes, machineInfo, user }) => filterOrFailFromValidWithError(canPublishResultatMiseEnConcurrence(user, machineInfo, etapes), 'droits insuffisants' as const)),
    Effect.flatMap(({ pool, params, user }) => getDemarchePivotEnConcurrence(pool, params.demarcheId, user)),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr("La démarche n'existe pas", 'Impossible de trouver la date de la première étape', () => ({ ...caminoError, status: HTTP_STATUS.BAD_REQUEST })),
        Match.whenOr("Impossible d'exécuter la requête dans la base de données", 'Machine associée à la démarche incohérente', 'Les données en base ne correspondent pas à ce qui est attendu', () => ({
          ...caminoError,
          status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
        })),
        Match.when('droits insuffisants', () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
        Match.exhaustive
      )
    )
  )
}
