import { titreGet, titresGet } from '../../database/queries/titres'
import { titresDemarchesGet } from '../../database/queries/titres-demarches'
import { titresActivitesGet } from '../../database/queries/titres-activites'
import { entreprisesGet } from '../../database/queries/entreprises'

import { titreFormat, titresFormat } from '../_format/titres'
import { titreDemarcheFormat } from '../_format/titres-demarches'

import { tableConvert } from './_convert'
import { fileNameCreate } from '../../tools/file-name-create'

import { titreGeojsonPropertiesFormat, titresGeojsonFormat, titresTableFormat } from './format/titres'
import { titresDemarchesFormatTable } from './format/titres-demarches'
import { titresActivitesFormatTable } from './format/titres-activites'
import { entreprisesFormatTable } from './format/entreprises'

import { User } from 'camino-common/src/roles'
import { DownloadFormat } from 'camino-common/src/rest'
import { Pool } from 'pg'
import { z, ZodType } from 'zod'
import { exhaustiveCheck, isNotNullNorUndefined, isNullOrUndefined } from 'camino-common/src/typescript-tools'
import { getCommunesIndex } from '../../database/queries/communes'
import { FeatureCollection, GeojsonFeaturePoint } from 'camino-common/src/perimetre'
import { FieldsTitre } from '../../database/queries/_options'
import { titresValidator, demarchesValidator, activitesValidator, entreprisesValidator } from '../../business/utils/filters'
import { GetEntreprises, getEntreprises } from './entreprises.queries'
import { EntrepriseId } from 'camino-common/src/entreprise'
import { callAndExit } from '../../tools/fp-tools'

const formatCheck = (formats: string[], format: string) => {
  if (!formats.includes(format)) {
    throw new Error(`Format « ${format} » non supporté.`)
  }
}

const titreFields: FieldsTitre = {
  substancesEtape: { id: {} },
  titulairesEtape: { id: {} },
  amodiatairesEtape: { id: {} },
  pointsEtape: { id: {} },
  demarches: {
    etapes: {
      id: {},
    },
  },
}

type GenericQueryInput<T extends ZodType> = { [key in keyof z.infer<T>]?: null | string }

interface ITitreInput {
  query: { format?: DownloadFormat }
  params: { id?: string | null }
}

export const titre =
  (pool: Pool) =>
  async ({ query: { format = 'geojson' }, params: { id } }: ITitreInput, user: User): Promise<{ nom: string; format: DownloadFormat; contenu: string } | null> => {
    formatCheck(['geojson'], format)

    const titre = await titreGet(id!, { fields: titreFields }, user)

    if (!titre) {
      return null
    }

    const titreFormatted = titreFormat(titre)

    const communesIndex = await getCommunesIndex(pool, titreFormatted.communes?.map(({ id }) => id) ?? [])
    const entreprises = await callAndExit(getEntreprises(pool))
    const entreprisesIndex = entreprises.reduce<Record<EntrepriseId, GetEntreprises>>((acc, entreprise) => {
      acc[entreprise.id] = entreprise

      return acc
    }, {})

    if (titreFormatted.pointsEtape === undefined) {
      throw new Error('Le périmètre du titre n’est pas chargé')
    }

    if (titreFormatted.pointsEtape === null || isNullOrUndefined(titreFormatted.pointsEtape.geojson4326Perimetre)) {
      throw new Error('Il n’y a pas de périmètre pour ce titre')
    }

    const geojson4326Points: GeojsonFeaturePoint[] = titreFormatted.pointsEtape.geojson4326Points?.features ?? []

    const titreGeojson: FeatureCollection = {
      type: 'FeatureCollection',
      features: [titreFormatted.pointsEtape.geojson4326Perimetre, ...geojson4326Points],

      properties: titreGeojsonPropertiesFormat(communesIndex, entreprisesIndex, titreFormatted),
    }

    return {
      nom: fileNameCreate(titre.id, format),
      format,
      contenu: JSON.stringify(titreGeojson, null, 2),
    }
  }

export const titres =
  (pool: Pool) =>
  async ({ query }: { query: GenericQueryInput<typeof titresValidator> }, user: User): Promise<null | { nom: string; format: DownloadFormat; contenu: string }> => {
    const params = titresValidator.parse(query)

    const titres = await titresGet(
      {
        ordre: params.ordre,
        colonne: params.colonne,
        typesIds: params.typesIds,
        domainesIds: params.domainesIds,
        statutsIds: params.statutsIds,
        ids: params.titresIds,
        entreprisesIds: params.entreprisesIds,
        substancesIds: params.substancesIds,
        references: params.references,
        communes: params.communes,
        departements: params.departements,
        regions: params.regions,
        facadesMaritimes: params.facadesMaritimes,
        perimetre: params.perimetre,
        demandeEnCours: true,
      },
      { fields: titreFields },
      user
    )

    const titresFormatted = titresFormat(titres)

    let contenu

    switch (params.format) {
      case 'csv':
      case 'xlsx':
      case 'ods':
        contenu = tableConvert('titres', await titresTableFormat(pool, titresFormatted), params.format)
        break
      case 'geojson':
        contenu = JSON.stringify(await titresGeojsonFormat(pool, titresFormatted), null, 2)
        break
      default:
        exhaustiveCheck(params.format)
    }

    return isNotNullNorUndefined(contenu)
      ? {
          nom: fileNameCreate(`titres-${titres.length}`, params.format),
          format: params.format,
          contenu,
        }
      : null
  }

export const travaux =
  (pool: Pool) =>
  async ({ query }: { query: GenericQueryInput<typeof demarchesValidator> }, user: User): Promise<null | { nom: string; format: DownloadFormat; contenu: string }> => {
    const funcDemarche = demarches(pool)

    return funcDemarche({ query: { ...query, travaux: 'true' } }, user)
  }
export const demarches =
  (pool: Pool) =>
  async ({ query }: { query: GenericQueryInput<typeof demarchesValidator> }, user: User): Promise<null | { nom: string; format: DownloadFormat; contenu: string }> => {
    const params = demarchesValidator.parse(query)

    const titresDemarches = await titresDemarchesGet(
      {
        ordre: params.ordre,
        colonne: params.colonne,
        typesIds: [...(params.demarchesTypesIds ?? []), ...(params.travauxTypesIds ?? [])],
        statutsIds: params.demarchesStatutsIds,
        etapesInclues: isNotNullNorUndefined(params.etapesInclues) ? JSON.parse(params.etapesInclues) : null,
        etapesExclues: isNotNullNorUndefined(params.etapesExclues) ? JSON.parse(params.etapesExclues) : null,
        titresTypesIds: params.typesIds,
        titresDomainesIds: params.domainesIds,
        titresStatutsIds: params.statutsIds,
        titresIds: params.titresIds,
        titresEntreprisesIds: params.entreprisesIds,
        titresSubstancesIds: params.substancesIds,
        titresReferences: params.references,
        travaux: params.travaux,
      },
      {
        fields: {
          titre: {
            id: {},
          },
          etapes: {
            id: {},
          },
        },
      },
      user
    )

    const demarchesFormatted = titresDemarches.map(titreDemarche => titreDemarcheFormat(titreDemarche))

    let contenu

    switch (params.format) {
      case 'csv':
      case 'xlsx':
      case 'ods':
        contenu = tableConvert('demarches', await titresDemarchesFormatTable(pool, demarchesFormatted), params.format)
        break
      default:
        exhaustiveCheck(params.format)
    }

    return isNotNullNorUndefined(contenu)
      ? {
          nom: fileNameCreate(`${params.travaux ? 'travaux' : 'demarches'}-${titresDemarches.length}`, params.format),
          format: params.format,
          contenu,
        }
      : null
  }

export const activites =
  (pool: Pool) =>
  async ({ query }: { query: GenericQueryInput<typeof activitesValidator> }, user: User): Promise<null | { nom: string; format: DownloadFormat; contenu: string }> => {
    const params = activitesValidator.parse(query)

    const titresActivites = await titresActivitesGet(
      {
        ordre: params.ordre,
        colonne: params.colonne,
        typesIds: params.activiteTypesIds,
        statutsIds: params.activiteStatutsIds,
        annees: params.annees,
        titresIds: params.titresIds,
        titresEntreprisesIds: params.entreprisesIds,
        titresSubstancesIds: params.substancesIds,
        titresReferences: params.references,
        titresTypesIds: params.typesIds,
        titresDomainesIds: params.domainesIds,
        titresStatutsIds: params.statutsIds,
      },
      {
        fields: {
          titre: { pointsEtape: { id: {} } },
        },
      },
      user
    )

    let contenu

    switch (params.format) {
      case 'csv':
      case 'xlsx':
      case 'ods':
        contenu = tableConvert('activites', await titresActivitesFormatTable(pool, titresActivites), params.format)
        break
      default:
        exhaustiveCheck(params.format)
    }

    return isNotNullNorUndefined(contenu)
      ? {
          nom: fileNameCreate(`activites-${titresActivites.length}`, params.format),
          format: params.format,
          contenu,
        }
      : null
  }

export const entreprises =
  (_pool: Pool) =>
  async ({ query }: { query: GenericQueryInput<typeof entreprisesValidator> }, user: User): Promise<null | { nom: string; format: DownloadFormat; contenu: string }> => {
    const params = entreprisesValidator.parse(query)

    const entreprises = await entreprisesGet({ noms: params.nomsEntreprise }, {}, user)

    let contenu

    switch (params.format) {
      case 'csv':
      case 'xlsx':
      case 'ods':
        contenu = tableConvert('entreprises', entreprisesFormatTable(entreprises), params.format)
        break
      default:
        exhaustiveCheck(params.format)
    }

    return isNotNullNorUndefined(contenu)
      ? {
          nom: fileNameCreate(`entreprises-${entreprises.length}`, params.format),
          format: params.format,
          contenu,
        }
      : null
  }
