/**
 * Seed R0 — idempotent (upserts) : 7 rôles, matrice COMPLÈTE (une ligne par
 * rôle × catégorie, invariant R0) et 7 comptes de démonstration (ADR-002).
 * La matrice ci-dessous est le point de DÉPART pédagogique : elle vit en base
 * et sera administrable (R1) — la modifier ici ne change pas une base déjà
 * seedée (les lignes existantes ne sont pas écrasées, voir plus bas).
 * Usage : pnpm seed  (ou prisma db seed)
 */
import 'dotenv/config';
import { PrismaClient } from '@prisma/client';
import {
  OBJECT_CATEGORIES,
  ROLE_NAMES,
  type ObjectCategory,
  type RoleName,
} from '@siop/shared';
import * as argon2 from 'argon2';

type Grant = Partial<{
  view: boolean;
  viewOther: boolean;
  create: boolean;
  edit: boolean;
  delete: boolean;
}>;

const FULL: Grant = { view: true, viewOther: true, create: true, edit: true, delete: true };
const READ: Grant = { view: true, viewOther: true };

/** Matrice de départ (rôle → catégorie → droits ; absent = tout à false). */
const MATRIX: Record<RoleName, Partial<Record<ObjectCategory, Grant>>> = {
  Administrateur: Object.fromEntries(
    OBJECT_CATEGORIES.map((c) => [c, FULL]),
  ) as Record<ObjectCategory, Grant>,
  Gestionnaire: {
    WORK_ORDERS: FULL,
    REQUESTS: FULL,
    ASSETS: FULL,
    LOCATIONS: FULL,
    METERS: FULL,
    PARTS: FULL,
    PURCHASE_ORDERS: FULL,
    PEOPLE_TEAMS: { view: true, viewOther: true, create: true, edit: true },
    ANALYTICS: READ,
    SETTINGS: { view: true },
  },
  Dispatcher: {
    WORK_ORDERS: { view: true, viewOther: true, create: true, edit: true },
    REQUESTS: { view: true, viewOther: true, create: true, edit: true },
    ASSETS: READ,
    LOCATIONS: READ,
    METERS: READ,
    PARTS: { view: true },
    PEOPLE_TEAMS: READ,
    ANALYTICS: READ,
  },
  Technicien: {
    WORK_ORDERS: { view: true, edit: true }, // ses OT uniquement (viewOther=false)
    REQUESTS: { view: true },
    ASSETS: READ,
    LOCATIONS: READ,
    METERS: { view: true, viewOther: true, create: true }, // relevés
    PARTS: { view: true },
  },
  'Technicien limité': {
    WORK_ORDERS: { view: true, edit: true }, // ses OT, sans consultation du parc
    ASSETS: { view: true },
  },
  Demandeur: {
    REQUESTS: { view: true, create: true }, // ses demandes uniquement
  },
  'Vue seule': {
    WORK_ORDERS: READ,
    REQUESTS: READ,
    ASSETS: READ,
    LOCATIONS: READ,
    METERS: READ,
    PARTS: READ,
    PURCHASE_ORDERS: READ,
    PEOPLE_TEAMS: READ,
    ANALYTICS: READ,
  },
};

/** 7 comptes démo — un par rôle. Les noms sont CEUX de la maquette validée
 *  (maquette-web.html) : la revue pixel compare écrans et maquettes. */
const DEMO_USERS: { email: string; displayName: string; role: RoleName }[] = [
  { email: 'admin@demo.siop.ma', displayName: 'Yasmine Alaoui', role: 'Administrateur' },
  { email: 'dispatcher@demo.siop.ma', displayName: 'Salma Idrissi', role: 'Dispatcher' },
  { email: 'technicien@demo.siop.ma', displayName: 'Ahmed Benali', role: 'Technicien' },
  { email: 'technicien-limite@demo.siop.ma', displayName: 'Youssef Tazi', role: 'Technicien limité' },
  { email: 'gestionnaire@demo.siop.ma', displayName: 'Nadia Berrada', role: 'Gestionnaire' },
  { email: 'demandeur@demo.siop.ma', displayName: 'Karim Doukkali', role: 'Demandeur' },
  { email: 'vue-seule@demo.siop.ma', displayName: 'Rachid Bennis', role: 'Vue seule' },
];

export async function seed(prisma: PrismaClient): Promise<void> {
  const roleIds = new Map<RoleName, string>();
  for (const name of ROLE_NAMES) {
    const role = await prisma.role.upsert({
      where: { name },
      update: {},
      create: { name },
    });
    roleIds.set(name, role.id);
  }

  // Matrice complète : une ligne par rôle × catégorie. Les lignes existantes
  // ne sont PAS écrasées (la base est la source de vérité, pas ce fichier).
  for (const name of ROLE_NAMES) {
    const roleId = roleIds.get(name)!;
    for (const category of OBJECT_CATEGORIES) {
      const g = MATRIX[name][category] ?? {};
      await prisma.permission.upsert({
        where: { roleId_objectCategory: { roleId, objectCategory: category } },
        update: {},
        create: {
          roleId,
          objectCategory: category,
          canView: g.view ?? false,
          canViewOther: g.viewOther ?? false,
          canCreate: g.create ?? false,
          canEdit: g.edit ?? false,
          canDelete: g.delete ?? false,
        },
      });
    }
  }

  // Mot de passe commun des comptes démo (la connexion classique reste testable).
  const passwordHash = await argon2.hash(
    process.env.SEED_DEMO_PASSWORD ?? 'Demo!2026',
  );
  await seedUsers(prisma, roleIds, passwordHash);
  await seedReferentiel(prisma);
  await seedExploitation(prisma);
  await seedGestion(prisma);
}

async function seedUsers(
  prisma: PrismaClient,
  roleIds: Map<RoleName, string>,
  passwordHash: string,
): Promise<void> {
  for (const u of DEMO_USERS) {
    // Les comptes démo appartiennent au seed : nom et rôle sont réalignés
    // à chaque exécution (jamais le mot de passe d'un compte existant).
    await prisma.user.upsert({
      where: { email: u.email },
      update: { displayName: u.displayName, roleId: roleIds.get(u.role)! },
      create: {
        email: u.email,
        displayName: u.displayName,
        passwordHash,
        roleId: roleIds.get(u.role)!,
        isDemo: true,
      },
    });
  }
}

// ————— R1 — Référentiel (données des maquettes validées) —————

const EQUIPMENT_CATEGORIES = [
  'Ascenseur électrique',
  'Ascenseur hydraulique',
  'Monte-charge',
  'EPMR (plateforme PMR)',
];
const COMPONENT_TYPES = [
  'Portes cabine / palières',
  'Treuil / machinerie',
  'Parachute',
  'Armoire de commande',
  'Boutons & signalisation',
];

/** site → zones ; positions réelles Casablanca/Mohammedia. */
const SITES: {
  name: string;
  address: string;
  city: string;
  guardianName?: string;
  guardianPhone?: string;
  latitude: number;
  longitude: number;
  zones: string[];
}[] = [
  {
    name: 'Tour Atlas', address: 'Bd de la Corniche, Aïn Diab', city: 'Casablanca',
    guardianName: 'Karim Doukkali', guardianPhone: '06 61 23 45 67',
    latitude: 33.6062, longitude: -7.6706,
    zones: ['Hall principal', 'Tour bureaux (étages 1-24)', 'Parking sous-sol', 'Résidence (aile est)'],
  },
  {
    name: 'Résidence Al Manar', address: 'Bd Hassan II', city: 'Mohammedia',
    guardianName: 'Hassan Alami',
    latitude: 33.6866, longitude: -7.383,
    zones: ['Hall principal'],
  },
  {
    name: 'Anfa Place', address: 'Bd de l’Océan Atlantique', city: 'Casablanca',
    latitude: 33.5883, longitude: -7.6822,
    zones: ['Galerie commerciale'],
  },
  {
    name: 'Clinique Yasmine', address: 'Rue Ibn Rochd', city: 'Casablanca',
    guardianName: 'Rachid Mansouri',
    latitude: 33.5731, longitude: -7.6316,
    zones: ['Bloc A'],
  },
  {
    name: 'Marina Center', address: 'Av. des FAR', city: 'Mohammedia',
    latitude: 33.7, longitude: -7.39,
    zones: ['Hall B'],
  },
];

const ASSETS: {
  reference: string; brand: string; model: string; serialNumber?: string;
  commissionedAt?: string; loadKg?: number; floors?: number;
  category: string; site: string; zone: string;
  status?: 'IN_SERVICE' | 'OUT_OF_SERVICE' | 'UNDER_MAINTENANCE';
  components?: { type: string; designation?: string }[];
}[] = [
  {
    reference: 'A1', brand: 'Otis', model: 'Gen2 Premier', serialNumber: 'OT-2020-4521',
    commissionedAt: '2020-03-15', loadKg: 630, floors: 8,
    category: 'Ascenseur électrique', site: 'Résidence Al Manar', zone: 'Hall principal',
    components: [
      { type: 'Portes cabine / palières', designation: 'Fermator 40/10' },
      { type: 'Treuil / machinerie', designation: 'Gen2 gearless' },
      { type: 'Parachute' },
      { type: 'Armoire de commande', designation: 'MCS 220' },
    ],
  },
  {
    reference: 'A2', brand: 'Otis', model: 'Gen2', loadKg: 630, floors: 12,
    category: 'Ascenseur électrique', site: 'Tour Atlas', zone: 'Hall principal',
  },
  {
    reference: 'B1', brand: 'Schindler', model: '3300', loadKg: 1000, floors: 24,
    category: 'Ascenseur électrique', site: 'Tour Atlas', zone: 'Tour bureaux (étages 1-24)',
  },
  {
    reference: 'B2', brand: 'Schindler', model: '3300', loadKg: 1000, floors: 24,
    category: 'Ascenseur électrique', site: 'Tour Atlas', zone: 'Tour bureaux (étages 1-24)',
    status: 'OUT_OF_SERVICE',
    components: [
      { type: 'Portes cabine / palières', designation: 'Sematic' },
      { type: 'Armoire de commande' },
    ],
  },
  {
    reference: 'M1', brand: 'Kone', model: 'TranSys', loadKg: 2000, floors: 3,
    category: 'Monte-charge', site: 'Tour Atlas', zone: 'Parking sous-sol',
    status: 'UNDER_MAINTENANCE',
  },
  {
    reference: 'C1', brand: 'Kone', model: 'MonoSpace 500', loadKg: 800, floors: 5,
    category: 'Ascenseur électrique', site: 'Anfa Place', zone: 'Galerie commerciale',
  },
  {
    reference: 'D1', brand: 'ThyssenKrupp', model: 'Evolution', loadKg: 1600, floors: 6,
    category: 'Ascenseur électrique', site: 'Clinique Yasmine', zone: 'Bloc A',
  },
  {
    reference: 'E2', brand: 'Otis', model: 'HydroFit', loadKg: 630, floors: 4,
    category: 'Ascenseur hydraulique', site: 'Marina Center', zone: 'Hall B',
  },
];

const TEAMS: { name: string; description: string; memberEmails: string[] }[] = [
  {
    name: 'Casablanca Centre',
    description: 'Tour Atlas · Anfa Place · Clinique Yasmine',
    memberEmails: ['technicien@demo.siop.ma', 'technicien-limite@demo.siop.ma'],
  },
  {
    name: 'Mohammedia',
    description: 'Résidence Al Manar · Marina Center',
    memberEmails: [],
  },
];

async function seedReferentiel(prisma: PrismaClient): Promise<void> {
  const categoryIds = new Map<string, string>();
  for (const [kind, names] of [
    ['EQUIPMENT', EQUIPMENT_CATEGORIES],
    ['COMPONENT_TYPE', COMPONENT_TYPES],
  ] as const) {
    for (const name of names) {
      const category = await prisma.category.upsert({
        where: { kind_name: { kind, name } },
        update: {},
        create: { kind, name },
      });
      categoryIds.set(name, category.id);
    }
  }

  // Pas d'unicité en base sur (parent, nom) : idempotence par findFirst.
  const zoneIds = new Map<string, string>(); // « site / zone » → id
  for (const site of SITES) {
    const { zones, ...data } = site;
    let root = await prisma.location.findFirst({
      where: { name: site.name, parentId: null },
    });
    root ??= await prisma.location.create({ data });
    for (const zoneName of zones) {
      let zone = await prisma.location.findFirst({
        where: { name: zoneName, parentId: root.id },
      });
      zone ??= await prisma.location.create({
        data: { name: zoneName, parentId: root.id, city: site.city },
      });
      zoneIds.set(`${site.name} / ${zoneName}`, zone.id);
    }
  }

  for (const asset of ASSETS) {
    const created = await prisma.asset.upsert({
      where: { reference: asset.reference },
      update: {},
      create: {
        reference: asset.reference,
        brand: asset.brand,
        model: asset.model,
        serialNumber: asset.serialNumber,
        commissionedAt: asset.commissionedAt ? new Date(asset.commissionedAt) : undefined,
        loadKg: asset.loadKg,
        floors: asset.floors,
        status: asset.status ?? 'IN_SERVICE',
        categoryId: categoryIds.get(asset.category)!,
        locationId: zoneIds.get(`${asset.site} / ${asset.zone}`)!,
      },
      include: { _count: { select: { components: true } } },
    });
    if (asset.components?.length && created._count.components === 0) {
      await prisma.assetComponent.createMany({
        data: asset.components.map((c) => ({
          assetId: created.id,
          typeId: categoryIds.get(c.type)!,
          designation: c.designation,
        })),
      });
    }
  }

  for (const team of TEAMS) {
    await prisma.team.upsert({
      where: { name: team.name },
      update: {},
      create: {
        name: team.name,
        description: team.description,
        members: { connect: team.memberEmails.map((email) => ({ email })) },
      },
    });
  }
}

// ————— R2 — Exploitation (référentiels du bilan, gabarits, données maquette) —————

const REFERENCE_VALUES: Record<string, string[]> = {
  DOOR_STATE: [
    'Fonctionnement normal',
    'Porte bloquée ouverte',
    'Porte bloquée fermée',
    'Fermeture incomplète',
    'Réouverture intempestive',
  ],
  CABIN_POSITION: ['À niveau', 'Entre deux niveaux', 'Cuvette', 'Dernier niveau'],
  ANOMALY: [
    'Frottement mécanique',
    'Défaut électrique',
    'Usure normale',
    'Choc / vandalisme',
    'Aucune anomalie constatée',
  ],
  EXTERNAL_CAUSE: ['Coupure électrique', 'Dégât des eaux', 'Mauvais usage', 'Aucune'],
  ACTION_TAKEN: [
    'Réglage',
    'Remplacement de pièce',
    'Nettoyage / graissage',
    'Remise en service simple',
    'Visite d’entretien',
    'Attente de pièce',
  ],
  COMPONENT_CONCERNED: [
    'Portes',
    'Guides',
    'Treuil / machinerie',
    'Armoire de commande',
    'Boutons / signalisation',
    'Parachute',
    'Cabine',
  ],
};

/** Gabarits du préventif (maquette R2) — période calendaire en mois. */
const TASK_TEMPLATES: {
  label: string;
  periodMonths: number;
  componentType?: string;
  isRegulatory?: boolean;
}[] = [
  { label: 'Contrôle fermeture / verrouillage des portes', periodMonths: 1, componentType: 'Portes cabine / palières' },
  { label: 'Nettoyage cuvette et toit de cabine', periodMonths: 1 },
  { label: 'Contrôle boutons cabine & paliers', periodMonths: 1, componentType: 'Boutons & signalisation' },
  { label: 'Vérification éclairage de secours', periodMonths: 1 },
  { label: 'Contrôle niveau d’huile réducteur', periodMonths: 3, componentType: 'Treuil / machinerie' },
  { label: 'Vérification jeu des coulisseaux', periodMonths: 6 },
  { label: 'Contrôle câbles de traction (usure, tension)', periodMonths: 6, componentType: 'Treuil / machinerie' },
  { label: 'Essai du parachute', periodMonths: 12, componentType: 'Parachute', isRegulatory: true },
];

async function seedExploitation(prisma: PrismaClient): Promise<void> {
  const refIds = new Map<string, string>(); // « FIELD/label » → id
  for (const [field, labels] of Object.entries(REFERENCE_VALUES)) {
    for (const label of labels) {
      const value = await prisma.referenceValue.upsert({
        where: { field_label: { field: field as never, label } },
        update: {},
        create: { field: field as never, label },
      });
      refIds.set(`${field}/${label}`, value.id);
    }
  }

  const componentTypes = new Map(
    (await prisma.category.findMany({ where: { kind: 'COMPONENT_TYPE' } })).map((c) => [
      c.name,
      c.id,
    ]),
  );
  for (const t of TASK_TEMPLATES) {
    await prisma.taskTemplate.upsert({
      where: { label: t.label },
      update: {},
      create: {
        label: t.label,
        periodMonths: t.periodMonths,
        isRegulatory: t.isRegulatory ?? false,
        componentTypeId: t.componentType ? componentTypes.get(t.componentType) : undefined,
      },
    });
  }

  // Données de démonstration (rejouent la maquette : OT en cours, urgence,
  // grille du mois, demandes à approuver). Idempotent par référence.
  const parReference = async (ref: string) =>
    (await prisma.asset.findUniqueOrThrow({ where: { reference: ref } })).id;
  const parEmail = async (email: string) =>
    (await prisma.user.findUniqueOrThrow({ where: { email } })).id;

  const a1 = await parReference('A1');
  const b2 = await parReference('B2');
  const c1 = await parReference('C1');
  const ahmed = await parEmail('technicien@demo.siop.ma');
  const salma = await parEmail('dispatcher@demo.siop.ma');
  const karim = await parEmail('demandeur@demo.siop.ma');
  const annee = new Date().getFullYear();

  const grilleLabels = TASK_TEMPLATES.filter((t) => t.periodMonths === 1).map(
    (t) => t.label,
  );

  const OTS: {
    ref: string; title: string; type: 'CORRECTIVE' | 'PREVENTIVE' | 'WORKS';
    status: 'OPEN' | 'IN_PROGRESS' | 'ON_HOLD' | 'DONE' | 'CANCELLED';
    priority: 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH' | 'PERSON_TRAPPED';
    assetId: string; assignees?: string[]; dueJours?: number;
    checklist?: string[]; bilan?: Record<string, string>;
  }[] = [
    {
      ref: `OT-${annee}-0342`, title: 'Personne bloquée en cabine', type: 'CORRECTIVE',
      status: 'OPEN', priority: 'PERSON_TRAPPED', assetId: b2,
    },
    {
      ref: `OT-${annee}-0341`, title: 'Bruit anormal en gaine', type: 'CORRECTIVE',
      status: 'IN_PROGRESS', priority: 'HIGH', assetId: a1, assignees: [ahmed], dueJours: 2,
    },
    {
      ref: `OT-${annee}-0338`, title: 'Grille du mois — juillet', type: 'PREVENTIVE',
      status: 'IN_PROGRESS', priority: 'LOW', assetId: a1, assignees: [ahmed],
      dueJours: 15, checklist: grilleLabels,
    },
    {
      ref: `OT-${annee}-0332`, title: 'Réglage nivellement cabine', type: 'CORRECTIVE',
      status: 'DONE', priority: 'LOW', assetId: a1,
      bilan: {
        doorStateId: refIds.get('DOOR_STATE/Fonctionnement normal')!,
        actionTakenId: refIds.get('ACTION_TAKEN/Réglage')!,
        componentConcernedId: refIds.get('COMPONENT_CONCERNED/Guides')!,
      },
    },
  ];
  for (const ot of OTS) {
    const existant = await prisma.workOrder.findUnique({ where: { reference: ot.ref } });
    if (existant) continue;
    await prisma.workOrder.create({
      data: {
        reference: ot.ref,
        title: ot.title,
        type: ot.type,
        status: ot.status,
        priority: ot.priority,
        assetId: ot.assetId,
        createdById: salma,
        dueDate: ot.dueJours
          ? new Date(Date.now() + ot.dueJours * 24 * 3600 * 1000)
          : undefined,
        startedAt: ot.status === 'IN_PROGRESS' || ot.status === 'DONE' ? new Date() : undefined,
        completedAt: ot.status === 'DONE' ? new Date() : undefined,
        assignees: ot.assignees ? { connect: ot.assignees.map((id) => ({ id })) } : undefined,
        events: { create: { kind: 'CREATED', message: 'OT créé (seed)', byId: salma } },
        checklist: ot.checklist
          ? { create: ot.checklist.map((label) => ({ label })) }
          : undefined,
        report: ot.bilan ? { create: ot.bilan } : undefined,
      },
    });
  }

  const DEMANDES: {
    ref: string; description: string; assetId: string; isPersonTrapped?: boolean;
    status: 'RECEIVED' | 'APPROVED' | 'REJECTED';
    rejectionReason?: string; otRef?: string;
  }[] = [
    {
      ref: `DEM-${annee}-0111`, assetId: a1, status: 'RECEIVED',
      description: 'La porte ne se ferme plus au 3ᵉ étage, il faut la retenir à la main.',
    },
    { ref: `DEM-${annee}-0110`, assetId: c1, status: 'RECEIVED', description: 'Voyant étage éteint.' },
    {
      ref: `DEM-${annee}-0107`, assetId: a1, status: 'APPROVED',
      description: 'Bruit anormal en gaine.', otRef: `OT-${annee}-0341`,
    },
    {
      ref: `DEM-${annee}-0104`, assetId: b2, status: 'REJECTED',
      description: 'Odeur de brûlé.', rejectionReason: 'Fausse alerte confirmée sur place par le gardien.',
    },
  ];
  for (const dem of DEMANDES) {
    const existant = await prisma.request.findUnique({ where: { reference: dem.ref } });
    if (existant) continue;
    const workOrderId = dem.otRef
      ? (await prisma.workOrder.findUnique({ where: { reference: dem.otRef } }))?.id
      : undefined;
    await prisma.request.create({
      data: {
        reference: dem.ref,
        description: dem.description,
        isPersonTrapped: dem.isPersonTrapped ?? false,
        status: dem.status,
        rejectionReason: dem.rejectionReason,
        assetId: dem.assetId,
        requestedById: karim,
        workOrderId,
      },
    });
  }

  // Compteurs de la maquette (A1)
  for (const [kind, valeurs] of [
    ['RUNNING_HOURS', [12246, 12322, 12411]],
    ['STARTS', [1815400, 1831970]],
  ] as const) {
    const meter = await prisma.meter.upsert({
      where: { assetId_kind: { assetId: a1, kind } },
      update: {},
      create: { assetId: a1, kind },
      include: { _count: { select: { readings: true } } },
    });
    if (meter._count.readings === 0) {
      await prisma.meterReading.createMany({
        data: valeurs.map((value) => ({ meterId: meter.id, value, readById: ahmed })),
      });
    }
  }
}

// ————— R3 — Gestion (tiers, pièces, mouvements, BC, taux — données maquette) —————

const PARTNERS: {
  name: string; kind: 'SUPPLIER' | 'CLIENT'; contactName?: string;
  phone?: string; email?: string; city?: string;
}[] = [
  { name: 'Ascentech Maroc', kind: 'SUPPLIER', contactName: 'M. Berrini', phone: '05 22 34 56 78', city: 'Casablanca' },
  { name: 'SchindlerParts', kind: 'SUPPLIER', email: 'commandes@schindlerparts.ma' },
  { name: 'Lubmaroc', kind: 'SUPPLIER', phone: '05 22 11 22 33' },
  { name: 'Atlas Property Management', kind: 'CLIENT', contactName: 'Mme Zerhouni', phone: '06 61 98 76 54' },
  { name: 'Syndic Al Manar', kind: 'CLIENT', contactName: 'M. Alami (gardien référent)' },
];

const PARTS: {
  reference: string; designation: string; threshold: number;
  lastUnitPrice?: number; supplier?: string; compatible?: string;
}[] = [
  { reference: 'P-0019', designation: 'Cellule barrière porte (paire)', threshold: 4, lastUnitPrice: 640, supplier: 'SchindlerParts' },
  { reference: 'P-0031', designation: 'Bouton palier lumineux Ø22', threshold: 10, lastUnitPrice: 45, supplier: 'Ascentech Maroc' },
  { reference: 'P-0042', designation: 'Graisse guide (cartouche 400 g)', threshold: 8, lastUnitPrice: 85, supplier: 'Lubmaroc' },
  { reference: 'P-0087', designation: 'Coulisseau de guide 16 mm', threshold: 6, lastUnitPrice: 120, supplier: 'Ascentech Maroc' },
  { reference: 'P-0113', designation: 'Contact de porte NC-31', threshold: 5, lastUnitPrice: 85, supplier: 'Ascentech Maroc', compatible: 'Otis Gen2 · Schindler 3300' },
];

async function seedGestion(prisma: PrismaClient): Promise<void> {
  const partnerIds = new Map<string, string>();
  for (const p of PARTNERS) {
    const partner = await prisma.partner.upsert({
      where: { name: p.name },
      update: {},
      create: p,
    });
    partnerIds.set(p.name, partner.id);
  }

  // Rattachements de la maquette Tiers : les syndics gèrent leur site.
  for (const [site, partner] of [
    ['Tour Atlas', 'Atlas Property Management'],
    ['Résidence Al Manar', 'Syndic Al Manar'],
  ] as const) {
    await prisma.location.updateMany({
      where: { name: site, parentId: null, partnerId: null },
      data: { partnerId: partnerIds.get(partner) },
    });
  }

  const partIds = new Map<string, string>();
  for (const p of PARTS) {
    const part = await prisma.part.upsert({
      where: { reference: p.reference },
      update: {},
      create: {
        reference: p.reference,
        designation: p.designation,
        threshold: p.threshold,
        lastUnitPrice: p.lastUnitPrice,
        compatible: p.compatible,
        supplierId: p.supplier ? partnerIds.get(p.supplier) : undefined,
      },
    });
    partIds.set(p.reference, part.id);
  }

  // Taux horaires courants (les saisies figent leur propre taux)
  for (const [email, taux] of [
    ['technicien@demo.siop.ma', 120],
    ['technicien-limite@demo.siop.ma', 90],
  ] as const) {
    await prisma.user.update({ where: { email }, data: { hourlyRate: taux } });
  }

  // Idempotence : si des mouvements existent déjà, l'histoire est en place
  if ((await prisma.stockMovement.count()) > 0) return;

  const annee = new Date().getFullYear();
  const ahmed = await prisma.user.findUniqueOrThrow({ where: { email: 'technicien@demo.siop.ma' } });
  const nadia = await prisma.user.findUniqueOrThrow({ where: { email: 'gestionnaire@demo.siop.ma' } });

  // BC reçus (historique) et BC en cours — comme la maquette
  const bc21 = await prisma.purchaseOrder.create({
    data: {
      reference: `BC-${annee}-0021`,
      status: 'RECEIVED',
      supplierId: partnerIds.get('Ascentech Maroc')!,
      createdById: nadia.id,
      sentAt: new Date(Date.now() - 40 * 86400e3),
      receivedAt: new Date(Date.now() - 34 * 86400e3),
      lines: {
        create: [
          { partId: partIds.get('P-0113')!, quantity: 5, unitPrice: 85 },
          { partId: partIds.get('P-0087')!, quantity: 6, unitPrice: 120 },
          { partId: partIds.get('P-0031')!, quantity: 20, unitPrice: 45 },
        ],
      },
    },
    include: { lines: true },
  });
  await prisma.purchaseOrder.create({
    data: {
      reference: `BC-${annee}-0024`,
      status: 'SENT',
      supplierId: partnerIds.get('Ascentech Maroc')!,
      createdById: nadia.id,
      sentAt: new Date(),
      lines: {
        create: [
          { partId: partIds.get('P-0113')!, quantity: 10, unitPrice: 85 },
          { partId: partIds.get('P-0087')!, quantity: 2, unitPrice: 127.5 },
        ],
      },
    },
  });

  // Mouvements : réceptions du BC-0021 + entrées initiales + consommations
  for (const line of bc21.lines) {
    await prisma.stockMovement.create({
      data: {
        partId: line.partId,
        kind: 'RECEIPT',
        quantity: line.quantity,
        unitPrice: line.unitPrice,
        purchaseOrderId: bc21.id,
        byId: nadia.id,
        createdAt: new Date(Date.now() - 34 * 86400e3),
      },
    });
  }
  for (const [ref, qte] of [
    ['P-0042', 19],
    ['P-0019', 7],
    ['P-0031', 6],
  ] as const) {
    await prisma.stockMovement.create({
      data: {
        partId: partIds.get(ref)!,
        kind: 'ENTRY',
        quantity: qte,
        reason: 'Reprise de l’inventaire initial',
        byId: nadia.id,
        createdAt: new Date(Date.now() - 60 * 86400e3),
      },
    });
  }
  // P-0113 : consommations + ajustement → stock 2 (sous le seuil, maquette)
  const ot341 = await prisma.workOrder.findUnique({
    where: { reference: `OT-${annee}-0341` },
  });
  await prisma.stockMovement.createMany({
    data: [
      { partId: partIds.get('P-0113')!, kind: 'ADJUSTMENT', quantity: -1, reason: 'Inventaire — pièce endommagée', byId: nadia.id },
      { partId: partIds.get('P-0113')!, kind: 'CONSUMPTION', quantity: -2, unitPrice: 85, byId: ahmed.id },
    ],
  });
  // P-0087 : consommation sur OT-0341 (carte maquette : 2 × 120 = 240 MAD)
  if (ot341) {
    await prisma.stockMovement.create({
      data: {
        partId: partIds.get('P-0087')!,
        kind: 'CONSUMPTION',
        quantity: -2,
        unitPrice: 120,
        workOrderId: ot341.id,
        byId: ahmed.id,
      },
    });
    await prisma.stockMovement.create({
      data: {
        partId: partIds.get('P-0042')!,
        kind: 'CONSUMPTION',
        quantity: -1,
        unitPrice: 85,
        workOrderId: ot341.id,
        byId: ahmed.id,
      },
    });
    // main-d'œuvre : 1 h 30 × 120 MAD/h = 180 (total maquette : 505 MAD)
    await prisma.laborTime.create({
      data: { workOrderId: ot341.id, userId: ahmed.id, minutes: 90, hourlyRate: 120 },
    });
  }
}

/* c8 ignore start — wrapper CLI */
if (require.main === module) {
  const prisma = new PrismaClient();
  seed(prisma)
    .then(async () => {
      const [roles, permissions, users] = await Promise.all([
        prisma.role.count(),
        prisma.permission.count(),
        prisma.user.count({ where: { isDemo: true } }),
      ]);
      console.log(
        `Seed OK — ${roles} rôles, ${permissions} lignes de matrice, ${users} comptes démo.`,
      );
    })
    .catch((e) => {
      console.error(e);
      process.exitCode = 1;
    })
    .finally(() => prisma.$disconnect());
}
/* c8 ignore stop */
