import { sql } from '@pgtyped/runtime'
import { EffectDbQueryAndValidateErrors, Redefine, dbQueryAndValidate, effectDbQueryAndValidate } from '../../pg-database'
import {
  IDeleteActiviteDocumentQueryQuery,
  IGetActiviteByIdQueryQuery,
  IGetActiviteDocumentsInternalQuery,
  IGetAdministrationsLocalesByActiviteIdQuery,
  IGetLargeobjectIdByActiviteDocumentIdInternalQuery,
  IGetTitreTypeIdByActiviteIdQuery,
  IGetTitulairesAmodiatairesTitreActiviteQuery,
  IInsertActiviteDocumentInternalQuery,
  IUpdateActiviteDbQuery,
  IActiviteDeleteDbQuery,
  IGetActivitesSuperDbQuery,
  IResetActiviteStatusDbQuery,
} from './activites.queries.types'
import {
  ActiviteDocument,
  ActiviteDocumentId,
  ActiviteId,
  ActiviteIdOrSlug,
  ActiviteSuper,
  activiteDocumentIdValidator,
  activiteDocumentValidator,
  activiteIdValidator,
  activiteSuperValidator,
  activiteValidator,
} from 'camino-common/src/activite'
import { Pool } from 'pg'
import { canDeleteActiviteDocument, canEditActivite, canReadTitreActivites } from 'camino-common/src/permissions/activites'
import { User, isSuper, utilisateurIdValidator } from 'camino-common/src/roles'
import { z } from 'zod'
import { EntrepriseId, entrepriseIdValidator } from 'camino-common/src/entreprise'
import { TitreTypeId, titreTypeIdValidator } from 'camino-common/src/static/titresTypes'
import { AdministrationId, administrationIdValidator } from 'camino-common/src/static/administrations'
import { ACTIVITES_STATUTS_IDS, ActivitesStatutId } from 'camino-common/src/static/activitesStatuts'
import { CaminoDate, getCurrent } from 'camino-common/src/date'
import { titreIdValidator } from 'camino-common/src/validators/titres'
import { ActivitesTypesId } from 'camino-common/src/static/activitesTypes'
import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, SimplePromiseFn } from 'camino-common/src/typescript-tools'
import { ActiviteDocumentTypeId } from 'camino-common/src/static/documentsTypes'
import { sectionValidator } from 'camino-common/src/static/titresTypes_demarchesTypes_etapesTypes/sections'
import { Effect } from 'effect'
import { CaminoError } from 'camino-common/src/zod-tools'
import { callAndExit } from '../../tools/fp-tools'

const typeDeTitreIntrouvablePourActivite = `Pas de type de titre trouvé pour l'activité` as const
type TitreTypeIdByActiviteIdErrors = EffectDbQueryAndValidateErrors | typeof typeDeTitreIntrouvablePourActivite
export const titreTypeIdByActiviteId = (activiteId: ActiviteIdOrSlug, pool: Pool): Effect.Effect<TitreTypeId, CaminoError<TitreTypeIdByActiviteIdErrors>> =>
  Effect.Do.pipe(
    Effect.flatMap(() => effectDbQueryAndValidate(getTitreTypeIdByActiviteId, { activiteId }, pool, titreTypeIdObjectValidator)),
    Effect.filterOrFail(
      result => isNotNullNorUndefinedNorEmpty(result) && result.length === 1,
      () => ({ message: typeDeTitreIntrouvablePourActivite, detail: `Pas de type de titre trouvé pour l'activité ${activiteId}` })
    ),
    Effect.map(result => result[0].titre_type_id)
  )

const miseAJourActiviteInterdite = `Interdiction d'éditer une activité` as const
export type UpdateActiviteQueryErrors = EffectDbQueryAndValidateErrors | typeof miseAJourActiviteInterdite
export const updateActiviteQuery = (
  pool: Pool,
  user: User,
  activiteId: ActiviteId,
  contenu: Contenu,
  titreTypeId: SimplePromiseFn<TitreTypeId>,
  titresAdministrationsLocales: SimplePromiseFn<AdministrationId[]>,
  entreprisesTitulairesOuAmodiataires: SimplePromiseFn<EntrepriseId[]>
): Effect.Effect<void, CaminoError<UpdateActiviteQueryErrors>> =>
  Effect.Do.pipe(
    Effect.bind('userNotNull', () =>
      Effect.Do.pipe(
        Effect.map(() => user),
        Effect.filterOrFail(
          (userNotNull): userNotNull is NonNullable<User> => isNotNullNorUndefined(userNotNull),
          () => ({ message: miseAJourActiviteInterdite, detail: 'Utilisateur null ou undefined' })
        )
      )
    ),
    Effect.bind('canEditActivite', () =>
      Effect.tryPromise({
        try: () => canEditActivite(user, titreTypeId, titresAdministrationsLocales, entreprisesTitulairesOuAmodiataires, ACTIVITES_STATUTS_IDS.EN_CONSTRUCTION),
        catch: error => ({ message: miseAJourActiviteInterdite, detail: 'Appel à canEditActivite échoué', extra: error }),
      })
    ),
    Effect.filterOrFail(
      ({ canEditActivite }) => canEditActivite,
      () => ({ message: miseAJourActiviteInterdite, detail: "Utilisateur n'a pas le droit de modifier cette activité", extra: { activiteId } })
    ),
    Effect.tap(({ userNotNull }) =>
      effectDbQueryAndValidate(updateActiviteDb, { userId: userNotNull.id, activiteId, dateSaisie: getCurrent(), activiteStatutId: ACTIVITES_STATUTS_IDS.EN_CONSTRUCTION, contenu }, pool, z.void())
    ),
    Effect.flatMap(() => Effect.void)
  )

const updateActiviteDb = sql<
  Redefine<IUpdateActiviteDbQuery, { userId: string; dateSaisie: CaminoDate; activiteId: ActiviteId; activiteStatutId: ActivitesStatutId; contenu: Contenu }, void>
>`update titres_activites set utilisateur_id = $userId!, date_saisie = $dateSaisie!, activite_statut_id = $activiteStatutId!, contenu = $contenu! where id = $activiteId;`
export type ResetActiviteStatusQueryErrors = UpdateActiviteQueryErrors
export const resetActiviteStatusQuery = (
  pool: Pool,
  user: User,
  activiteId: ActiviteId,
  titreTypeId: SimplePromiseFn<TitreTypeId>,
  titresAdministrationsLocales: SimplePromiseFn<AdministrationId[]>,
  entreprisesTitulairesOuAmodiataires: SimplePromiseFn<EntrepriseId[]>
): Effect.Effect<void, CaminoError<ResetActiviteStatusQueryErrors>> =>
  Effect.Do.pipe(
    Effect.bind('canEditActivite', () =>
      Effect.tryPromise({
        try: () => canEditActivite(user, titreTypeId, titresAdministrationsLocales, entreprisesTitulairesOuAmodiataires, ACTIVITES_STATUTS_IDS.DEPOSE),
        catch: error => ({ message: miseAJourActiviteInterdite, detail: 'Appel à canEditActivite échoué', extra: error }),
      })
    ),
    Effect.filterOrFail(
      ({ canEditActivite }) => canEditActivite,
      () => ({ message: miseAJourActiviteInterdite, detail: "Utilisateur n'a pas le droit de modifier cette activité", extra: { activiteId } })
    ),
    Effect.tap(() => effectDbQueryAndValidate(resetActiviteStatusDb, { activiteId, activiteStatutId: ACTIVITES_STATUTS_IDS.EN_CONSTRUCTION }, pool, z.void())),
    Effect.flatMap(() => Effect.void)
  )

const resetActiviteStatusDb = sql<
  Redefine<IResetActiviteStatusDbQuery, { activiteId: ActiviteId; activiteStatutId: ActivitesStatutId }, void>
>`update titres_activites set activite_statut_id = $activiteStatutId! where id = $activiteId;`
export const contenuValidator = z.record(z.string(), z.record(z.string(), z.unknown().optional()).optional()).nullable()

export type Contenu = z.infer<typeof contenuValidator>
const dbActiviteValidator = activiteValidator
  .pick({
    id: true,
    type_id: true,
    activite_statut_id: true,
    date: true,
    annee: true,
    date_saisie: true,
    periode_id: true,
    suppression: true,
    slug: true,
  })
  .extend({
    contenu: contenuValidator,
    sections: z.array(sectionValidator),
    titre_id: titreIdValidator,
    titre_nom: z.string(),
    titre_slug: z.string(),
    utilisateur_id: utilisateurIdValidator.nullable(),
  })

const activiteInterdite = `Lecture de l'activité impossible` as const
const activiteIntrouvable = `Pas d'activité trouvée` as const
export type GetActiviteByIdErrors = EffectDbQueryAndValidateErrors | typeof activiteInterdite | typeof activiteIntrouvable
export type DbActivite = z.infer<typeof dbActiviteValidator>
export const getActiviteById = (
  activiteId: ActiviteIdOrSlug,
  pool: Pool,
  user: User,
  titreTypeId: SimplePromiseFn<TitreTypeId>,
  titresAdministrationsLocales: SimplePromiseFn<AdministrationId[]>,
  entreprisesTitulairesOuAmodiataires: SimplePromiseFn<EntrepriseId[]>
): Effect.Effect<DbActivite & { suppression: boolean }, CaminoError<GetActiviteByIdErrors>> => {
  return Effect.Do.pipe(
    Effect.bind('canRead', () =>
      Effect.tryPromise({
        try: () => canReadTitreActivites(user, titreTypeId, titresAdministrationsLocales, entreprisesTitulairesOuAmodiataires),
        catch: error => ({ message: activiteInterdite, detail: 'Appel à canReadTitreActivites échoué', extra: error }),
      })
    ),
    Effect.filterOrFail(
      ({ canRead }) => canRead,
      () => ({ message: activiteInterdite, detail: `Utilisateur ${user?.id} n'a pas la permission nécessaire` })
    ),
    Effect.flatMap(() => effectDbQueryAndValidate(getActiviteByIdQuery, { activiteId }, pool, dbActiviteValidator)),
    Effect.filterOrFail(
      result => isNotNullNorUndefinedNorEmpty(result) && result.length === 1,
      () => ({ message: activiteIntrouvable, detail: `Pas d'activité trouvée pour l'id '${activiteId}'` })
    ),
    Effect.map(result => ({ ...result[0], suppression: canDeleteActivite(result[0], user) }))
  )
}

const getActiviteByIdQuery = sql<Redefine<IGetActiviteByIdQueryQuery, { activiteId: ActiviteIdOrSlug }, DbActivite>>`
select
    ta.*,
    t.slug as titre_slug,
    t.nom as titre_nom
from
    titres_activites ta
    join titres t on t.id = ta.titre_id
where
    ta.id = $ activiteId !
    or ta.slug = $ activiteId !
LIMIT 1
`

const canDeleteActivite = (activite: DbActivite, user: User): boolean => {
  return isSuper(user) && activite.suppression
}

const suppressionActiviteInterdite = `Suppression de l'activité interdite` as const
type ActiviteDeleteQueryErrors = EffectDbQueryAndValidateErrors
export const activiteDeleteQuery = (
  activiteId: ActiviteId,
  pool: Pool,
  user: User,
  titreTypeId: SimplePromiseFn<TitreTypeId>,
  titresAdministrationsLocales: SimplePromiseFn<AdministrationId[]>,
  entreprisesTitulairesOuAmodiataires: SimplePromiseFn<EntrepriseId[]>
): Effect.Effect<boolean, CaminoError<ActiviteDeleteQueryErrors>> =>
  Effect.Do.pipe(
    Effect.flatMap(() => getActiviteById(activiteId, pool, user, titreTypeId, titresAdministrationsLocales, entreprisesTitulairesOuAmodiataires)),
    Effect.filterOrFail(
      activite => activite.suppression,
      () => ({ message: suppressionActiviteInterdite })
    ),
    Effect.flatMap(() =>
      Effect.Do.pipe(
        Effect.tap(() => effectDbQueryAndValidate(activiteDocumentDeleteDb, { activiteId }, pool, z.void())),
        Effect.tap(() => effectDbQueryAndValidate(activiteDeleteDb, { activiteId }, pool, z.void())),
        Effect.map(() => true)
      )
    ),
    // @TODO 2025-03-25: retirer cette ligne et mieux gérer les cas d'erreurs au niveau des appelants (ne plus retourner de booléen)
    Effect.catchAll(() => Effect.succeed(false))
  )

const activiteDeleteDb = sql<Redefine<IActiviteDeleteDbQuery, { activiteId: ActiviteId }, void>>`
delete from titres_activites ta
where ta.id = $ activiteId !
`

const activiteDocumentDeleteDb = sql<Redefine<IActiviteDeleteDbQuery, { activiteId: ActiviteId }, void>>`
delete from activites_documents
where activite_id = $ activiteId !
`

export const getActiviteDocumentsByActiviteId = (activiteId: ActiviteId, pool: Pool): Effect.Effect<ActiviteDocument[], CaminoError<EffectDbQueryAndValidateErrors>> =>
  effectDbQueryAndValidate(
    getActiviteDocumentsInternal,
    {
      activiteId,
    },
    pool,
    activiteDocumentValidator
  )

export const administrationsLocalesByActiviteId = async (activiteId: ActiviteIdOrSlug, pool: Pool): Promise<AdministrationId[]> => {
  const admins = await dbQueryAndValidate(getAdministrationsLocalesByActiviteId, { activiteId }, pool, administrationsLocalesValidator)
  if (admins.length > 1) {
    throw new Error(`Trop d'administrations locales trouvées pour l'activité ${activiteId}`)
  }
  if (admins.length === 0) {
    return []
  }

  return admins[0].administrations_locales
}

const administrationsLocalesValidator = z.object({ administrations_locales: z.array(administrationIdValidator) })
const getAdministrationsLocalesByActiviteId = sql<Redefine<IGetAdministrationsLocalesByActiviteIdQuery, { activiteId: ActiviteIdOrSlug }, z.infer<typeof administrationsLocalesValidator>>>`
select
    te.administrations_locales
from
    titres_activites ta
    join titres t on t.id = ta.titre_id
    left join titres_etapes te on te.id = t.props_titre_etapes_ids ->> 'points'
where
    ta.id = $ activiteId !
    or ta.slug = $ activiteId !
`

const titreTypeIdObjectValidator = z.object({ titre_type_id: titreTypeIdValidator })
const getTitreTypeIdByActiviteId = sql<Redefine<IGetTitreTypeIdByActiviteIdQuery, { activiteId: ActiviteIdOrSlug }, z.infer<typeof titreTypeIdObjectValidator>>>`
select
    t.type_id as titre_type_id
from
    titres_activites ta
    join titres t on t.id = ta.titre_id
where
    ta.id = $ activiteId !
    or ta.slug = $ activiteId !
`

export const entreprisesTitulairesOuAmoditairesByActiviteId = async (activiteId: ActiviteIdOrSlug, pool: Pool): Promise<EntrepriseId[]> => {
  const entreprises = await dbQueryAndValidate(getTitulairesAmodiatairesTitreActivite, { activiteId }, pool, entrepriseIdObjectValidator)

  return entreprises.map(({ id }) => id)
}

const entrepriseIdObjectValidator = z.object({ id: entrepriseIdValidator })
const getTitulairesAmodiatairesTitreActivite = sql<Redefine<IGetTitulairesAmodiatairesTitreActiviteQuery, { activiteId: ActiviteIdOrSlug }, z.infer<typeof entrepriseIdObjectValidator>>>`
select distinct
    e.id
from
    entreprises e,
    titres_activites ta
    join titres t on t.id = ta.titre_id
    left join titres_etapes etape_titulaires on etape_titulaires.id = t.props_titre_etapes_ids ->> 'titulaires'
    left join titres_etapes etape_amodiataires on etape_amodiataires.id = t.props_titre_etapes_ids ->> 'amodiataires'
where (ta.id = $ activiteId !
    or ta.slug = $ activiteId !)
and (etape_titulaires.titulaire_ids ? e.id
    or etape_amodiataires.amodiataire_ids ? e.id)
`

const getActiviteDocumentsInternal = sql<Redefine<IGetActiviteDocumentsInternalQuery, { activiteId: ActiviteId }, ActiviteDocument>>`
select
    d.id,
    d.description,
    d.activite_document_type_id
from
    activites_documents d
where
    d.activite_id = $ activiteId !
`

const droitsInsuffisanstPourSupprimerLeDocument = "droits insuffisants pour supprimer un document d'activité" as const
export type DeleteActiviteDocumentErrors = EffectDbQueryAndValidateErrors | typeof droitsInsuffisanstPourSupprimerLeDocument
export const deleteActiviteDocument = (
  id: ActiviteDocumentId,
  activiteDocumentTypeId: ActiviteDocumentTypeId,
  activiteTypeId: ActivitesTypesId,
  activiteStatutId: ActivitesStatutId,
  pool: Pool
): Effect.Effect<true, CaminoError<DeleteActiviteDocumentErrors>> =>
  Effect.Do.pipe(
    Effect.filterOrFail(
      () => canDeleteActiviteDocument(activiteDocumentTypeId, activiteTypeId, activiteStatutId),
      () => ({ message: droitsInsuffisanstPourSupprimerLeDocument })
    ),
    Effect.flatMap(() => effectDbQueryAndValidate(deleteActiviteDocumentQuery, { id }, pool, z.void())),
    Effect.map(() => true as const)
  )

const deleteActiviteDocumentQuery = sql<Redefine<IDeleteActiviteDocumentQueryQuery, { id: ActiviteDocumentId }, void>>`
delete from activites_documents
where id = $ id !
`

export const insertActiviteDocument = (
  pool: Pool,
  params: {
    id: ActiviteDocumentId
    activite_document_type_id: ActiviteDocumentTypeId
    date: CaminoDate
    activite_id: ActiviteId
    description: string
    largeobject_id: number
  }
): Effect.Effect<{ id: ActiviteDocumentId }[], CaminoError<EffectDbQueryAndValidateErrors>> =>
  effectDbQueryAndValidate(insertActiviteDocumentInternal, params, pool, z.object({ id: activiteDocumentIdValidator }))

const insertActiviteDocumentInternal = sql<
  Redefine<
    IInsertActiviteDocumentInternalQuery,
    {
      id: ActiviteDocumentId
      activite_document_type_id: ActiviteDocumentTypeId
      date: CaminoDate
      activite_id: ActiviteId
      description: string
      largeobject_id: number
    },
    { id: ActiviteDocumentId }
  >
>`
insert into activites_documents (id, activite_document_type_id, date, activite_id, description, largeobject_id)
    values ($ id !, $ activite_document_type_id !, $ date !, $ activite_id !, $ description !, $ largeobject_id !)
RETURNING
    id;
`

const activiteDocumentLargeObjectIdValidator = z.number().brand('ActiviteDocumentLargeObjectId')
type ActiviteDocumentLargeObjectId = z.infer<typeof activiteDocumentLargeObjectIdValidator>
const loidByActiviteDocumentIdValidator = z.object({ largeobject_id: activiteDocumentLargeObjectIdValidator, activite_id: activiteIdValidator })

export const getLargeobjectIdByActiviteDocumentId = async (activiteDocumentId: ActiviteDocumentId, pool: Pool, user: User): Promise<ActiviteDocumentLargeObjectId | null> => {
  const result = await dbQueryAndValidate(
    getLargeobjectIdByActiviteDocumentIdInternal,
    {
      activiteDocumentId,
    },
    pool,
    loidByActiviteDocumentIdValidator
  )

  if (result.length === 1) {
    const activiteDocument = result[0]

    const titreTypeId = () => callAndExit(titreTypeIdByActiviteId(activiteDocument.activite_id, pool))
    const administrationsLocales = () => administrationsLocalesByActiviteId(activiteDocument.activite_id, pool)
    const entreprisesTitulairesOuAmodiataires = () => entreprisesTitulairesOuAmoditairesByActiviteId(activiteDocument.activite_id, pool)
    if (await canReadTitreActivites(user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires)) {
      return result[0].largeobject_id
    }
  }

  return null
}
const getLargeobjectIdByActiviteDocumentIdInternal = sql<
  Redefine<IGetLargeobjectIdByActiviteDocumentIdInternalQuery, { activiteDocumentId: ActiviteDocumentId }, z.infer<typeof loidByActiviteDocumentIdValidator>>
>`
select
    d.largeobject_id,
    d.activite_id
from
    activites_documents d
where
    d.id = $ activiteDocumentId !
LIMIT 1
`

export const getActivitesSuper = (pool: Pool): Effect.Effect<ActiviteSuper[], CaminoError<EffectDbQueryAndValidateErrors>> =>
  Effect.Do.pipe(Effect.flatMap(() => effectDbQueryAndValidate(getActivitesSuperDb, {}, pool, activiteSuperValidator)))

const getActivitesSuperDb = sql<Redefine<IGetActivitesSuperDbQuery, {}, z.infer<typeof activiteSuperValidator>>>`
select
    t.nom as titre_nom,
    t.type_id as titre_type_id,
    ta.id,
    ta.annee,
    ta.type_id,
    ta.periode_id,
    ta.activite_statut_id
from titres_activites ta
left join titres t on ta.titre_id = t.id
where ta.suppression is true
order by t.nom asc, ta.annee asc, ta.periode_id asc
`
