import { DemarcheId } from 'camino-common/src/demarche'
import { Pool } from 'pg'
import { pipe, Effect, Match } from 'effect'
import { GeoSystemes } from 'camino-common/src/static/geoSystemes'
import { HTTP_STATUS } from 'camino-common/src/http'
import {
  ConvertPointsErrors,
  GetGeojsonByGeoSystemeIdErrorMessages,
  GetGeojsonInformationErrorMessages,
  GetTitresIntersectionWithGeojson,
  convertPoints,
  getGeojsonByGeoSystemeId as getGeojsonByGeoSystemeIdQuery,
  getGeojsonInformation,
  getTitresIntersectionWithGeojson,
} from './perimetre.queries'
import { TitreTypeId } from 'camino-common/src/static/titresTypes'
import { getMostRecentEtapeFondamentaleValide } from './titre-heritage'
import { isAdministrationAdmin, isAdministrationEditeur, isDefault, isSuper, User } from 'camino-common/src/roles'
import { getDemarcheByIdOrSlug, GetDemarcheByIdOrSlugErrors, getEtapesByDemarcheId } from './demarches.queries'
import { getAdministrationsLocalesByTitreId, getTitreByIdOrSlug, GetTitreByIdOrSlugErrors, getTitulairesAmodiatairesByTitreId } from './titres.queries'
import { getEtapeById } from './etapes.queries'
import {
  FeatureCollectionPoints,
  FeatureMultiPolygon,
  GeojsonInformations,
  MultiPolygon,
  PerimetreInformations,
  featureCollectionPointsValidator,
  featureCollectionMultipolygonValidator,
  multiPolygonValidator,
  polygonValidator,
  featureCollectionPolygonValidator,
  GeojsonImportPointsResponse,
  GeojsonImportForagesResponse,
  featureCollectionForagesValidator,
  FeatureCollectionForages,
  featureForagePropertiesValidator,
} from 'camino-common/src/perimetre'
import { join } from 'node:path'
import { readFileSync } from 'node:fs'
import { parseShp } from 'shpjs'
import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined, memoize } from 'camino-common/src/typescript-tools'
import { SDOMZoneId } from 'camino-common/src/static/sdom'
import { TitreSlug } from 'camino-common/src/validators/titres'
import { canReadEtape } from './permissions/etapes'
import { EtapeTypeId } from 'camino-common/src/static/etapesTypes'
import xlsx from 'xlsx'
import { ZodTypeAny, z } from 'zod'
import { CommuneId } from 'camino-common/src/static/communes'
import { CaminoApiError } from '../../types'
import { DBNotFound, EffectDbQueryAndValidateErrors } from '../../pg-database'
import { ZodUnparseable, zodParseEffect, zodParseEffectCallback } from '../../tools/fp-tools'
import { CaminoError } from 'camino-common/src/zod-tools'
import { RestNewGetCall, RestNewPostCall } from '../../server/rest'

const userNotConnected = 'Il faut être connecté pour utiliser cette route' as const
type GetPerimetreInfosByEtapeErrors = EffectDbQueryAndValidateErrors | DBNotFound | typeof userNotConnected | GetPerimetreInfosInternalErrors
export const getPerimetreInfosByEtape: RestNewGetCall<'/rest/etapes/:etapeId/geojson'> = (rootPipe): Effect.Effect<PerimetreInformations, CaminoApiError<GetPerimetreInfosByEtapeErrors>> =>
  rootPipe.pipe(
    Effect.filterOrFail(
      ({ user }) => isNotNullNorUndefined(user),
      () => ({ message: userNotConnected })
    ),
    Effect.bind('etape', ({ pool, params }) =>
      getEtapeById(pool, params.etapeId).pipe(
        Effect.map(myEtape => {
          const etapeReworked: GetPerimetreInfosInternalEtape = {
            demarche_id: myEtape.demarche_id,
            geojson4326_perimetre: myEtape.geojson4326_perimetre,
            sdom_zones: myEtape.sdom_zones ?? [],
            etape_type_id: myEtape.etape_type_id,
            communes: myEtape.communes.map(({ id }) => id),
          }
          return etapeReworked
        })
      )
    ),
    Effect.flatMap(({ pool, user, etape }) => getPerimetreInfosInternal(pool, user, etape)),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr('Il faut être connecté pour utiliser cette route', "Droits insuffisant pour lire l'étape", () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
        Match.whenOr(
          'Élément non trouvé dans la base de données',
          'Les données en base ne correspondent pas à ce qui est attendu',
          "Impossible d'exécuter la requête dans la base de données",
          "La démarche n'existe pas",
          'titre non trouvé en base',
          () => ({
            ...caminoError,
            status: HTTP_STATUS.BAD_REQUEST,
          })
        ),
        Match.when("Une erreur est survenue lors de la gestion des droits de l'étape", () => ({
          ...caminoError,
          status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
        })),
        Match.exhaustive
      )
    )
  )

type GetPerimetreInfosByDemarcheErrors = EffectDbQueryAndValidateErrors | typeof userNotConnected | GetDemarcheByIdOrSlugErrors | GetPerimetreInfosInternalErrors
export const getPerimetreInfosByDemarche: RestNewGetCall<'/rest/demarches/:demarcheId/geojson'> = (rootPipe): Effect.Effect<PerimetreInformations, CaminoApiError<GetPerimetreInfosByDemarcheErrors>> =>
  rootPipe.pipe(
    Effect.filterOrFail(
      ({ user }) => isNotNullNorUndefined(user),
      () => ({ message: userNotConnected })
    ),
    Effect.bind('demarche', ({ pool, params }) => getDemarcheByIdOrSlug(pool, params.demarcheId)),
    Effect.bind('etapes', ({ pool, demarche }) => getEtapesByDemarcheId(pool, demarche.demarche_id)),
    Effect.let('mostRecentEtapeFondamentale', ({ etapes }) => getMostRecentEtapeFondamentaleValide([{ ordre: 1, etapes }])),
    Effect.let('etape', ({ mostRecentEtapeFondamentale, demarche }) => {
      if (isNullOrUndefined(mostRecentEtapeFondamentale)) {
        return null
      }
      const etapeReworked: GetPerimetreInfosInternalEtape = {
        demarche_id: demarche.demarche_id,
        geojson4326_perimetre: mostRecentEtapeFondamentale.geojson4326_perimetre,
        sdom_zones: mostRecentEtapeFondamentale.sdom_zones ?? [],
        etape_type_id: mostRecentEtapeFondamentale.etape_type_id,
        communes: mostRecentEtapeFondamentale.communes.map(({ id }) => id),
      }

      return etapeReworked
    }),
    Effect.flatMap(({ pool, user, etape }) => {
      return Match.value(etape).pipe(
        Match.when(null, () => {
          const emptyResult: PerimetreInformations = {
            communes: [],
            sdomZoneIds: [],
            superposition_alertes: [],
          }
          return Effect.succeed(emptyResult)
        }),
        Match.orElse(myEtape => getPerimetreInfosInternal(pool, user, myEtape))
      )
    }),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr('Il faut être connecté pour utiliser cette route', "Droits insuffisant pour lire l'étape", () => ({ ...caminoError, status: HTTP_STATUS.FORBIDDEN })),
        Match.whenOr('Les données en base ne correspondent pas à ce qui est attendu', "Impossible d'exécuter la requête dans la base de données", "La démarche n'existe pas", () => ({
          ...caminoError,
          status: HTTP_STATUS.BAD_REQUEST,
        })),
        Match.whenOr("Une erreur est survenue lors de la gestion des droits de l'étape", 'titre non trouvé en base', () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })),
        Match.exhaustive
      )
    )
  )
type GetPerimetreInfosInternalEtape = { demarche_id: DemarcheId; geojson4326_perimetre: MultiPolygon | null; sdom_zones: SDOMZoneId[]; etape_type_id: EtapeTypeId; communes: CommuneId[] }

const erreurLorsDeLaGestionDesDroitsDeLEtape = "Une erreur est survenue lors de la gestion des droits de l'étape" as const
const cantReadEtapeError = "Droits insuffisant pour lire l'étape" as const
type GetPerimetreInfosInternalErrors =
  | EffectDbQueryAndValidateErrors
  | GetDemarcheByIdOrSlugErrors
  | GetTitreByIdOrSlugErrors
  | typeof cantReadEtapeError
  | typeof erreurLorsDeLaGestionDesDroitsDeLEtape
const getPerimetreInfosInternal = (pool: Pool, user: User, etape: GetPerimetreInfosInternalEtape): Effect.Effect<PerimetreInformations, CaminoError<GetPerimetreInfosInternalErrors>> =>
  Effect.Do.pipe(
    Effect.bind('demarche', () => getDemarcheByIdOrSlug(pool, etape.demarche_id)),
    Effect.bind('titre', ({ demarche }) => getTitreByIdOrSlug(pool, demarche.titre_id)),
    Effect.bind('canReadEtape', ({ demarche, titre }) =>
      Effect.tryPromise({
        try: () =>
          canReadEtape(
            user,
            memoize(() => Promise.resolve(titre.titre_type_id)),
            memoize(() => getAdministrationsLocalesByTitreId(pool, demarche.titre_id)),
            memoize(() => getTitulairesAmodiatairesByTitreId(pool, demarche.titre_id)),
            etape.etape_type_id,
            { ...demarche, titre_public_lecture: titre.public_lecture }
          ),

        catch: e => ({ message: erreurLorsDeLaGestionDesDroitsDeLEtape, extra: e }),
      })
    ),
    Effect.filterOrFail(
      ({ canReadEtape }) => canReadEtape,
      () => ({ message: cantReadEtapeError })
    ),
    Effect.bind('superpositionAlertes', ({ titre }) => getAlertesSuperposition(etape.geojson4326_perimetre, titre.titre_type_id, titre.titre_slug, user, pool)),
    Effect.map(({ superpositionAlertes }) => {
      const response: PerimetreInformations = {
        communes: etape.communes,
        sdomZoneIds: etape.sdom_zones,
        superposition_alertes: superpositionAlertes,
      }
      return response
    })
  )

const shapeValidator = z.array(polygonValidator.or(multiPolygonValidator)).max(1).min(1)
const geojsonValidator = featureCollectionMultipolygonValidator.or(featureCollectionPolygonValidator)

const csvInputNumberValidator = z.union([z.string(), z.number()]).transform<number>(val => {
  if (typeof val === 'string') {
    if (val.includes(',')) {
      return Number.parseFloat(val.replace(/\./g, '').replace(/,/g, '.'))
    }

    return Number.parseFloat(val)
  } else {
    return val
  }
})

const csvCommonValidator = z
  .object({})
  .passthrough()
  .transform(value => {
    if ('Nom du point' in value) {
      value.nom = value['Nom du point']
    }
    Object.keys(value).forEach(k => (value[k.toLowerCase().trim()] = value[k]))

    return value
  })

const csvCommonLowerValidator = z.object({ nom: z.string(), description: z.string().optional() })
const makeCsvToJsonValidator = <T extends ZodTypeAny>(pointValidator: T) => z.array(csvCommonValidator.pipe(pointValidator))
const csvXYValidator = makeCsvToJsonValidator(csvCommonLowerValidator.extend({ x: csvInputNumberValidator, y: csvInputNumberValidator }))
const csvLatLongValidator = makeCsvToJsonValidator(csvCommonLowerValidator.extend({ longitude: csvInputNumberValidator, latitude: csvInputNumberValidator }))
const csvForageXYValidator = makeCsvToJsonValidator(
  featureForagePropertiesValidator.omit({ profondeur: true }).extend({ x: csvInputNumberValidator, y: csvInputNumberValidator, profondeur: csvInputNumberValidator })
)
const csvForageDegValidator = makeCsvToJsonValidator(
  featureForagePropertiesValidator.omit({ profondeur: true }).extend({ longitude: csvInputNumberValidator, latitude: csvInputNumberValidator, profondeur: csvInputNumberValidator })
)

const ouvertureGeoJSONError = "Une erreur s'est produite lors de l'ouverture du fichier GeoJSON" as const
const ouvertureShapeError = "Une erreur s'est produite lors de l'ouverture du fichier shape" as const
const ouvertureCsvError = 'Une erreur est survenue lors de la lecture du csv' as const
const MAX_CSV_LENGTH = 50 as const
const importCsvRestrictionError = `L'import CSV est fait pour des petits polygones simple de moins de ${MAX_CSV_LENGTH} sommets` as const
const recuperationInfoCsvError = `Une erreur est survenue lors de la récupération des informations du CSV` as const
const extractionGeoJSONError = "Une erreur s'est produite lors de l'extraction du multi-polygone du fichier GeoJSON" as const
const extractionShapeError = "Une erreur s'est produite lors de l'extraction du multi-polygone du fichier shape" as const
type GeojsonImportErrorMessages =
  | ZodUnparseable
  | EffectDbQueryAndValidateErrors
  | typeof ouvertureGeoJSONError
  | typeof ouvertureShapeError
  | typeof importCsvRestrictionError
  | typeof recuperationInfoCsvError
  | typeof extractionGeoJSONError
  | typeof extractionShapeError
  | GetGeojsonByGeoSystemeIdErrorMessages
  | GetGeojsonInformationErrorMessages
  | ConvertPointsErrors
  | FileNameToCsvErrors

export const geojsonImport: RestNewPostCall<'/rest/geojson/import/:geoSystemeId'> = (rootPipe): Effect.Effect<GeojsonInformations, CaminoApiError<GeojsonImportErrorMessages>> => {
  return rootPipe.pipe(
    Effect.let('pathFrom', ({ body }) => join(process.cwd(), `/files/tmp/${body.tempDocumentName}`)),
    Effect.bind('result', ({ body, pathFrom, params }) => {
      return Match.value(body.fileType).pipe(
        Match.when('geojson', () =>
          pipe(
            fileNameToJson(pathFrom, geojsonValidator),
            Effect.flatMap(features =>
              Effect.try({
                try: () => {
                  const firstGeometry = features.features[0].geometry
                  const multiPolygon: MultiPolygon = firstGeometry.type === 'Polygon' ? { type: 'MultiPolygon', coordinates: [firstGeometry.coordinates] } : firstGeometry

                  const geojsonOriginFeatureMultiPolygon: FeatureMultiPolygon = { type: 'Feature', properties: {}, geometry: multiPolygon }

                  let geojsonOriginFeatureCollectionPoints: null | FeatureCollectionPoints = null
                  // On a des points après le multipolygone
                  if (features.features.length > 1) {
                    const [_multi, ...points] = features.features
                    geojsonOriginFeatureCollectionPoints = { type: 'FeatureCollection', features: points }
                  }

                  return { geojsonOriginFeatureMultiPolygon, geojsonOriginFeatureCollectionPoints }
                },
                catch: e => ({ message: extractionGeoJSONError, extra: e }),
              })
            )
          )
        ),
        Match.when('shp', () =>
          pipe(
            fileNameToShape(pathFrom, shapeValidator),
            Effect.flatMap(shapePolygonOrMultipolygons =>
              Effect.try({
                try: () => {
                  const shapePolygonOrMultipolygon = shapePolygonOrMultipolygons[0]
                  let coordinates: [number, number][][][]
                  if (shapePolygonOrMultipolygon.type === 'MultiPolygon') {
                    coordinates = shapePolygonOrMultipolygon.coordinates
                  } else {
                    coordinates = [shapePolygonOrMultipolygon.coordinates]
                  }
                  const geojsonOriginFeatureMultiPolygon: FeatureMultiPolygon = { type: 'Feature', geometry: { type: 'MultiPolygon', coordinates }, properties: {} }

                  return { geojsonOriginFeatureMultiPolygon, geojsonOriginFeatureCollectionPoints: null }
                },
                catch: e => ({ message: extractionShapeError, extra: e }),
              })
            )
          )
        ),
        Match.when('csv', () =>
          pipe(
            fileNameToCsv(pathFrom),
            Effect.filterOrFail(
              converted => converted.length <= MAX_CSV_LENGTH,
              () => ({ message: importCsvRestrictionError })
            ),
            Effect.flatMap(converted => {
              const uniteId = GeoSystemes[params.geoSystemeId].uniteId
              const myPipe = Match.value(uniteId).pipe(
                Match.when('met', () =>
                  pipe(
                    zodParseEffect(csvXYValidator, converted),
                    Effect.flatMap(rows =>
                      Effect.try({
                        try: () => {
                          const coordinates: MultiPolygon['coordinates'] = [[rows.map(({ x, y }) => [x, y])]]
                          const points: FeatureCollectionPoints['features'] = rows.map(ligne => ({
                            type: 'Feature',
                            properties: { nom: ligne.nom, description: ligne.description },
                            geometry: { type: 'Point', coordinates: [ligne.x, ligne.y] },
                          }))

                          coordinates[0][0].push([rows[0].x, rows[0].y])

                          return { coordinates, points }
                        },
                        catch: e => ({ message: recuperationInfoCsvError, extra: e }),
                      })
                    )
                  )
                ),
                Match.whenOr('gon', 'deg', () =>
                  pipe(
                    zodParseEffect(csvLatLongValidator, converted),
                    Effect.flatMap(rows =>
                      Effect.try({
                        try: () => {
                          const coordinates: MultiPolygon['coordinates'] = [[rows.map(({ longitude, latitude }) => [longitude, latitude])]]
                          const points: FeatureCollectionPoints['features'] = rows.map(ligne => ({
                            type: 'Feature',
                            properties: { nom: ligne.nom, description: ligne.description },
                            geometry: { type: 'Point', coordinates: [ligne.longitude, ligne.latitude] },
                          }))
                          coordinates[0][0].push([rows[0].longitude, rows[0].latitude])

                          return { coordinates, points }
                        },
                        catch: e => ({ message: recuperationInfoCsvError, extra: e }),
                      })
                    )
                  )
                ),
                Match.exhaustive
              )

              return pipe(
                myPipe,
                Effect.map(({ coordinates, points }) => {
                  const geojsonOriginFeatureMultiPolygon: FeatureMultiPolygon = { type: 'Feature', properties: {}, geometry: { type: 'MultiPolygon', coordinates } }
                  const geojsonOriginFeatureCollectionPoints: FeatureCollectionPoints = { type: 'FeatureCollection', features: points }

                  return { geojsonOriginFeatureMultiPolygon, geojsonOriginFeatureCollectionPoints }
                })
              )
            })
          )
        ),
        Match.exhaustive
      )
    }),
    Effect.bind('geojson4326MultiPolygon', ({ result, pool, params }) =>
      pipe(
        getGeojsonByGeoSystemeIdQuery(pool, params.geoSystemeId, result.geojsonOriginFeatureMultiPolygon),
        Effect.map(result => result.geometry)
      )
    ),
    Effect.bind('geojson4326FeatureCollectionPoints', ({ result, pool, params }) => {
      if (isNotNullNorUndefined(result.geojsonOriginFeatureCollectionPoints)) {
        return convertPoints(pool, params.geoSystemeId, result.geojsonOriginFeatureCollectionPoints)
      }

      return Effect.succeed(null)
    }),
    Effect.bind('geoInfo', ({ geojson4326MultiPolygon, pool }) => getGeojsonInformation(pool, geojson4326MultiPolygon)),
    Effect.bind('alertesSuperposition', ({ geojson4326MultiPolygon, body, user, pool }) => getAlertesSuperposition(geojson4326MultiPolygon, body.titreTypeId, body.titreSlug, user, pool)),
    Effect.map(({ result, geojson4326MultiPolygon, geojson4326FeatureCollectionPoints, geoInfo, alertesSuperposition, params }) => {
      const geojsonInformations: GeojsonInformations = {
        superposition_alertes: alertesSuperposition,
        communes: geoInfo.communes,
        foretIds: geoInfo.forets,
        sdomZoneIds: geoInfo.sdom,
        secteurMaritimeIds: geoInfo.secteurs,
        surface: geoInfo.surface,
        geojson4326_perimetre: { type: 'Feature', geometry: geojson4326MultiPolygon, properties: {} },
        geojson4326_points: geojson4326FeatureCollectionPoints,
        geojson_origine_perimetre: result.geojsonOriginFeatureMultiPolygon,
        geojson_origine_points: result.geojsonOriginFeatureCollectionPoints,
        geojson_origine_geo_systeme_id: params.geoSystemeId,
      }

      return geojsonInformations
    }),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.whenOr(
          'Une erreur est survenue lors de la lecture du csv',
          'Une erreur est survenue lors de la récupération des informations du CSV',
          'Une erreur inattendue est survenue lors de la récupération des informations geojson en base',
          "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 transformer le geojson dans le référentiel donné',
          () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
        ),
        Match.whenOr(
          'Problème de validation de données',
          "Une erreur s'est produite lors de l'ouverture du fichier GeoJSON",
          "Une erreur s'est produite lors de l'ouverture du fichier shape",
          'Impossible de convertir la géométrie en JSON',
          'Impossible de convertir le geojson vers le système',
          "L'import CSV est fait pour des petits polygones simple de moins de 50 sommets",
          "Le périmètre n'est pas valide dans le référentiel donné",
          "Une erreur s'est produite lors de l'extraction du multi-polygone du fichier GeoJSON",
          "Une erreur s'est produite lors de l'extraction du multi-polygone du fichier shape",
          'Impossible de transformer la feature collection',
          'La liste des points est vide',
          'Le nombre de points est invalide',
          'Problème de Système géographique (SRID)',
          'Le CSV ne contient aucun élément',
          'Le séparateur est probablement incorrect',
          () => ({ ...caminoError, status: HTTP_STATUS.BAD_REQUEST })
        ),
        Match.exhaustive
      )
    )
  )
}
const fileNameToJson = <T extends ZodTypeAny>(pathFrom: string, validator: T): Effect.Effect<z.infer<T>, CaminoError<ZodUnparseable | typeof ouvertureGeoJSONError>> => {
  return pipe(
    Effect.try({
      try: () => {
        const fileContent = readFileSync(pathFrom)

        return JSON.parse(fileContent.toString())
      },
      catch: () => ({ message: ouvertureGeoJSONError }),
    }),
    Effect.flatMap(zodParseEffectCallback(validator))
  )
}
const fileNameToShape = <T extends ZodTypeAny>(pathFrom: string, validator: T): Effect.Effect<z.infer<T>, CaminoError<ZodUnparseable | typeof ouvertureShapeError>> => {
  return pipe(
    Effect.try({
      try: () => {
        const fileContent = readFileSync(pathFrom)

        return parseShp(fileContent)
      },
      catch: () => ({ message: ouvertureShapeError }),
    }),
    Effect.flatMap(zodParseEffectCallback(validator))
  )
}

const readIsoOrUTF8FileSync = (path: string): string => {
  const buffer = readFileSync(path)

  try {
    return new TextDecoder('utf-8', { fatal: true }).decode(buffer)
  } catch (e) {
    return new TextDecoder('iso-8859-15', { fatal: true }).decode(buffer)
  }
}

const parseCSV = (pathFrom: string, separator: ',' | ';'): Effect.Effect<unknown[], CaminoError<FileNameToCsvErrors>> => {
  return Effect.try({
    try: () => {
      const fileContent = readIsoOrUTF8FileSync(pathFrom)
      const result = xlsx.read(fileContent, { type: 'string', FS: separator, raw: true })

      if (result.SheetNames.length !== 1) {
        throw new Error(`une erreur est survenue lors de la lecture du csv, il ne devrait y avoir qu'un seul document ${result.SheetNames}`)
      }

      const sheet1 = result.Sheets[result.SheetNames[0]]
      return xlsx.utils.sheet_to_json(sheet1, { raw: true })
    },
    catch: e => ({ message: ouvertureCsvError, extra: e }),
  }).pipe(
    Effect.filterOrFail(
      result => isNotNullNorUndefinedNorEmpty(result),
      () => ({ message: csvVideError })
    ),
    Effect.filterOrFail(
      result => {
        const firstValue = result[0]
        return typeof firstValue === 'object' && isNotNullNorUndefined(firstValue) && Object.keys(firstValue).length >= 2
      },
      () => ({ message: mauvaisSeparateurCsvError, detail: `Ce CSV n'est pas formatté avec le séparateur attendu : ${separator}` })
    )
  )
}

const csvVideError = 'Le CSV ne contient aucun élément' as const
const mauvaisSeparateurCsvError = 'Le séparateur est probablement incorrect' as const
type FileNameToCsvErrors = typeof ouvertureCsvError | typeof mauvaisSeparateurCsvError | typeof csvVideError
const fileNameToCsv = (pathFrom: string): Effect.Effect<unknown[], CaminoError<FileNameToCsvErrors>> => {
  return Effect.Do.pipe(
    Effect.flatMap(() => parseCSV(pathFrom, ';')),
    Effect.catchIf(
      error => error.message === mauvaisSeparateurCsvError,
      () => parseCSV(pathFrom, ',')
    ),
    Effect.mapError(error => {
      if (error.message === mauvaisSeparateurCsvError) {
        return { ...error, detail: "Seuls les séparateurs ';' ou ',' sont acceptés" }
      }
      return error
    })
  )
}

const accesInterditError = 'Accès interdit' as const
type GeosjsonImportPointsErrorMessages = ZodUnparseable | EffectDbQueryAndValidateErrors | typeof accesInterditError | 'Fichier incorrect' | ConvertPointsErrors

export const geojsonImportPoints: RestNewPostCall<'/rest/geojson_points/import/:geoSystemeId'> = (
  rootPipe
): Effect.Effect<GeojsonImportPointsResponse, CaminoApiError<GeosjsonImportPointsErrorMessages>> => {
  return rootPipe.pipe(
    Effect.filterOrFail(
      ({ user }) => !isDefault(user),
      () => ({ message: 'Accès interdit' as const })
    ),
    Effect.bind('features', ({ body }) => {
      return pipe(
        Effect.try({
          try: () => {
            const filename = body.tempDocumentName
            const pathFrom = join(process.cwd(), `/files/tmp/${filename}`)
            const fileContent = readFileSync(pathFrom)

            return JSON.parse(fileContent.toString())
          },
          catch: () => ({ message: 'Fichier incorrect' as const }),
        }),
        Effect.flatMap(zodParseEffectCallback(featureCollectionPointsValidator))
      )
    }),
    Effect.bind('geojson4326points', ({ features, pool, params }) => convertPoints(pool, params.geoSystemeId, features)),
    Effect.map(result => ({ geojson4326: result.geojson4326points, origin: result.features })),
    Effect.mapError(caminoError =>
      Match.value(caminoError.message).pipe(
        Match.when('Accès interdit', () => ({ ...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.whenOr(
          'Fichier incorrect',
          'Impossible de transformer la feature collection',
          'La liste des points est vide',
          'Le nombre de points est invalide',
          'Problème de validation de données',
          'Problème de Système géographique (SRID)',
          () => ({ ...caminoError, status: HTTP_STATUS.BAD_REQUEST })
        ),
        Match.exhaustive
      )
    )
  )
}

type GeosjsonImportForagesErrorMessages = ZodUnparseable | EffectDbQueryAndValidateErrors | typeof ouvertureShapeError | typeof ouvertureGeoJSONError | ConvertPointsErrors | FileNameToCsvErrors
export const geojsonImportForages: RestNewPostCall<'/rest/geojson_forages/import/:geoSystemeId'> = (
  rootPipe
): Effect.Effect<GeojsonImportForagesResponse, CaminoApiError<GeosjsonImportForagesErrorMessages>> => {
  return rootPipe.pipe(
    Effect.let('filename', ({ body }) => body.tempDocumentName),
    Effect.let('pathFrom', ({ filename }) => join(process.cwd(), `/files/tmp/${filename}`)),
    Effect.let('fileType', ({ body }) => body.fileType),
    Effect.bind('features', ({ pathFrom, params, fileType }) =>
      Match.value(fileType).pipe(
        Match.when('geojson', () => fileNameToJson(pathFrom, featureCollectionForagesValidator)),
        Match.when('shp', () => fileNameToShape(pathFrom, featureCollectionForagesValidator)),
        Match.when('csv', () =>
          pipe(
            fileNameToCsv(pathFrom),
            Effect.flatMap(converted => {
              const uniteId = GeoSystemes[params.geoSystemeId].uniteId

              return Match.value(uniteId).pipe(
                Match.when('met', () =>
                  pipe(
                    zodParseEffect(csvForageXYValidator, converted),
                    Effect.map(rows => {
                      const points: FeatureCollectionForages['features'] = rows.map(ligne => ({
                        type: 'Feature',
                        properties: { nom: ligne.nom, description: ligne.description, profondeur: ligne.profondeur, type: ligne.type },
                        geometry: { type: 'Point', coordinates: [ligne.x, ligne.y] },
                      }))

                      return { type: 'FeatureCollection', features: points } as const
                    })
                  )
                ),
                Match.whenOr('gon', 'deg', () =>
                  pipe(
                    zodParseEffect(csvForageDegValidator, converted),
                    Effect.map(rows => {
                      const points: FeatureCollectionForages['features'] = rows.map(ligne => ({
                        type: 'Feature',
                        properties: { nom: ligne.nom, description: ligne.description, profondeur: ligne.profondeur, type: ligne.type },
                        geometry: { type: 'Point', coordinates: [ligne.longitude, ligne.latitude] },
                      }))

                      return { type: 'FeatureCollection', features: points } as const
                    })
                  )
                ),
                Match.exhaustive
              )
            }),
            Effect.flatMap(zodParseEffectCallback(featureCollectionForagesValidator))
          )
        ),
        Match.exhaustive
      )
    ),
    Effect.bind('conversion', ({ features, pool, params }) => convertPoints(pool, params.geoSystemeId, features)),
    Effect.map(({ conversion, features }) => {
      return { geojson4326: conversion, origin: features }
    }),
    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.whenOr(
          'Problème de validation de données',
          'Une erreur est survenue lors de la lecture du csv',
          "Une erreur s'est produite lors de l'ouverture du fichier GeoJSON",
          "Une erreur s'est produite lors de l'ouverture du fichier shape",
          'Impossible de transformer la feature collection',
          'La liste des points est vide',
          'Le nombre de points est invalide',
          'Problème de Système géographique (SRID)',
          'Le CSV ne contient aucun élément',
          'Le séparateur est probablement incorrect',
          () => ({ ...caminoError, status: HTTP_STATUS.BAD_REQUEST })
        ),
        Match.exhaustive
      )
    )
  )
}

const getAlertesSuperposition = (
  geojson4326_perimetre: MultiPolygon | null,
  titreTypeId: TitreTypeId,
  titreSlug: TitreSlug,
  user: User,
  pool: Pool
): Effect.Effect<GetTitresIntersectionWithGeojson[], CaminoError<EffectDbQueryAndValidateErrors>> => {
  if (titreTypeId === 'axm' && (isSuper(user) || isAdministrationAdmin(user) || isAdministrationEditeur(user)) && geojson4326_perimetre !== null) {
    // vérifie qu’il n’existe pas de demandes de titres en cours sur ce périmètre
    return getTitresIntersectionWithGeojson(pool, geojson4326_perimetre, titreSlug)
  }

  return Effect.succeed([])
}
