/**
 * E2E R2.2 — préventif : génération IDEMPOTENTE (l'unicité est en base),
 * premier contrôle (tout), périodicités ancrées sur la mise en service ;
 * compteurs strictement croissants.
 * Les générations de test utilisent des mois lointains (2031) puis sont purgées.
 */
process.env.DEMO_MODE = 'true';

import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { PrismaClient } from '@prisma/client';
import request from 'supertest';
import { seed } from '../prisma/seed';
import { AppModule } from '../src/app.module';

describe('Préventif & compteurs (e2e)', () => {
  let app: INestApplication;
  let admin: string;
  let ahmed: string;
  const prisma = new PrismaClient();
  const http = () => request(app.getHttpServer());
  const auth = (t: string) => ({ Authorization: `Bearer ${t}` });

  beforeAll(async () => {
    await seed(prisma);
    await prisma.workOrder.deleteMany({ where: { periodKey: { startsWith: '2031-' } } });
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule.forRoot()],
    }).compile();
    app = moduleRef.createNestApplication();
    await app.init();
    const { body } = await http().get('/auth/demo-accounts');
    const login = async (roleName: string) => {
      const compte = body.accounts.find((a: { roleName: string }) => a.roleName === roleName);
      return (await http().post('/auth/demo-login').send({ userId: compte.id })).body
        .accessToken as string;
    };
    admin = await login('Administrateur');
    ahmed = await login('Technicien');
  });

  afterAll(async () => {
    await prisma.workOrder.deleteMany({ where: { periodKey: { startsWith: '2031-' } } });
    await prisma.taskTemplate.deleteMany({ where: { label: { contains: 'TEST-' } } });
    await prisma.meterReading.deleteMany({ where: { value: { gte: 90000000 } } });
    await app?.close();
    await prisma.$disconnect();
  });

  // Les assertions portent sur les 8 appareils du SEED : d'autres specs Jest
  // créent/détruisent des appareils en parallèle sur la même base.
  const SEED_REFS = ['A1', 'A2', 'B1', 'B2', 'C1', 'D1', 'E2', 'M1'];
  const grillesSeed = (period: string) =>
    prisma.workOrder.count({
      where: { periodKey: period, asset: { reference: { in: SEED_REFS } } },
    });

  it('génère une grille par appareil sous contrat — et regénérer ne double RIEN', async () => {
    const premiere = await http()
      .post('/preventive/generate')
      .set(auth(admin))
      .send({ month: '2031-01' })
      .expect(200);
    expect(premiere.body.created).toBeGreaterThanOrEqual(SEED_REFS.length);
    expect(premiere.body.references.length).toBe(premiere.body.created);
    expect(await grillesSeed('2031-01')).toBe(SEED_REFS.length);

    const seconde = await http()
      .post('/preventive/generate')
      .set(auth(admin))
      .send({ month: '2031-01' })
      .expect(200);
    expect(seconde.body.skipped).toBeGreaterThanOrEqual(SEED_REFS.length);
    expect(await grillesSeed('2031-01')).toBe(SEED_REFS.length); // toujours 8, pas 16
  });

  it('premier contrôle (toutes les tâches) pour un appareil sans historique ; grille normale sinon', async () => {
    const templatesActifs = await prisma.taskTemplate.count({ where: { isActive: true } });
    const mensuelles = await prisma.taskTemplate.count({
      where: { isActive: true, periodMonths: 1 },
    });

    // Un appareil VIERGE créé par ce test (aucune génération n'a pu le toucher)
    const categorie = await prisma.category.findFirstOrThrow({ where: { kind: 'EQUIPMENT' } });
    const site = await prisma.location.findFirstOrThrow({ where: { parentId: null } });
    const vierge = await prisma.asset.create({
      data: {
        reference: `PVT-${Date.now().toString(36)}`,
        brand: 'Test',
        categoryId: categorie.id,
        locationId: site.id,
      },
    });
    try {
      await http()
        .post('/preventive/generate')
        .set(auth(admin))
        .send({ month: '2031-09' })
        .expect(200);
      const grille = await prisma.workOrder.findUniqueOrThrow({
        where: { assetId_periodKey: { assetId: vierge.id, periodKey: '2031-09' } },
        include: { _count: { select: { checklist: true } } },
      });
      expect(grille.title).toContain('Premier contrôle');
      expect(grille._count.checklist).toBe(templatesActifs);
    } finally {
      await prisma.workOrder.deleteMany({ where: { assetId: vierge.id } });
      await prisma.asset.delete({ where: { id: vierge.id } });
    }

    // A1 a toujours un historique préventif (seed) → grille normale.
    // Mise en service mars 2020 : janvier 2031 (écart 130 mois) n'est le mois
    // ni du 3, ni du 6, ni du 12 → seules les tâches MENSUELLES sont dues.
    const a1 = await prisma.asset.findUniqueOrThrow({ where: { reference: 'A1' } });
    const grilleA1 = await prisma.workOrder.findUniqueOrThrow({
      where: { assetId_periodKey: { assetId: a1.id, periodKey: '2031-01' } },
      include: { _count: { select: { checklist: true } } },
    });
    expect(grilleA1.title).toContain('Grille du mois');
    expect(grilleA1._count.checklist).toBe(mensuelles);
  });

  it('mois anniversaire de mise en service : les périodicités longues tombent (mars 2031 pour A1)', async () => {
    await http()
      .post('/preventive/generate')
      .set(auth(admin))
      .send({ month: '2031-03' })
      .expect(200);
    // écart mars 2020 → mars 2031 = 132 mois : divisible par 3, 6 et 12
    const templatesActifs = await prisma.taskTemplate.count({ where: { isActive: true } });
    const a1 = await prisma.asset.findUniqueOrThrow({ where: { reference: 'A1' } });
    const grille = await prisma.workOrder.findUniqueOrThrow({
      where: { assetId_periodKey: { assetId: a1.id, periodKey: '2031-03' } },
      include: { checklist: true },
    });
    expect(grille.checklist.length).toBe(templatesActifs);
    expect(grille.checklist.some((c) => c.label === 'Essai du parachute')).toBe(true);
  });

  it('gabarits : ajout, doublon 409, désactivation (exclu des générations suivantes), 404', async () => {
    const cree = await http()
      .post('/preventive/templates')
      .set(auth(admin))
      .send({ label: 'TEST-Contrôle éclairage gaine', periodMonths: 1 })
      .expect(201);
    await http()
      .post('/preventive/templates')
      .set(auth(admin))
      .send({ label: 'TEST-Contrôle éclairage gaine', periodMonths: 1 })
      .expect(409);
    await http()
      .patch(`/preventive/templates/${cree.body.id}`)
      .set(auth(admin))
      .send({ isActive: false })
      .expect(200);
    await http()
      .patch('/preventive/templates/00000000-0000-4000-8000-000000000000')
      .set(auth(admin))
      .send({ label: 'X' })
      .expect(404);
    // désactivé → absent de la génération d'un nouveau mois
    await http()
      .post('/preventive/generate')
      .set(auth(admin))
      .send({ month: '2031-05' })
      .expect(200);
    const labels = await prisma.checklistItem.findMany({
      where: { workOrder: { periodKey: '2031-05' } },
      select: { label: true },
    });
    expect(labels.some((l) => l.label.startsWith('TEST-'))).toBe(false);
    // le statut du mois courant répond (structure)
    const status = await http().get('/preventive/status').set(auth(ahmed)).expect(200);
    expect(status.body).toHaveProperty('generated');
    expect(status.body).toHaveProperty('late');
  });

  it('compteurs : relevé croissant accepté, régression refusée, appareil inconnu 404', async () => {
    const { body: assets } = await http().get('/assets').set(auth(ahmed));
    const a1 = assets.assets.find((a: { reference: string }) => a.reference === 'A1');

    const liste = await http().get(`/assets/${a1.id}/meters`).set(auth(ahmed)).expect(200);
    expect(liste.body.meters).toHaveLength(2); // les 2 compteurs, même vides
    const heures = liste.body.meters.find((m: { kind: string }) => m.kind === 'RUNNING_HOURS');
    const dernier = heures.readings[0]?.value ?? 0;

    const apres = await http()
      .post(`/assets/${a1.id}/meter-readings`)
      .set(auth(ahmed))
      .send({ kind: 'RUNNING_HOURS', value: 90000000 + dernier })
      .expect(201);
    const majHeures = apres.body.meters.find(
      (m: { kind: string }) => m.kind === 'RUNNING_HOURS',
    );
    expect(majHeures.readings[0].value).toBe(90000000 + dernier);
    expect(majHeures.readings[0].readBy.displayName).toBe('Ahmed Benali');

    await http()
      .post(`/assets/${a1.id}/meter-readings`)
      .set(auth(ahmed))
      .send({ kind: 'RUNNING_HOURS', value: dernier })
      .expect(400);
    await http()
      .get('/assets/00000000-0000-4000-8000-000000000000/meters')
      .set(auth(ahmed))
      .expect(404);
  });
});
