import { ITitreEtape, TitreEtapesTravauxTypes as Travaux } from '../../types'

import { titreEtapesSortDescByOrdre } from '../utils/titre-etapes-sort'
import { titreEtapePublicationCheck } from './titre-etape-publication-check'
import { TitreEtapeForMachine, toMachineEtapes } from '../rules-demarches/machine-common'
import { DemarcheStatutId, DemarchesStatutsIds } from 'camino-common/src/static/demarchesStatuts'
import { TITRES_TYPES_IDS, TitreTypeId } from 'camino-common/src/static/titresTypes'
import { DemarcheTypeId, TravauxIds } from 'camino-common/src/static/demarchesTypes'
import { ETAPES_TYPES, EtapeTypeId } from 'camino-common/src/static/etapesTypes'
import { ETAPES_STATUTS, isEtapeStatusRejete } from 'camino-common/src/static/etapesStatuts'
import { exhaustiveCheck, isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, RecordPartial } from 'camino-common/src/typescript-tools'
import { MachineInfo } from 'camino-common/src/machines'
import { ApiMachineInfo } from '../rules-demarches/machines'

const titreEtapesDecisivesCommunesTypes = ['css', 'abd', 'and'] as const satisfies EtapeTypeId[]

const titreEtapesDecisivesDemandesTypes = [
  'mfr',
  'men',
  'meo',
  'des',
  'mcp',
  'mcr',
  'dex',
  'aca',
  'def',
  'sco',
  'aco',
  'apu',
  'rpu',
  'dpu',
  'ihi',
  ...titreEtapesDecisivesCommunesTypes,
] as const satisfies EtapeTypeId[]

const titreDemarchesDemandesTypes = ['oct', 'pro', 'pr1', 'pr2', 'pre', 'rec', 'rep', 'fus', 'exp', 'exs', 'mut', 'vut', 'amo', 'res', 'ces', 'vct'] as const satisfies DemarcheTypeId[]

const titreDemarchesTravauxTypes = ['aom', 'dam', 'dot'] as const satisfies readonly TravauxIds[]

const titreEtapesDecisivesUnilateralesTypes = ['spp', 'dpu', 'dex', 'aco', ...titreEtapesDecisivesCommunesTypes] as const satisfies EtapeTypeId[]

const titreDemarchesUnilateralesTypes = ['ret', 'prr', 'dec'] as const satisfies DemarcheTypeId[]

const titresDemarcheCommunesStatutIdFind = (titreEtapeRecent: Pick<ITitreEtape, 'typeId' | 'statutId'>): DemarcheStatutId | null => {
  //  - le type de l’étape est classement sans suite (css)
  //  - le titre est une ARM
  //    - et le type de l’étape est avis de la commission ARM (aca)
  //    - ou le type de l’étape est recevabilité de la demande (mcr) (historique)
  //    - et le statut de l’étape est défavorable (def)
  if (titreEtapeRecent.typeId === 'css') {
    //  - le statut de la démarche est classé sans suite (cls)
    return DemarchesStatutsIds.ClasseSansSuite
  }

  //  - le type de l’étape est abrogation de la décision (abd)
  if (titreEtapeRecent.typeId === 'abd') {
    //  - le statut de la démarche repasse en “instruction”
    return DemarchesStatutsIds.EnInstruction
  }

  //  - le type de l’étape est annulation de la décision (and)
  if (titreEtapeRecent.typeId === 'and') {
    //  - si le statut est fait
    //  - alors, le statut de la démarche repasse en “instruction”
    //  - sinon, le statut de la démarche a celui l'étape (accepté ou rejeté)
    switch (titreEtapeRecent.statutId) {
      case 'fai':
        return DemarchesStatutsIds.EnInstruction
      case 'acc':
        return DemarchesStatutsIds.Accepte
      case 'rej':
      case 'rei':
        return DemarchesStatutsIds.Rejete
    }
  }

  return null
}

const titreDemarcheUnilateralStatutIdFind = (titreDemarcheEtapes: Pick<ITitreEtape, 'typeId' | 'ordre' | 'statutId'>[]): DemarcheStatutId => {
  // filtre les types d'étapes qui ont un impact
  // sur le statut de la démarche de demande
  const titreEtapesDecisivesUnilaterale = titreDemarcheEtapes.filter(titreEtape => titreEtapesDecisivesUnilateralesTypes.includes(titreEtape.typeId))

  // si aucune étape décisive n'est présente dans la démarche
  // le statut est indéterminé
  if (!titreEtapesDecisivesUnilaterale.length) return DemarchesStatutsIds.Indetermine

  // l'étape la plus récente
  const titreEtapeRecent = titreEtapesSortDescByOrdre(titreEtapesDecisivesUnilaterale)[0]

  // calcule le statut de démarche pour les étapes communes
  const statutId = titresDemarcheCommunesStatutIdFind(titreEtapeRecent)
  if (statutId) return statutId

  if ([ETAPES_TYPES.publicationDeDecisionAuJORF, ETAPES_TYPES.decisionDeLAutoriteAdministrative].includes(titreEtapeRecent.typeId)) {
    // - le statut de la démarche est terminé
    return DemarchesStatutsIds.Termine
  }

  // - le type de l’étape est saisine du préfet
  if (titreEtapeRecent.typeId === 'spp') {
    //  - le statut de la démarche est “en instruction”
    return DemarchesStatutsIds.EnInstruction
  }

  // - le type de l’étape est avenant à l’autorisation de recherche minière
  if (titreEtapeRecent.typeId === 'aco') {
    // - le statut de la démarche est "terminé"
    return DemarchesStatutsIds.Termine
  }

  // - si il y a plusieurs étapes
  if (titreDemarcheEtapes.length > 1) {
    // - le statut de la démarche est "en instruction"
    return DemarchesStatutsIds.EnInstruction
  }

  // - sinon, le type de l’étape est initiation de la démarche
  // alors, le statut de la démarche est “initié”
  return DemarchesStatutsIds.Initie
}

const titreDemarcheDemandeStatutIdFind = (titreDemarcheEtapes: Pick<ITitreEtape, 'typeId' | 'ordre' | 'statutId'>[], titreTypeId: TitreTypeId): DemarcheStatutId => {
  // filtre les types d'étapes qui ont un impact
  // sur le statut de la démarche de demande
  const titreEtapesDecisivesDemande = titreDemarcheEtapes.filter(titreEtape => titreEtapesDecisivesDemandesTypes.includes(titreEtape.typeId))

  // si aucune étape décisive n'est présente dans la démarche
  // le statut est indéterminé
  if (!titreEtapesDecisivesDemande.length) return DemarchesStatutsIds.Indetermine

  // l'étape la plus récente
  const titreEtapeRecent = titreEtapesSortDescByOrdre(titreEtapesDecisivesDemande)[0]

  // calcule le statut de démarche pour les étapes communes
  const statutId = titresDemarcheCommunesStatutIdFind(titreEtapeRecent)

  if (statutId) return statutId

  //  - le type de l’étape est une publication
  //  - ou une décision implicite (dex avec statut rei ou aci)
  //  - ou des informations historiques incomplètes
  const titreEtapesPublication = titreDemarcheEtapes.filter(
    titreEtape =>
      titreEtapePublicationCheck(titreEtape.typeId, titreTypeId) ||
      [ETAPES_TYPES.informationsHistoriquesIncompletes].includes(titreEtape.typeId) ||
      (ETAPES_TYPES.decisionDeLAutoriteAdministrative === titreEtape.typeId && [ETAPES_STATUTS.ACCEPTE_DECISION_IMPLICITE, ETAPES_STATUTS.REJETE_DECISION_IMPLICITE].includes(titreEtape.statutId))
  )

  if (titreEtapesPublication.length) {
    // si l'étape de publication la plus récente est
    let titreEtapePublicationRecent = titreEtapesSortDescByOrdre(titreEtapesPublication)[0]

    // si l'étape est une publication au JORF, on cherche si il y'a une décision avant ou une abrogation qui pourrait changer le statut de la démarche
    if (titreEtapePublicationRecent.typeId === ETAPES_TYPES.publicationDeDecisionAuJORF) {
      const previousEtape = titreDemarcheEtapes.find(({ ordre }) => ordre === (titreEtapePublicationRecent.ordre ?? 0) - 1)
      if (isNotNullNorUndefined(previousEtape)) {
        if (previousEtape.typeId === ETAPES_TYPES.abrogationDeLaDecision) {
          const decisionAvantAbrogation = titreDemarcheEtapes.find(
            ({ ordre, typeId }) => typeId === ETAPES_TYPES.decisionDeLAutoriteAdministrative && (ordre === (previousEtape.ordre ?? 0) - 1 || ordre === (previousEtape.ordre ?? 0) - 2)
          )

          if (isNotNullNorUndefined(decisionAvantAbrogation)) {
            switch (decisionAvantAbrogation.statutId) {
              case 'fai':
              case 'acc':
              case 'aci':
                return DemarchesStatutsIds.Rejete
              case 'rej':
              case 'rei':
                return DemarchesStatutsIds.Accepte
            }
          }
        }
        if (previousEtape.typeId === ETAPES_TYPES.decisionDeLAutoriteAdministrative) {
          titreEtapePublicationRecent = previousEtape
        }
      }
    }

    // si l'étape de publication est de type unilatérale
    // alors la démarche a le statut accepté
    // sinon la démarche a le statut de l'étape (accepté ou rejeté)
    switch (titreEtapePublicationRecent.statutId) {
      case 'fai':
      case 'acc':
      case 'aci':
        return DemarchesStatutsIds.Accepte
      case 'rej':
      case 'rei':
        return DemarchesStatutsIds.Rejete
      case 'def':
      case 'dre':
      case 'dep':
      case 'enc':
      case 'exe':
      case 'req':
      case 'com':
      case 'inc':
      case 'fav':
      case 'pro':
      case 'ter':
      case 'fre':
      case 'nul':
      case 'ajo':
        return DemarchesStatutsIds.Indetermine
      default:
        exhaustiveCheck(titreEtapePublicationRecent.statutId)
    }
  }

  //  - le type de l’étape est décision expresse (dex)
  //  - et le statut de l’étape est rejeté (rej)
  if (titreEtapeRecent.typeId === ETAPES_TYPES.decisionDeLAutoriteAdministrative && isEtapeStatusRejete(titreEtapeRecent.statutId)) {
    //  - le statut de la démarche est rejeté (rej)
    return DemarchesStatutsIds.Rejete
  }

  //  - le type de l’étape est désistement du demandeur (des)
  if (titreEtapeRecent.typeId === DemarchesStatutsIds.Desiste) {
    //  - le statut de la démarche est “désisté”
    return DemarchesStatutsIds.Desiste
  }

  //  - le type de l’étape est rejeté (rej)
  //  - le titre est une ARM
  //    - et le type de l’étape est avis de la commission ARM (aca)
  //    - et le statut de l’étape est défavorable (def)
  if (titreTypeId === 'arm' && titreEtapeRecent.typeId === 'aca' && titreEtapeRecent.statutId === 'def') {
    return DemarchesStatutsIds.Rejete
  }

  //  - le type de l’étape est recevabilité de la demande (mcr)
  //  - le type de l’étape est enregistrement de la demande (men)
  //  - le type de l’étape est décision explicite (dex)
  //  - la date de l'étape est inférieure à la date du jour
  //  OU
  //  - le type de l’étape est avis de la commission ARM (aca) (non défavorable)
  //  - le type de l’étape est décision de l'ONF (def) (non défavorable)
  if (
    [ETAPES_TYPES.recevabiliteDeLaDemande, ETAPES_TYPES.decisionDeLAutoriteAdministrative].includes(titreEtapeRecent.typeId) ||
    (titreTypeId === TITRES_TYPES_IDS.AUTORISATION_DE_RECHERCHE_METAUX &&
      [
        ETAPES_TYPES.enregistrementDeLaDemande,
        ETAPES_TYPES.priseEnChargeParLOfficeNationalDesForets,
        ETAPES_TYPES.completudeDeLaDemande,
        ETAPES_TYPES.decisionDeLOfficeNationalDesForets,
        ETAPES_TYPES.avisDeLaCommissionDesAutorisationsDeRecherchesMinieres_CARM_,
      ].includes(titreEtapeRecent.typeId))
  ) {
    //  - le statut de la démarche est “en instruction”
    return DemarchesStatutsIds.EnInstruction
  }

  //  - le type de l’étape est enregistrement de la demande (men)
  //  - il n’y a pas d’étape après
  if (titreEtapeRecent.typeId === ETAPES_TYPES.enregistrementDeLaDemande) {
    //  - le statut de la démarche est “déposé”
    return DemarchesStatutsIds.Depose
  }

  //  - le type de l’étape est formalisation de la demande (mfr)
  if (titreEtapeRecent.typeId === 'mfr') {
    //  - le statut de la démarche est “en construction”
    return DemarchesStatutsIds.EnConstruction
  }

  // sinon le statut de la démarche est indéterminé
  return DemarchesStatutsIds.Indetermine
}

const statuts: RecordPartial<EtapeTypeId, DemarcheStatutId> = {
  [Travaux.DemandeAutorisationOuverture]: DemarchesStatutsIds.Depose,
  [Travaux.DeclarationOuverture]: DemarchesStatutsIds.Depose,
  [Travaux.DeclarationArret]: DemarchesStatutsIds.Depose,
  [Travaux.DepotDemande]: DemarchesStatutsIds.Depose,
  [Travaux.DemandeComplements]: DemarchesStatutsIds.EnInstruction,
  [Travaux.ReceptionComplements]: DemarchesStatutsIds.EnInstruction,
  [Travaux.RecevabiliteDeLaDemande]: DemarchesStatutsIds.EnInstruction,
  [Travaux.AvisReception]: DemarchesStatutsIds.EnInstruction,
  [Travaux.SaisineAutoriteEnvironmentale]: DemarchesStatutsIds.EnInstruction,
  [Travaux.AvisAutoriteEnvironmentale]: DemarchesStatutsIds.EnInstruction,
  [Travaux.ArretePrefectoralSursis]: DemarchesStatutsIds.EnInstruction,
  [Travaux.AvisDesServicesEtCommissionsConsultatives]: DemarchesStatutsIds.EnInstruction,
  [Travaux.AvisRapportDirecteurREAL]: DemarchesStatutsIds.EnInstruction,
  [Travaux.TransPrescriptionsDemandeur]: DemarchesStatutsIds.EnInstruction,
  [Travaux.EnquetePublique]: DemarchesStatutsIds.EnInstruction,
  [Travaux.AvisPrescriptionsDemandeur]: DemarchesStatutsIds.EnInstruction,
  [Travaux.ArretePrescriptionComplementaire]: DemarchesStatutsIds.EnInstruction,
  [Travaux.ArretePrefectDonneActe1]: DemarchesStatutsIds.EnInstruction,
  [Travaux.MemoireFinTravaux]: DemarchesStatutsIds.EnInstruction,
  [Travaux.Recolement]: DemarchesStatutsIds.EnInstruction,
  [Travaux.ArreteOuvertureTravauxMiniers]: DemarchesStatutsIds.Accepte,
  [Travaux.DonneActeDeclaration]: DemarchesStatutsIds.Accepte,
  [Travaux.DesistementDuDemandeur]: DemarchesStatutsIds.Desiste,
  [Travaux.ArretePrefectDonneActe2]: DemarchesStatutsIds.FinPoliceMines,
  [Travaux.PorterAConnaissance]: DemarchesStatutsIds.FinPoliceMines,
  [Travaux.DecisionAdmin]: DemarchesStatutsIds.Indetermine,
  [Travaux.PubliDecisionRecueilActesAdmin]: DemarchesStatutsIds.Indetermine,
} as const satisfies { [key in (typeof Travaux)[keyof typeof Travaux]]: DemarcheStatutId }

const titreDemarcheTravauxStatutIdFind = (titreDemarcheEtapes: Pick<ITitreEtape, 'ordre' | 'typeId'>[], demarcheTypeId: string): DemarcheStatutId => {
  if (titreDemarcheEtapes.length === 0) {
    return DemarchesStatutsIds.Indetermine
  }
  const titreEtapesRecent = titreEtapesSortDescByOrdre(titreDemarcheEtapes)[0]

  if (titreEtapesRecent.typeId === Travaux.PubliDecisionRecueilActesAdmin) {
    switch (demarcheTypeId) {
      case 'aom':
        return DemarchesStatutsIds.Accepte
      case 'dam':
        return DemarchesStatutsIds.FinPoliceMines
    }
  }

  return statuts[titreEtapesRecent.typeId] ?? DemarchesStatutsIds.Indetermine
}

export const titreDemarcheStatutIdFind = (machineInfo: MachineInfo, titreDemarcheEtapes: TitreEtapeForMachine[]): DemarcheStatutId => {
  // si la démarche ne contient pas d'étapes
  // -> le statut est indétrminé
  if (!isNotNullNorUndefinedNorEmpty(titreDemarcheEtapes)) return DemarchesStatutsIds.Indetermine

  // si la démarche est pour des travaux
  if (titreDemarchesTravauxTypes.includes(machineInfo.demarcheTypeId)) {
    return titreDemarcheTravauxStatutIdFind(titreDemarcheEtapes, machineInfo.demarcheTypeId)
  }

  const machine = new ApiMachineInfo(machineInfo)

  if (isNotNullNorUndefined(machine.machine)) {
    return machine.machine.demarcheStatut(toMachineEtapes(titreDemarcheEtapes)).demarcheStatut
  }

  //  si la démarche fait l’objet d’une demande
  // (son type est :
  //  - octroi ou prolongation(1, 2 ou exceptionnelle)
  //  - renonciation ou fusion (native ou virtuelle) ou extension du périmètre
  //  - extension de substance ou mutation (native ou virtuelle) ou amodiation
  //  - résiliation d’amodiation ou déplacement de périmètre)
  if (titreDemarchesDemandesTypes.includes(machineInfo.demarcheTypeId)) {
    return titreDemarcheDemandeStatutIdFind(titreDemarcheEtapes, machineInfo.titreTypeId)
  }

  //  si la démarche ne fait pas l’objet d’une demande (unilatérale)
  //  (son type est retrait ou abrogation ou prorogation)
  else if (titreDemarchesUnilateralesTypes.includes(machineInfo.demarcheTypeId)) {
    return titreDemarcheUnilateralStatutIdFind(titreDemarcheEtapes)
  }

  //  sinon, le statut est indéterminé
  return DemarchesStatutsIds.Indetermine
}
