import { Pool } from 'pg'
import { Effect, pipe } from 'effect'
import { CaminoError } from 'camino-common/src/zod-tools'
import { DemarcheEtapeFondamentale, DemarcheId } from 'camino-common/src/demarche'
import { isEtapeComplete } from 'camino-common/src/permissions/titres-etapes'
import { userSuper } from '../database/user-super'
import { FirstEtapeDate } from 'camino-common/src/date'
import { titreEtapeGet } from '../database/queries/titres-etapes'
import { isNotNullNorUndefined, memoize, toSorted } from 'camino-common/src/typescript-tools'
import { ApiFlattenEtape, iTitreEtapeToFlattenEtape } from '../api/_format/titres-etapes'
import { ITitreEtape } from '../types'
import {
  EtapeAvisDb,
  getDocumentsByEtapeId,
  getEntrepriseDocumentIdsByEtapeId,
  getEtapeAvisLargeObjectIdsByEtapeId,
  insertEtapeAvis,
  insertEtapeDocumentsRawDontUseOutsideScript,
} from '../database/queries/titres-etapes.queries'
import { getAdministrationsLocales } from 'camino-common/src/administrations'
import { TitreId, TitreSlug } from 'camino-common/src/validators/titres'
import { ETAPE_IS_BROUILLON, EtapeDocument, TempEtapeAvis } from 'camino-common/src/etape'
import { getTitre, getTitres } from '../api/rest/titres.queries'
import { getDocuments } from 'camino-common/src/static/titresTypes_demarchesTypes_etapesTypes/documents'
import { isArmMecanise } from 'camino-common/src/static/mecanise'
import { DocumentType, isAutreDocument } from 'camino-common/src/static/documentsTypes'
import { getMostRecentValuePropFromEtapeFondamentaleValide, TitreGetDemarche } from 'camino-common/src/titres'
import { isDemarcheStatutNonStatue, isDemarcheStatutNonValide } from 'camino-common/src/static/demarchesStatuts'
import { getAvisTypes } from 'camino-common/src/avisTypes'
import { AvisRegularTypeId, isAvisRegularTypeId } from 'camino-common/src/static/avisTypes'
import { MachineInfo } from 'camino-common/src/machines'
import { demarcheEnregistrementDemandeDateFind } from './rules-demarches/machines'

type CheckedProlongation = {
  titre_slug: TitreSlug
  demarche_id: DemarcheId
  etapeFull: ITitreEtape
  flattenEtape: ApiFlattenEtape
  etapeDocuments: EtapeDocument[]
  etapeAvis: EtapeAvisDb[]
  propsHeritees: {
    substances: DemarcheEtapeFondamentale['fondamentale']['substances']
    titulaireIds: DemarcheEtapeFondamentale['fondamentale']['titulaireIds']
    perimetre: DemarcheEtapeFondamentale['fondamentale']['perimetre']
  }
  firstEtapeDate: FirstEtapeDate
  titre_id: TitreId
  errors: string[]
}

const titreEtapeGetError = 'Le fetch de la firstEtape a échoué' as const

const checkDemarcheEtapes = (pool: Pool, titreId: TitreId): Effect.Effect<CheckedProlongation[], CaminoError<string>> => {
  return Effect.Do.pipe(
    Effect.bind('titre', () =>
      Effect.Do.pipe(
        Effect.flatMap(() =>
          Effect.tryPromise({
            try: () => getTitre(pool, userSuper, titreId),
            catch: error => ({ message: titreGetError, extra: error }),
          })
        ),
        Effect.filterOrFail(
          (titre): titre is NonNullable<typeof titre> => isNotNullNorUndefined(titre),
          error => ({ message: titreGetIsNullError, extra: error })
        )
      )
    ),
    Effect.flatMap(({ titre }) =>
      Effect.forEach(titre.demarches, demarche => {
        return Effect.Do.pipe(
          Effect.bind('firstEtapeDate', () =>
            Effect.Do.pipe(
              Effect.map(() => demarcheEnregistrementDemandeDateFind(demarche.etapes.map(etape => ({ ...etape, typeId: etape.etape_type_id })))),
              Effect.filterOrFail(
                date => isNotNullNorUndefined(date),
                () => ({ message: 'FirstEtapeDate null' })
              )
            )
          ),
          Effect.bind('propsHeritees', () =>
            Effect.Do.pipe(
              Effect.flatMap(() => {
                const demarches: TitreGetDemarche[] = []
                for (const demarcheSorted of toSorted([...titre.demarches], (a, b) => a.ordre - b.ordre)) {
                  if (demarcheSorted.id === demarche.id) {
                    return Effect.succeed([...demarches, demarche])
                  } else if (!isDemarcheStatutNonStatue(demarcheSorted.demarche_statut_id) && !isDemarcheStatutNonValide(demarcheSorted.demarche_statut_id)) {
                    demarches.push(demarcheSorted)
                  }
                }

                return Effect.fail({ message: 'Pas de démarches' })
              }),
              Effect.map(demarches => {
                return {
                  substances: getMostRecentValuePropFromEtapeFondamentaleValide('substances', demarches),
                  titulaireIds: getMostRecentValuePropFromEtapeFondamentaleValide('titulaireIds', demarches),
                  perimetre: getMostRecentValuePropFromEtapeFondamentaleValide('perimetre', demarches),
                }
              })
            )
          ),

          Effect.let('titreProps', ({ propsHeritees }) => ({
            titreTypeId: memoize(() => Promise.resolve(titre.titre_type_id)),
            administrationsLocales: memoize(() =>
              Promise.resolve(getAdministrationsLocales(propsHeritees.perimetre?.communes.map(({ id }) => id) ?? [], propsHeritees.perimetre?.secteurs_maritimes ?? []))
            ),
            entreprisesTitulairesOuAmodiataires: memoize(() => Promise.resolve(propsHeritees.titulaireIds ?? [])),
          })),

          Effect.flatMap(({ firstEtapeDate, titreProps, propsHeritees }) =>
            Effect.forEach(demarche.etapes, etape => {
              return Effect.Do.pipe(
                Effect.bind('etapeFull', () =>
                  Effect.tryPromise({
                    try: () => titreEtapeGet(etape.id, { fields: { id: {} }, fetchHeritage: true }, userSuper),
                    catch: error => ({ message: titreEtapeGetError, extra: error }),
                  }).pipe(
                    Effect.filterOrFail(
                      (firstEtape): firstEtape is ITitreEtape => isNotNullNorUndefined(firstEtape),
                      () => ({ message: titreEtapeGetError, extra: `étape ${etape.id} est null ou undefined` })
                    )
                  )
                ),

                Effect.bind('etapeDocuments', ({ etapeFull }) => {
                  return getDocumentsByEtapeId(
                    etapeFull.id,
                    pool,
                    userSuper,
                    titreProps.titreTypeId,
                    titreProps.administrationsLocales,
                    titreProps.entreprisesTitulairesOuAmodiataires,
                    etapeFull.typeId,
                    {
                      demarche_type_id: demarche.demarche_type_id,
                      entreprises_lecture: true,
                      public_lecture: true,
                      titre_public_lecture: true,
                    }
                  )
                }),
                Effect.bind('entrepriseDocuments', ({ etapeFull }) => getEntrepriseDocumentIdsByEtapeId({ titre_etape_id: etapeFull.id }, pool, userSuper)),
                Effect.bind('etapeAvis', ({ etapeFull }) =>
                  getEtapeAvisLargeObjectIdsByEtapeId(
                    etapeFull.id,
                    pool,
                    userSuper,
                    titreProps.titreTypeId,
                    titreProps.administrationsLocales,
                    titreProps.entreprisesTitulairesOuAmodiataires,
                    etapeFull.typeId,
                    {
                      demarche_type_id: demarche.demarche_type_id,
                      entreprises_lecture: true,
                      public_lecture: true,
                      titre_public_lecture: true,
                    }
                  )
                ),
                Effect.bind('machineInfo', () => {
                  const machineInfo = MachineInfo.withMachineId(titre.titre_type_id, demarche.demarche_type_id, demarche.id, demarche.machine_id)
                  if (!machineInfo.valid) {
                    return Effect.fail({ message: 'Machine associée à la démarche incohérente', extra: machineInfo.error })
                  }

                  return Effect.succeed(machineInfo.value)
                }),
                Effect.bind('flattenEtape', ({ etapeFull, machineInfo }) => iTitreEtapeToFlattenEtape(machineInfo, etapeFull)),

                Effect.map(({ etapeFull, flattenEtape, etapeDocuments, entrepriseDocuments, etapeAvis }) => {
                  if (flattenEtape.isBrouillon === ETAPE_IS_BROUILLON) {
                    return null
                  }
                  const isFirstEtapeComplete = isEtapeComplete(
                    flattenEtape,
                    MachineInfo.withDate(titre.titre_type_id, demarche.demarche_type_id, demarche.id, firstEtapeDate),
                    etapeDocuments,
                    entrepriseDocuments,
                    etapeFull.sdomZones,
                    isNotNullNorUndefined(etapeFull.communes) ? etapeFull.communes.map(({ id }) => id) : [],
                    etapeAvis,
                    userSuper
                  )
                  const result: CheckedProlongation = {
                    titre_slug: titre.slug,
                    demarche_id: demarche.id,
                    etapeFull,
                    flattenEtape: flattenEtape,
                    etapeDocuments,
                    firstEtapeDate,
                    titre_id: titreId,
                    propsHeritees,
                    etapeAvis,
                    errors: isFirstEtapeComplete.valid ? [] : isFirstEtapeComplete.errors,
                  }
                  return result
                }),
                Effect.catchAll(caminoError => {
                  console.error('Erreur étapes', caminoError)
                  return Effect.succeed(null)
                })
              )
            })
          ),
          Effect.catchAll(error => {
            console.info('Erreur démarche', error)
            return Effect.succeed([] as (CheckedProlongation | null)[])
          }),
          Effect.map(erreurs => erreurs.filter(isNotNullNorUndefined))
        )
      })
    ),
    Effect.map(etapesEnErreur => etapesEnErreur.flatMap(plop => plop))
  )
}

const titreGetError = 'Impossible de fetch le titre' as const
const titreGetIsNullError = 'Titre introuvable' as const
const etapeIntrouvableError = 'Étape introuvable dans le titre' as const

const addDocumentNonRenseigne = (pool: Pool, checkedProlongation: CheckedProlongation): Effect.Effect<CheckedProlongation, CaminoError<string>> => {
  return Effect.Do.pipe(
    Effect.bind('titre', () =>
      Effect.tryPromise({
        try: () => getTitre(pool, userSuper, checkedProlongation.titre_id),
        catch: error => ({ message: titreGetError, extra: error }),
      }).pipe(
        Effect.filterOrFail(
          (titre): titre is NonNullable<typeof titre> => isNotNullNorUndefined(titre),
          error => ({ message: titreGetIsNullError, extra: error })
        )
      )
    ),
    Effect.bind('demarche', ({ titre }) =>
      Effect.succeed(titre.demarches.find(({ id }) => id === checkedProlongation.demarche_id)).pipe(
        Effect.filterOrFail(
          (demarche): demarche is NonNullable<typeof demarche> => isNotNullNorUndefined(checkedProlongation.demarche_id) && isNotNullNorUndefined(demarche),
          () => ({ message: etapeIntrouvableError, extra: 'Démarche introuvable' })
        )
      )
    ),
    Effect.flatMap(({ titre, demarche }) => {
      const documentTypes = getDocuments(
        MachineInfo.withDate(titre.titre_type_id, demarche.demarche_type_id, demarche.id, checkedProlongation.firstEtapeDate),
        checkedProlongation.etapeFull.typeId,
        checkedProlongation.etapeFull.sdomZones ?? [],
        isArmMecanise(checkedProlongation.flattenEtape.contenu)
      )

      console.info(`Trying to fix documents for http://localhost:4180/etapes/${checkedProlongation.etapeFull.slug}`)

      const documentsObligatoiresManquants = documentTypes.filter(
        ({ optionnel, id }) => !optionnel && checkedProlongation.etapeDocuments.every(({ etape_document_type_id }) => etape_document_type_id !== id)
      )

      return insertEtapeDocumentsRawDontUseOutsideScript(
        pool,
        checkedProlongation.etapeFull.id,
        documentsObligatoiresManquants
          .filter((documentType): documentType is DocumentType => !isAutreDocument(documentType.id))
          .map(documentType => ({
            type: 'DESCRIPTION_OPTIONNELLE',
            description: null,
            entreprises_lecture: false,
            public_lecture: false,
            etape_document_type_id: documentType.id,
          }))
      )
    }),
    Effect.map(() => checkedProlongation),
    Effect.catchAll(error => {
      console.info(error)
      return Effect.succeed(checkedProlongation)
    })
  )
}

const addAvisNonRenseigne = (pool: Pool, checkedProlongation: CheckedProlongation): Effect.Effect<CheckedProlongation, CaminoError<string>> => {
  return Effect.Do.pipe(
    Effect.bind('titre', () =>
      Effect.tryPromise({
        try: () => getTitre(pool, userSuper, checkedProlongation.titre_id),
        catch: error => ({ message: titreGetError, extra: error }),
      }).pipe(
        Effect.filterOrFail(
          (titre): titre is NonNullable<typeof titre> => isNotNullNorUndefined(titre),
          error => ({ message: titreGetIsNullError, extra: error })
        )
      )
    ),
    Effect.bind('demarche', ({ titre }) =>
      Effect.succeed(titre.demarches.find(({ id }) => id === checkedProlongation.demarche_id)).pipe(
        Effect.filterOrFail(
          (demarche): demarche is NonNullable<typeof demarche> => isNotNullNorUndefined(checkedProlongation.demarche_id) && isNotNullNorUndefined(demarche),
          () => ({ message: etapeIntrouvableError, extra: 'Démarche introuvable' })
        )
      )
    ),
    Effect.flatMap(({ titre, demarche }) => {
      const avisTypes = getAvisTypes(
        checkedProlongation.etapeFull.typeId,
        MachineInfo.withDate(titre.titre_type_id, demarche.demarche_type_id, demarche.id, checkedProlongation.firstEtapeDate),
        (checkedProlongation.propsHeritees.perimetre?.communes ?? []).map(({ id }) => id),
        isArmMecanise(checkedProlongation.flattenEtape.contenu)
      )

      console.info(`Trying to fix avis for http://localhost:4180/etapes/${checkedProlongation.etapeFull.slug}`)

      const avisManquants = Object.values(avisTypes).filter(avisType => !avisType.optionnel && checkedProlongation.etapeAvis.every(avis => avis.avis_type_id !== avisType.id))

      return insertEtapeAvis(
        pool,
        checkedProlongation.etapeFull.id,
        avisManquants
          .map(({ id }) => id)
          .filter((avisId): avisId is AvisRegularTypeId => isAvisRegularTypeId(avisId))
          .map(avisManquant => {
            const result: TempEtapeAvis = {
              avis_type_id: avisManquant,
              description: '',
              date: checkedProlongation.etapeFull.date,
              avis_visibility_id: 'Administrations',
              avis_statut_id: 'Non renseigné',
            }
            return result
          })
      )
    }),
    Effect.map(() => checkedProlongation),
    Effect.catchAll(error => {
      console.info(error)
      return Effect.succeed(checkedProlongation)
    })
  )
}

const toPercentage = (percentage: number): number => {
  return Math.round(percentage * 100)
}

let lastProgression = 0
export const fillDocumentsNonRenseigne = (pool: Pool): Effect.Effect<CheckedProlongation[][], CaminoError<string>> => {
  return pipe(
    getTitres(pool),
    Effect.tap(() => console.info('titres chargées')),
    Effect.flatMap(titres => {
      return Effect.forEach(
        titres,
        (titre, index) => {
          const progression = toPercentage(index / titres.length)

          if (progression % 1 === 0 && lastProgression !== progression) {
            console.info(`progression ${progression} %`)
            lastProgression = progression
          }
          return pipe(
            checkDemarcheEtapes(pool, titre.id),
            Effect.flatMap(checkedEtapes =>
              Effect.forEach(checkedEtapes, checkedEtape => {
                return Effect.Do.pipe(
                  Effect.flatMap(() => {
                    if (checkedEtape.errors.some(error => error.startsWith('le document') && error.endsWith('est obligatoire'))) {
                      return addDocumentNonRenseigne(pool, checkedEtape)
                    }
                    return Effect.succeed(checkedEtape)
                  }),
                  Effect.flatMap(() => {
                    if (checkedEtape.errors.some(error => error.includes('Il manque des avis obligatoires'))) {
                      return addAvisNonRenseigne(pool, checkedEtape)
                    }
                    return Effect.succeed(checkedEtape)
                  })
                )
              })
            ),
            Effect.catchAll(error => {
              console.info(error)
              return Effect.succeed([])
            })
          )
        },
        { concurrency: 20 }
      )
    }),
    Effect.catchAll(error => {
      console.info(error)
      return Effect.succeed([])
    })
  )
}
