/**
 * E2E R3 (recette) — recherche globale : chaque famille de résultats est
 * filtrée par la matrice, les OT respectent « voir autre » ; au passage,
 * rattachements Tiers (siteNames) et période analytics paramétrable.
 */
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('Recherche globale & corrections de recette R3 (e2e)', () => {
  let app: INestApplication;
  let yasmine: string; // Administrateur
  let salma: string; // Dispatcher (voir autre)
  let youssef: string; // Technicien limité : SES OT, ASSETS view, pas de LOCATIONS
  let karim: string; // Demandeur : aucune des trois familles
  let otId: string;
  const prisma = new PrismaClient();
  const http = () => request(app.getHttpServer());
  const auth = (t: string) => ({ Authorization: `Bearer ${t}` });
  const suffix = `rech-${Date.now().toString(36)}`;

  beforeAll(async () => {
    await seed(prisma);
    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;
    };
    yasmine = await login('Administrateur');
    salma = await login('Dispatcher');
    youssef = await login('Technicien limité');
    karim = await login('Demandeur');

    // Un OT au titre unique, assigné à Ahmed (PAS à Youssef).
    const { body: assets } = await http().get('/assets').set(auth(salma));
    const a1 = assets.assets.find((a: { reference: string }) => a.reference === 'A1');
    const ahmed = body.accounts.find((a: { roleName: string }) => a.roleName === 'Technicien');
    const cree = await http()
      .post('/work-orders')
      .set(auth(salma))
      .send({
        title: `Recherche E2E ${suffix}`,
        type: 'CORRECTIVE',
        priority: 'LOW',
        assetId: a1.id,
        assigneeIds: [ahmed.id],
      })
      .expect(201);
    otId = cree.body.id;
  });

  afterAll(async () => {
    await prisma.workOrder.deleteMany({ where: { title: { contains: suffix } } });
    await app?.close();
    await prisma.$disconnect();
  });

  it('dispatcher (voir autre) : OT par titre, appareil par référence, site insensible à la casse', async () => {
    const parTitre = await http().get(`/search?q=${suffix}`).set(auth(salma)).expect(200);
    expect(parTitre.body.workOrders.map((w: { id: string }) => w.id)).toContain(otId);

    const parRef = await http().get('/search?q=A1').set(auth(salma)).expect(200);
    expect(parRef.body.assets.some((a: { reference: string }) => a.reference === 'A1')).toBe(true);
    expect(parRef.body.assets[0].siteName).toBeTruthy();

    const parSite = await http().get('/search?q=atlas').set(auth(salma)).expect(200);
    expect(parSite.body.sites.some((s: { name: string }) => s.name === 'Tour Atlas')).toBe(true);
  });

  it('technicien limité : ne trouve pas l’OT d’un autre, ni les sites ; le parc oui', async () => {
    const res = await http().get(`/search?q=${suffix}`).set(auth(youssef)).expect(200);
    expect(res.body.workOrders).toHaveLength(0); // pas « voir autre »

    const parc = await http().get('/search?q=A1').set(auth(youssef)).expect(200);
    expect(parc.body.assets.length).toBeGreaterThan(0); // ASSETS view
    const sites = await http().get('/search?q=atlas').set(auth(youssef)).expect(200);
    expect(sites.body.sites).toHaveLength(0); // pas de LOCATIONS view
  });

  it('demandeur : 200 mais aucune famille (aucune permission view)', async () => {
    const res = await http().get('/search?q=atlas').set(auth(karim)).expect(200);
    expect(res.body).toEqual({ workOrders: [], assets: [], sites: [] });
  });

  it('moins de 2 caractères : réponse vide, pas de requête inutile', async () => {
    const res = await http().get('/search?q=a').set(auth(salma)).expect(200);
    expect(res.body).toEqual({ workOrders: [], assets: [], sites: [] });
  });

  it('sans jeton : 401 (API fermée par défaut)', async () => {
    await http().get('/search?q=atlas').expect(401);
  });

  it('tiers : les syndics portent leurs sites (« Rattachements » de la maquette)', async () => {
    const { body } = await http().get('/partners').set(auth(yasmine)).expect(200);
    const atlas = body.partners.find(
      (p: { name: string }) => p.name === 'Atlas Property Management',
    );
    expect(atlas.siteNames).toEqual(['Tour Atlas']);
    const fournisseur = body.partners.find((p: { name: string }) => p.name === 'Lubmaroc');
    expect(fournisseur.siteNames).toEqual([]);
  });

  it('analytics : période 3/6/12 mois, 12 par défaut, valeur inconnue repliée sur 12', async () => {
    const trois = await http().get('/analytics/summary?months=3').set(auth(yasmine)).expect(200);
    expect(trois.body.months).toBe(3);
    expect(trois.body.costsByMonth).toHaveLength(3);

    const defaut = await http().get('/analytics/summary').set(auth(yasmine)).expect(200);
    expect(defaut.body.months).toBe(12);
    expect(defaut.body.costsByMonth).toHaveLength(12);

    const inconnu = await http().get('/analytics/summary?months=7').set(auth(yasmine)).expect(200);
    expect(inconnu.body.months).toBe(12);
  });

  it('sites : le rattachement n’accepte qu’un tiers Client / syndic', async () => {
    const { body: partners } = await http().get('/partners').set(auth(yasmine));
    const fournisseur = partners.partners.find((p: { kind: string }) => p.kind === 'SUPPLIER');
    const { body: locations } = await http().get('/locations').set(auth(yasmine));
    const site = locations.locations.find(
      (l: { parentId: string | null; name: string }) => !l.parentId && l.name === 'Anfa Place',
    );
    await http()
      .patch(`/locations/${site.id}`)
      .set(auth(yasmine))
      .send({ partnerId: fournisseur.id })
      .expect(400);
  });
});

describe('Verrou optimiste D2 (e2e) — la version protège les saisies mobiles', () => {
  let app: INestApplication;
  let salma: string;
  let ahmed: string;
  let otId: string;
  const prisma = new PrismaClient();
  const http = () => request(app.getHttpServer());
  const auth = (t: string) => ({ Authorization: `Bearer ${t}` });
  const suffix = `verrou-${Date.now().toString(36)}`;

  beforeAll(async () => {
    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;
    };
    salma = await login('Dispatcher');
    ahmed = await login('Technicien');
    const { body: assets } = await http().get('/assets').set(auth(salma));
    const a1 = assets.assets.find((a: { reference: string }) => a.reference === 'A1');
    const technicien = body.accounts.find((a: { roleName: string }) => a.roleName === 'Technicien');
    const cree = await http()
      .post('/work-orders')
      .set(auth(salma))
      .send({
        title: `Verrou E2E ${suffix}`,
        type: 'CORRECTIVE',
        assetId: a1.id,
        assigneeIds: [technicien.id],
      })
      .expect(201);
    otId = cree.body.id;
  });

  afterAll(async () => {
    await prisma.workOrder.deleteMany({ where: { title: { contains: suffix } } });
    await app?.close();
    await prisma.$disconnect();
  });

  it('version à jour : la transition passe ; le détail expose updatedAt', async () => {
    const detail = await http().get(`/work-orders/${otId}`).set(auth(ahmed)).expect(200);
    expect(detail.body.updatedAt).toBeTruthy();
    await http()
      .post(`/work-orders/${otId}/transition`)
      .set(auth(ahmed))
      .send({ to: 'IN_PROGRESS', baseUpdatedAt: detail.body.updatedAt })
      .expect(200);
  });

  it('OT modifié entre-temps (commentaire) : 409 « Conflit de version » contextualisé', async () => {
    const detail = await http().get(`/work-orders/${otId}`).set(auth(ahmed)).expect(200);
    const versionLue = detail.body.updatedAt as string;
    // Salma commente pendant qu'Ahmed est « hors-ligne » — la version avance
    await http()
      .post(`/work-orders/${otId}/comments`)
      .set(auth(salma))
      .send({ message: 'Réassigné après appel du syndic' })
      .expect(201);
    const refus = await http()
      .post(`/work-orders/${otId}/transition`)
      .set(auth(ahmed))
      .send({ to: 'ON_HOLD', baseUpdatedAt: versionLue })
      .expect(409);
    expect(refus.body.message).toContain('Conflit de version');
    expect(refus.body.message).toContain('Salma');
    // L'OT n'a PAS bougé — rien d'écrasé en silence
    const apres = await http().get(`/work-orders/${otId}`).set(auth(ahmed)).expect(200);
    expect(apres.body.status).toBe('IN_PROGRESS');
  });

  it('le bilan et la coche font avancer la version (sinon le verrou est aveugle)', async () => {
    const avant = (await http().get(`/work-orders/${otId}`).set(auth(ahmed))).body.updatedAt;
    const { body: refs } = await http().get('/reference-values').set(auth(ahmed));
    const porte = refs.referenceValues.find(
      (v: { field: string; isActive: boolean }) => v.field === 'DOOR_STATE' && v.isActive,
    );
    await http()
      .put(`/work-orders/${otId}/report`)
      .set(auth(ahmed))
      .send({ doorStateId: porte.id })
      .expect(200);
    const apres = (await http().get(`/work-orders/${otId}`).set(auth(ahmed))).body.updatedAt;
    expect(new Date(apres).getTime()).toBeGreaterThan(new Date(avant).getTime());
  });

  it('sans baseUpdatedAt (web) : comportement inchangé, même après modification', async () => {
    await http()
      .post(`/work-orders/${otId}/comments`)
      .set(auth(salma))
      .send({ message: 'Nouvelle note' })
      .expect(201);
    await http()
      .post(`/work-orders/${otId}/transition`)
      .set(auth(ahmed))
      .send({ to: 'ON_HOLD' })
      .expect(200);
  });
});
