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 { 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_NOT_BROUILLON } from 'camino-common/src/etape'
import { insertTitreGraph } from '../../../tests/integration-test-helper'
import { DemarcheId } from 'camino-common/src/demarche'

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,
    }
  `)
})
