import { dbManager } from '../../../tests/db-manager'
import { titreUpdate } from '../../database/queries/titres'
import { titreDemarcheCreate } from '../../database/queries/titres-demarches'
import { titreEtapeCreate } from '../../database/queries/titres-etapes'
import { userSuper } from '../../database/user-super'
import { restCall, restDeleteCall, restNewCall, restNewPostCall, restPostCall } from '../../../tests/_utils/index'
import { ADMINISTRATION_IDS } from 'camino-common/src/static/administrations'
import { ITitreDemarche, ITitreEtape } from '../../types'
import { entreprisesUpsert } from '../../database/queries/entreprises'
import { Knex } from 'knex'
import { caminoDateValidator, toCaminoDate } from 'camino-common/src/date'
import { afterAll, beforeAll, beforeEach, describe, test, expect, vi } from 'vitest'
import { newEntrepriseId } from 'camino-common/src/entreprise'
import type { Pool } from 'pg'
import { newDemarcheId, newEtapeId, newTitreId } from '../../database/models/_format/id-create'
import { HTTP_STATUS } from 'camino-common/src/http'
import { toCommuneId } from 'camino-common/src/static/communes'
import { insertCommune } from '../../database/queries/communes.queries'
import { titreSlugValidator } from 'camino-common/src/validators/titres'
import TitresDemarches from '../../database/models/titres-demarches'
import TitresEtapes from '../../database/models/titres-etapes'
import Titres from '../../database/models/titres'
import { ETAPE_IS_BROUILLON, ETAPE_IS_NOT_BROUILLON } from 'camino-common/src/etape'
import { insertTitreGraph } from '../../../tests/integration-test-helper'
import { DemarcheId } from 'camino-common/src/demarche'
import { testBlankUser } from 'camino-common/src/tests-utils'
import { DEMARCHES_TYPES_IDS } from 'camino-common/src/static/demarchesTypes'
import { ETAPES_TYPES } from 'camino-common/src/static/etapesTypes'
import { ETAPES_STATUTS } from 'camino-common/src/static/etapesStatuts'

console.info = vi.fn()
console.error = vi.fn()

let knex: Knex<any, unknown[]>
let dbPool: Pool
beforeAll(async () => {
  const { knex: knexInstance, pool } = await dbManager.populateDb()
  dbPool = pool
  knex = knexInstance

  await insertCommune(pool, { id: toCommuneId('97300'), nom: 'Une ville en Guyane', geometry: '010100000000000000000000000000000000000000' })
  const entreprises = await entreprisesUpsert([{ id: newEntrepriseId('plop'), nom: 'Mon Entreprise' }])
  await insertTitreGraph({
    id: newTitreId(),
    nom: 'mon titre simple',
    typeId: 'arm',
    titreStatutId: 'val',
    propsTitreEtapesIds: {},
  })

  await createTitreWithEtapes(
    'titre1',
    [
      {
        typeId: 'mfr',
        statutId: 'fai',
        isBrouillon: ETAPE_IS_NOT_BROUILLON,
        date: toCaminoDate('2022-01-01'),
        ordre: 0,
        administrationsLocales: [ADMINISTRATION_IDS['DGTM - GUYANE']],
        communes: [{ id: toCommuneId('97300'), surface: 12 }],
      },
      {
        typeId: 'men',
        statutId: 'fai',
        isBrouillon: ETAPE_IS_NOT_BROUILLON,
        date: toCaminoDate('2022-02-01'),
        ordre: 1,
      },
      {
        typeId: 'pfd',
        statutId: 'fai',
        isBrouillon: ETAPE_IS_NOT_BROUILLON,
        date: toCaminoDate('2022-02-10'),
        ordre: 2,
      },
      {
        typeId: 'mcp',
        statutId: 'com',
        isBrouillon: ETAPE_IS_NOT_BROUILLON,
        date: toCaminoDate('2022-03-10'),
        ordre: 3,
      },
    ],
    entreprises
  )
  await createTitreWithEtapes(
    'titre2',
    [
      {
        typeId: 'mfr',
        statutId: 'fai',
        isBrouillon: ETAPE_IS_NOT_BROUILLON,
        date: toCaminoDate('2022-01-01'),
        ordre: 0,
        administrationsLocales: [ADMINISTRATION_IDS['DGTM - GUYANE']],
      },
      {
        typeId: 'men',
        statutId: 'fai',
        isBrouillon: ETAPE_IS_NOT_BROUILLON,
        date: toCaminoDate('2022-02-01'),
        ordre: 1,
      },
      {
        typeId: 'pfd',
        statutId: 'fai',
        isBrouillon: ETAPE_IS_NOT_BROUILLON,
        date: toCaminoDate('2022-02-10'),
        ordre: 2,
      },
    ],
    entreprises
  )
})

afterAll(async () => {
  await dbManager.closeKnex()
})

const titreEtapesCreate = async (demarche: ITitreDemarche, etapes: Omit<ITitreEtape, 'id' | 'titreDemarcheId' | 'concurrence' | 'hasTitreFrom'>[]) => {
  const etapesCrees = []
  for (const etape of etapes) {
    etapesCrees.push(
      await titreEtapeCreate(
        {
          ...etape,
          titreDemarcheId: demarche.id,
        },
        userSuper,
        demarche.titreId
      )
    )
  }

  return etapesCrees
}

async function createTitreWithEtapes(
  nomTitre: string,
  etapes: (Omit<ITitreEtape, 'id' | 'titreDemarcheId' | 'concurrence' | 'demarcheIdsConsentement' | 'hasTitreFrom'> & { demarcheIdsConsentement?: DemarcheId[] })[],
  entreprises: any
) {
  const titreId = newTitreId()
  await insertTitreGraph({
    id: titreId,
    nom: nomTitre,
    typeId: 'arm',
    titreStatutId: 'mod',
    propsTitreEtapesIds: {},
    references: [
      {
        referenceTypeId: 'onf',
        nom: 'ONF',
      },
    ],
  })

  const titreDemarche = await titreDemarcheCreate({
    titreId,
    typeId: 'oct',
  })

  const [firstEtape, ...remainingEtapes] = etapes

  const etapesCrees = await titreEtapesCreate(titreDemarche, [
    { demarcheIdsConsentement: [], ...firstEtape, titulaireIds: [entreprises[0].id] },
    ...remainingEtapes.map(e => ({ demarcheIdsConsentement: [], ...e })),
  ])

  await knex('titres')
    .update({ propsTitreEtapesIds: { titulaires: etapesCrees[0].id, points: etapesCrees[0].id } })
    .where('id', titreId)

  return titreId
}

describe('titresAdministration', () => {
  test('teste la récupération des données pour les Administrations', async () => {
    const tested = await restCall(
      dbPool,
      '/rest/titresAdministrations',
      {},
      {
        role: 'admin',
        administrationId: ADMINISTRATION_IDS['DGTM - GUYANE'],
      }
    )

    expect(tested.statusCode).toBe(200)
    expect(tested.body).toHaveLength(2)
    expect(tested.body[0]).toMatchSnapshot({
      id: expect.any(String),
      slug: expect.any(String),
    })
    expect(tested.body[1]).toMatchSnapshot({
      id: expect.any(String),
      slug: expect.any(String),
    })
  })
})
describe('titresLiaisons', () => {
  test('peut lier deux titres', async () => {
    const getTitres = await restCall(
      dbPool,
      '/rest/titresAdministrations',
      {},
      {
        role: 'admin',
        administrationId: ADMINISTRATION_IDS['DGTM - GUYANE'],
      }
    )
    const titreId = getTitres.body[0].id

    const axmId = newTitreId()
    const axmNom = 'mon axm simple'
    await insertTitreGraph({
      id: axmId,
      nom: axmNom,
      typeId: 'axm',
      titreStatutId: 'val',
      propsTitreEtapesIds: {},
    })

    const tested = await restNewPostCall(
      dbPool,
      '/rest/titres/:id/titreLiaisons',
      { id: axmId },
      {
        role: 'admin',
        administrationId: ADMINISTRATION_IDS['DGTM - GUYANE'],
      },
      [titreId]
    )

    expect(tested.statusCode).toBe(200)
    expect(tested.body.amont).toHaveLength(1)
    expect(tested.body.aval).toHaveLength(0)
    expect(tested.body.amont[0]).toStrictEqual({
      id: titreId,
      nom: getTitres.body[0].nom,
    })

    const avalTested = await restNewCall(
      dbPool,
      '/rest/titres/:id/titreLiaisons',
      { id: titreId },
      {
        role: 'admin',
        administrationId: ADMINISTRATION_IDS['DGTM - GUYANE'],
      }
    )

    expect(avalTested.statusCode).toBe(200)
    expect(avalTested.body.amont).toHaveLength(0)
    expect(avalTested.body.aval).toHaveLength(1)
    expect(avalTested.body.aval[0]).toStrictEqual({
      id: axmId,
      nom: axmNom,
    })
  })
})

describe('titreModifier', () => {
  let id = newTitreId('')

  beforeEach(async () => {
    id = newTitreId()
    await insertTitreGraph({
      id,
      nom: 'mon titre',
      typeId: 'arm',
      titreStatutId: 'ind',
      propsTitreEtapesIds: {},
    })
  })

  test('ne peut pas modifier un titre (utilisateur anonyme)', async () => {
    const tested = await restPostCall(dbPool, '/rest/titres/:titreId', { titreId: id }, undefined, { id, nom: 'mon titre modifié', references: [] })

    expect(tested.statusCode).toBe(403)
  })

  test("ne peut pas modifier un titre (un utilisateur 'entreprise')", async () => {
    const tested = await restPostCall(dbPool, '/rest/titres/:titreId', { titreId: id }, { role: 'entreprise', entrepriseIds: [] }, { id, nom: 'mon titre modifié', references: [] })

    expect(tested.statusCode).toBe(404)
  })

  test('modifie un titre (un utilisateur userSuper)', async () => {
    const tested = await restPostCall(dbPool, '/rest/titres/:titreId', { titreId: id }, { role: 'super' }, { id, nom: 'mon titre modifié', references: [] })
    expect(tested.statusCode).toBe(204)
  })

  test("modifie un titre ARM (un utilisateur 'admin' PTMG)", async () => {
    const tested = await restPostCall(
      dbPool,
      '/rest/titres/:titreId',
      { titreId: id },
      {
        role: 'admin',
        administrationId: ADMINISTRATION_IDS['PÔLE TECHNIQUE MINIER DE GUYANE'],
      },
      { id, nom: 'mon titre modifié', references: [] }
    )
    expect(tested.statusCode).toBe(404)
  })

  test("ne peut pas modifier un titre ARM échu (un utilisateur 'admin' PTMG)", async () => {
    const id = newTitreId()
    await insertTitreGraph({ id, nom: 'mon titre échu', typeId: 'arm', titreStatutId: 'ech', propsTitreEtapesIds: {} })

    const tested = await restPostCall(
      dbPool,
      '/rest/titres/:titreId',
      { titreId: id },
      {
        role: 'admin',
        administrationId: ADMINISTRATION_IDS['PÔLE TECHNIQUE MINIER DE GUYANE'],
      },
      { id, nom: 'mon titre modifié', references: [] }
    )
    expect(tested.statusCode).toBe(404)
  })

  test("ne peut pas modifier un titre ARM (un utilisateur 'admin' DGCL/SDFLAE/FL1)", async () => {
    const tested = await restPostCall(
      dbPool,
      '/rest/titres/:titreId',
      { titreId: id },
      { role: 'admin', administrationId: ADMINISTRATION_IDS['DGCL/SDFLAE/FL1'] },
      { id, nom: 'mon titre modifié', references: [] }
    )
    expect(tested.statusCode).toBe(403)
  })
})

describe('titreSupprimer', () => {
  let id = newTitreId('')

  beforeEach(async () => {
    id = newTitreId()
    await insertTitreGraph({
      id,
      nom: 'mon titre',
      typeId: 'arm',
      titreStatutId: 'ind',
      propsTitreEtapesIds: {},
    })
  })

  test('ne peut pas supprimer un titre (utilisateur anonyme)', async () => {
    const tested = await restDeleteCall(dbPool, '/rest/titres/:titreId', { titreId: id }, undefined)

    expect(tested.statusCode).toBe(403)
  })

  test('peut supprimer un titre (utilisateur super)', async () => {
    const tested = await restDeleteCall(dbPool, '/rest/titres/:titreId', { titreId: id }, userSuper)

    expect(tested.statusCode).toBe(204)
  })

  test('ne peut pas supprimer un titre inexistant (utilisateur super)', async () => {
    const tested = await restDeleteCall(dbPool, '/rest/titres/:titreId', { titreId: newTitreId('toto') }, userSuper)
    expect(tested.statusCode).toBe(404)
  })
})

describe('getTitre', () => {
  test('ne peut pas récupérer un titre inexistant', async () => {
    const tested = await restCall(dbPool, '/rest/titres/:titreId', { titreId: newTitreId('not existing') }, userSuper)

    expect(tested.statusCode).toBe(404)
  })

  test('peut récupérer un titre', async () => {
    const titreId = newTitreId('other-titre-id')
    await Titres.query().insert({
      id: titreId,
      nom: 'mon nouveau titre',
      typeId: 'arm',
      titreStatutId: 'ind',
      slug: titreSlugValidator.parse('arm-slug'),
      propsTitreEtapesIds: {},
      archive: false,
    })

    const tested = await restCall(dbPool, '/rest/titres/:titreId', { titreId }, userSuper)

    expect(tested.statusCode).toBe(200)
    expect(tested.body).toMatchInlineSnapshot(`
      {
        "demarches": [],
        "id": "other-titre-id",
        "nb_activites_to_do": null,
        "nom": "mon nouveau titre",
        "references": [],
        "slug": "arm-slug",
        "titre_doublon": null,
        "titre_last_modified_date": null,
        "titre_statut_id": "ind",
        "titre_type_id": "arm",
      }
    `)
  })
  test('ne peut pas récupérer un titre archivé', async () => {
    const titreId = newTitreId('archive-titre-id')
    await Titres.query().insert({
      id: titreId,
      nom: 'mon nouveau titre',
      typeId: 'arm',
      titreStatutId: 'ind',
      slug: titreSlugValidator.parse('arm-slug'),
      propsTitreEtapesIds: {},
      archive: true,
    })

    const tested = await restCall(dbPool, '/rest/titres/:titreId', { titreId }, userSuper)

    expect(tested.statusCode).toBe(404)
    expect(tested.body).toMatchInlineSnapshot(`{}`)
  })

  test('peut récupérer un titre avec des administrations locales', async () => {
    const titreId = newTitreId('titre-id')
    await Titres.query().insert({
      id: titreId,
      nom: 'mon titre',
      typeId: 'arm',
      slug: titreSlugValidator.parse('slug'),
      titreStatutId: 'val',
      propsTitreEtapesIds: {},
      archive: false,
    })

    const demarcheId = newDemarcheId('demarche-id')
    await TitresDemarches.query().insert({
      id: demarcheId,
      titreId,
      typeId: 'oct',
    })

    const etapeId = newEtapeId('titre-etape-id')
    await TitresEtapes.query().insert({
      id: etapeId,
      titreDemarcheId: demarcheId,
      typeId: 'mfr',
      statutId: 'fai',
      date: toCaminoDate('2022-01-01'),
      ordre: 0,
      administrationsLocales: ['aut-97300-01', 'aut-mrae-guyane-01'],
    })

    await titreUpdate(titreId, { propsTitreEtapesIds: { points: etapeId } })

    const tested = await restCall(dbPool, '/rest/titres/:titreId', { titreId }, userSuper)

    expect(tested.statusCode).toBe(200)
    expect(tested.body).toMatchInlineSnapshot(`
      {
        "demarches": [
          {
            "demarche_date_debut": null,
            "demarche_date_fin": null,
            "demarche_statut_id": "ind",
            "demarche_type_id": "oct",
            "description": null,
            "etapes": [
              {
                "avis_documents": [],
                "date": "2022-01-01",
                "demarche_id_en_concurrence": null,
                "demarches_consentement": [],
                "entreprises_documents": [],
                "etape_documents": [],
                "etape_statut_id": "fai",
                "etape_type_id": "mfr",
                "fondamentale": {
                  "amodiataireIds": null,
                  "date_debut": null,
                  "date_fin": null,
                  "duree": null,
                  "perimetre": null,
                  "substances": [],
                  "titulaireIds": null,
                },
                "id": "titre-etape-id",
                "is_brouillon": false,
                "note": {
                  "is_avertissement": false,
                  "valeur": "",
                },
                "ordre": 0,
                "sections_with_values": [
                  {
                    "elements": [
                      {
                        "description": "",
                        "id": "mecanise",
                        "nom": "Prospection mécanisée",
                        "optionnel": false,
                        "type": "radio",
                        "value": null,
                      },
                      {
                        "description": "Nombre de franchissements de cours d'eau",
                        "id": "franchissements",
                        "nom": "Franchissements de cours d'eau",
                        "optionnel": true,
                        "type": "integer",
                        "value": null,
                      },
                    ],
                    "id": "arm",
                    "nom": "Caractéristiques ARM",
                  },
                ],
                "slug": "demarche-id-mfr99",
              },
            ],
            "id": "demarche-id",
            "ordre": 0,
            "slug": "titre-id-oct99",
          },
        ],
        "id": "titre-id",
        "nb_activites_to_do": null,
        "nom": "mon titre",
        "references": [],
        "slug": "slug",
        "titre_doublon": null,
        "titre_last_modified_date": null,
        "titre_statut_id": "val",
        "titre_type_id": "arm",
      }
    `)
  })
})

test('utilisateurTitreAbonner', async () => {
  const id = newTitreId()
  await insertTitreGraph({
    id,
    nom: 'mon autre titre',
    typeId: 'arm',
    slug: titreSlugValidator.parse('slug'),
    titreStatutId: 'val',
    propsTitreEtapesIds: {},
  })

  const tested = await restPostCall(dbPool, '/rest/titres/:titreId/abonne', { titreId: id }, userSuper, { abonne: true })

  expect(tested.statusCode).toBe(HTTP_STATUS.NO_CONTENT)
})

test('getUtilisateurTitreAbonner', async () => {
  const id = newTitreId()
  await insertTitreGraph({
    id,
    nom: 'mon autre titre',
    typeId: 'arm',
    slug: titreSlugValidator.parse('slug'),
    titreStatutId: 'val',
    propsTitreEtapesIds: {},
  })
  let tested = await restNewCall(dbPool, '/rest/titres/:titreId/abonne', { titreId: id }, userSuper)

  expect(tested.body).toBe(false)
  const abonnement = await restPostCall(dbPool, '/rest/titres/:titreId/abonne', { titreId: id }, userSuper, { abonne: true })

  expect(abonnement.statusCode).toBe(HTTP_STATUS.NO_CONTENT)

  tested = await restNewCall(dbPool, '/rest/titres/:titreId/abonne', { titreId: id }, userSuper)

  expect(tested.body).toBe(true)
  tested = await restNewCall(dbPool, '/rest/titres/:titreId/abonne', { titreId: id }, undefined)

  expect(tested.body).toMatchInlineSnapshot(`
    {
      "message": "Droit insuffisant pour accéder au titre ou titre inexistant",
      "status": 403,
    }
  `)
})

describe('titresSuper', () => {
  test('ne peut pas appeler si pas super', async () => {
    const tested = await restNewCall(dbPool, '/rest/titresSuper', {}, { ...testBlankUser, role: 'defaut' })
    expect(tested.body).toMatchInlineSnapshot(`
      {
        "message": "Seuls les super peuvent accéder à ces données",
        "status": 403,
      }
    `)
  })

  test('récupère une liste vide', async () => {
    const tested = await restNewCall(dbPool, '/rest/titresSuper', {}, userSuper)
    expect(tested.body).toMatchInlineSnapshot(`
    []
  `)
  })

  test('récupère la liste des titres avec une étape en brouillon', async () => {
    const id = newTitreId('titreIdAvecBrouillon')
    const demarcheId = newDemarcheId('demarcheIdAvecBrouillon')
    const etapeId = newEtapeId('etapeIdEnBrouillon')
    const idSansBrouillon = newTitreId('titreIdSansBrouillon')
    const demarcheIdSansBrouillon = newDemarcheId('demarcheIdSansBrouillon')
    const etapeIdSansBrouillon = newEtapeId('etapeIdNonBrouillon')
    await insertTitreGraph({
      id,
      nom: 'mon titre avec brouillon',
      typeId: 'arm',
      slug: titreSlugValidator.parse('slug'),
      titreStatutId: 'val',
      propsTitreEtapesIds: {},
      demarches: [
        {
          id: demarcheId,
          typeId: DEMARCHES_TYPES_IDS.Octroi,
          titreId: id,
          etapes: [
            {
              id: etapeId,
              typeId: ETAPES_TYPES.demande,
              titreDemarcheId: demarcheId,
              statutId: ETAPES_STATUTS.FAIT,
              isBrouillon: ETAPE_IS_BROUILLON,
              date: caminoDateValidator.parse('2025-01-10'),
            },
          ],
        },
      ],
    })

    await insertTitreGraph({
      id: idSansBrouillon,
      nom: 'mon titre sans brouillon',
      typeId: 'arm',
      slug: titreSlugValidator.parse('slug-sans-brouillon'),
      titreStatutId: 'val',
      propsTitreEtapesIds: {},
      demarches: [
        {
          id: demarcheIdSansBrouillon,
          typeId: DEMARCHES_TYPES_IDS.Octroi,
          titreId: idSansBrouillon,
          etapes: [
            {
              id: etapeIdSansBrouillon,
              typeId: ETAPES_TYPES.demande,
              titreDemarcheId: demarcheIdSansBrouillon,
              statutId: ETAPES_STATUTS.FAIT,
              isBrouillon: ETAPE_IS_NOT_BROUILLON,
              date: caminoDateValidator.parse('2025-02-28'),
            },
          ],
        },
      ],
    })

    const tested = await restNewCall(dbPool, '/rest/titresSuper', {}, userSuper)
    expect(tested.body).toMatchInlineSnapshot(`
      [
        {
          "demarche_slug": "titreIdAvecBrouillon-oct99",
          "demarche_type_id": "oct",
          "etape_date": "2025-01-10",
          "etape_slug": "demarcheIdAvecBrouillon-mfr99",
          "etape_type_id": "mfr",
          "titre_nom": "mon titre avec brouillon",
          "titre_slug": "slug",
          "titre_statut_id": "val",
          "titre_type_id": "arm",
        },
      ]
    `)
  })
})
