/**
 * E2E R1 — référentiel : parcours de recette (site → zone → appareil → organes)
 * + invariants (profondeur 2, types d'organes, matrice de permissions vivante).
 */
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('Référentiel (e2e)', () => {
  let app: INestApplication;
  let admin: string; // jetons
  let technicien: string;
  const prisma = new PrismaClient();
  const http = () => request(app.getHttpServer());
  const suffix = Date.now().toString(36); // données de test uniques et repérables

  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);
      const res = await http().post('/auth/demo-login').send({ userId: compte.id });
      return res.body.accessToken as string;
    };
    admin = await login('Administrateur');
    technicien = await login('Technicien');
  });

  afterAll(async () => {
    await prisma.asset.deleteMany({ where: { reference: { contains: suffix } } });
    await prisma.location.deleteMany({ where: { name: { contains: suffix } } });
    await app?.close();
    await prisma.$disconnect();
  });

  it('parcours de recette : site → zone → appareil avec organes', async () => {
    const site = await http()
      .post('/locations')
      .set('Authorization', `Bearer ${admin}`)
      .send({
        name: `Site Recette ${suffix}`,
        city: 'Casablanca',
        latitude: 33.59,
        longitude: -7.61,
      })
      .expect(201);
    expect(site.body.parentId).toBeNull();

    const zone = await http()
      .post('/locations')
      .set('Authorization', `Bearer ${admin}`)
      .send({ name: `Hall Recette ${suffix}`, parentId: site.body.id })
      .expect(201);

    const { body: cats } = await http()
      .get('/categories')
      .set('Authorization', `Bearer ${admin}`);
    const equipement = cats.categories.find(
      (c: { kind: string }) => c.kind === 'EQUIPMENT',
    );
    const typeOrgane = cats.categories.find(
      (c: { kind: string; isActive: boolean }) => c.kind === 'COMPONENT_TYPE' && c.isActive,
    );

    const asset = await http()
      .post('/assets')
      .set('Authorization', `Bearer ${admin}`)
      .send({
        reference: `T-${suffix}`,
        brand: 'Otis',
        model: 'Gen2',
        categoryId: equipement.id,
        locationId: zone.body.id,
        components: [{ typeId: typeOrgane.id, designation: 'Organe de test' }],
      })
      .expect(201);
    expect(asset.body.components).toHaveLength(1);
    expect(asset.body.siteName).toBe(`Site Recette ${suffix}`);

    // Ajout puis retrait d'un organe
    const organe = await http()
      .post(`/assets/${asset.body.id}/components`)
      .set('Authorization', `Bearer ${admin}`)
      .send({ typeId: typeOrgane.id })
      .expect(201);
    await http()
      .delete(`/assets/${asset.body.id}/components/${organe.body.id}`)
      .set('Authorization', `Bearer ${admin}`)
      .expect(204);
  });

  it('refuse une hiérarchie de profondeur 3 (site → zone → ?)', async () => {
    const site = await http()
      .post('/locations')
      .set('Authorization', `Bearer ${admin}`)
      .send({ name: `Site Profond ${suffix}` })
      .expect(201);
    const zone = await http()
      .post('/locations')
      .set('Authorization', `Bearer ${admin}`)
      .send({ name: `Zone Profonde ${suffix}`, parentId: site.body.id })
      .expect(201);
    await http()
      .post('/locations')
      .set('Authorization', `Bearer ${admin}`)
      .send({ name: `Sous-zone ${suffix}`, parentId: zone.body.id })
      .expect(400);
  });

  it('refuse un appareil dont la catégorie n’est pas un équipement', async () => {
    const { body: cats } = await http()
      .get('/categories')
      .set('Authorization', `Bearer ${admin}`);
    const typeOrgane = cats.categories.find((c: { kind: string }) => c.kind === 'COMPONENT_TYPE');
    const { body: locs } = await http()
      .get('/locations')
      .set('Authorization', `Bearer ${admin}`);
    await http()
      .post('/assets')
      .set('Authorization', `Bearer ${admin}`)
      .send({
        reference: `KO-${suffix}`,
        brand: 'Test',
        categoryId: typeOrgane.id, // kind COMPONENT_TYPE → refus
        locationId: locs.locations[0].id,
      })
      .expect(400);
  });

  it('la matrice vit : un Technicien lit le parc mais ne crée rien', async () => {
    await http().get('/assets').set('Authorization', `Bearer ${technicien}`).expect(200);
    await http().get('/locations').set('Authorization', `Bearer ${technicien}`).expect(200);
    await http()
      .post('/locations')
      .set('Authorization', `Bearer ${technicien}`)
      .send({ name: `Interdit ${suffix}` })
      .expect(403);
    await http().get('/users').set('Authorization', `Bearer ${technicien}`).expect(403);
  });

  it('le seed de la maquette est en place (A1 et ses organes, statuts)', async () => {
    const { body } = await http()
      .get('/assets')
      .set('Authorization', `Bearer ${admin}`)
      .expect(200);
    const a1 = body.assets.find((a: { reference: string }) => a.reference === 'A1');
    expect(a1.siteName).toBe('Résidence Al Manar');
    expect(a1.componentCount).toBe(4);
    const b2 = body.assets.find((a: { reference: string }) => a.reference === 'B2');
    expect(b2.status).toBe('OUT_OF_SERVICE');
  });

  it('catégorie utilisée : désactivable, jamais supprimable (la route n’existe pas)', async () => {
    const { body: cats } = await http()
      .get('/categories')
      .set('Authorization', `Bearer ${admin}`);
    const used = cats.categories.find((c: { usageCount: number }) => c.usageCount > 0);
    await http()
      .delete(`/categories/${used.id}`)
      .set('Authorization', `Bearer ${admin}`)
      .expect(404); // pas de DELETE dans le contrat
    const off = await http()
      .patch(`/categories/${used.id}`)
      .set('Authorization', `Bearer ${admin}`)
      .send({ isActive: false })
      .expect(200);
    expect(off.body.isActive).toBe(false);
    await http()
      .patch(`/categories/${used.id}`)
      .set('Authorization', `Bearer ${admin}`)
      .send({ isActive: true })
      .expect(200);
  });
});
