/**
 * Tests unitaires pour le générateur de PNJ Traveller
 * 
 * Ces tests vérifient le bon fonctionnement des fonctions de génération
 * sans nécessiter l'environnement Foundry VTT.
 * 
 * Pour exécuter ces tests, utilisez un environnement Node.js avec Jest ou
 * un framework de test similaire.
 */

// Import explicite des globals Jest (obligatoire en ESM)
import { jest, describe, it, expect, beforeEach } from '@jest/globals';

// Mock des dépendances Foundry pour l'environnement de test
const mockFoundry = {
  utils: {
    deepClone: (obj) => JSON.parse(JSON.stringify(obj)),
    mergeObject: (target, source) => ({ ...target, ...source }),
    setProperty: () => {},
    getProperty: () => {}
  },
  applications: {
    handlebars: {
      renderTemplate: async () => '<div>Test</div>'
    }
  }
};

global.foundry = mockFoundry;

// Mock console pour capturer les logs
const mockConsole = {
  log: jest.fn(),
  warn: jest.fn(),
  error: jest.fn(),
  debug: jest.fn()
};

// Remplacer la console globale
console.log = mockConsole.log;
console.warn = mockConsole.warn;
console.error = mockConsole.error;
console.debug = mockConsole.debug;

// Mock ui.notifications
const mockUi = {
  notifications: {
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
    success: jest.fn()
  }
};

global.ui = mockUi;

// Mock game object
const mockGame = {
  system: { id: 'mgt2e' },
  packs: {
    get: jest.fn()
  },
  i18n: {
    localize: jest.fn((key) => key)
  },
  settings: {
    get: jest.fn()
  }
};

global.game = mockGame;

// Mock canvas
const mockCanvas = {
  tokens: {
    controlled: []
  }
};

global.canvas = mockCanvas;

// Imports des modules à tester
import {
  generateCharacteristics,
  generateSkills,
  generateTravellerNpc,
  generateAndCreateTravellerNpc,
  convertSkillToMgt2eFormat,
  buildMgt2eCharacteristics,
  buildMgt2eSkills,
  getMgt2eBaseActorSystem,
  toHex,
  calculateDm,
  pickRandomItem,
  shuffleArray,
  popRandomItems,
  getRoleByKey,
  getCitizenCategoryByKey,
  getExperienceLevelByKey,
  getGenderByKey,
  getSkillsForRole,
  getCharacteristicPrioritiesForRole,
  validateOptions,
  DEFAULT_OPTIONS,
  CITIZEN_CATEGORY_LIST,
  EXPERIENCE_LEVEL_LIST,
  ROLE_LIST,
  GENDER_LIST,
  CHARACTERISTIC_LIST,
  UPP_ORDER,
  ERROR_CODES,
  TravellerNpcError
} from '../travellerNpcGenerator.js';

import {
  CITIZEN_CATEGORY,
  EXPERIENCE_LEVEL,
  ROLE,
  GENDER,
  CHARACTERISTIC,
  CHARACTERISTIC_PRIORITIES,
  ROLE_SKILLS,
  NAME_CATALOGS
} from '../data/travellerNpcGenerator.js';

import {
  ModuleCache,
  travellerNpcCache,
  ERROR_CODES as UTILS_ERROR_CODES
} from '../utils/travellerNpcUtils.js';

// ============================================================================
// Tests des fonctions utilitaires
// ============================================================================

describe('Fonctions utilitaires', () => {
  describe('toHex', () => {
    it('devrait convertir 0 en "0"', () => {
      expect(toHex(0)).toBe('0');
    });

    it('devrait convertir 7 en "7"', () => {
      expect(toHex(7)).toBe('7');
    });

    it('devrait convertir 10 en "A"', () => {
      expect(toHex(10)).toBe('A');
    });

    it('devrait convertir 15 en "F"', () => {
      expect(toHex(15)).toBe('F');
    });

    it('devrait limiter les valeurs à 0-15', () => {
      expect(toHex(-5)).toBe('0');
      expect(toHex(20)).toBe('F');
    });

    it('devrait arrondir vers le bas', () => {
      expect(toHex(10.7)).toBe('A');
      expect(toHex(10.2)).toBe('A');
    });
  });

  describe('calculateDm', () => {
    it('devrait calculer le DM pour la valeur 6 (DM=0)', () => {
      expect(calculateDm(6)).toBe(0);
    });

    it('devrait calculer le DM pour la valeur 7 (DM=0)', () => {
      expect(calculateDm(7)).toBe(0);
    });

    it('devrait calculer le DM pour la valeur 8 (DM=0)', () => {
      expect(calculateDm(8)).toBe(0);
    });

    it('devrait calculer le DM pour la valeur 9 (DM=+1)', () => {
      expect(calculateDm(9)).toBe(1);
    });

    it('devrait calculer le DM pour la valeur 12 (DM=+2)', () => {
      expect(calculateDm(12)).toBe(2);
    });

    it('devrait calculer le DM pour la valeur 4 (DM=-1)', () => {
      expect(calculateDm(4)).toBe(-1);
    });

    it('devrait calculer le DM pour la valeur 3 (DM=-1)', () => {
      expect(calculateDm(3)).toBe(-1);
    });

    it('devrait calculer le DM pour la valeur 1 (DM=-2)', () => {
      expect(calculateDm(1)).toBe(-2);
    });
  });

  describe('pickRandomItem', () => {
    it('devrait retourner un élément aléatoire du tableau', () => {
      const items = [1, 2, 3, 4, 5];
      const result = pickRandomItem(items);
      expect(items).toContain(result);
    });

    it('devrait lancer une erreur pour un tableau vide', () => {
      expect(() => pickRandomItem([])).toThrow('Cannot pick from empty array');
    });

    it('devrait lancer une erreur pour null/undefined', () => {
      expect(() => pickRandomItem(null)).toThrow('Cannot pick from empty array');
      expect(() => pickRandomItem(undefined)).toThrow('Cannot pick from empty array');
    });
  });

  describe('shuffleArray', () => {
    it('devrait retourner un tableau de même longueur', () => {
      const array = [1, 2, 3, 4, 5];
      const shuffled = shuffleArray(array);
      expect(shuffled).toHaveLength(5);
    });

    it('devrait retourner tous les éléments originaux', () => {
      const array = [1, 2, 3, 4, 5];
      const shuffled = shuffleArray(array);
      expect(shuffled).toEqual(expect.arrayContaining(array));
    });

    it('devrait retourner un tableau différent (sauf cas très rare)', () => {
      const array = [1, 2, 3, 4, 5];
      const shuffled = shuffleArray(array);
      // Note: Il y a une très faible probabilité que le tableau reste identique
      // Cette assertion peut échouer très rarement
      expect(shuffled).not.toEqual(array);
    });

    it('devrait retourner une copie (ne pas modifier l\'original)', () => {
      const array = [1, 2, 3, 4, 5];
      const original = [...array];
      shuffleArray(array);
      expect(array).toEqual(original);
    });
  });

  describe('popRandomItems', () => {
    it('devrait retourner le bon nombre d\'éléments extraits', () => {
      const array = [1, 2, 3, 4, 5, 6, 7, 8];
      const [taken, remaining] = popRandomItems(array, 3);
      expect(taken).toHaveLength(3);
    });

    it('devrait retourner les éléments restants', () => {
      const array = [1, 2, 3, 4, 5, 6, 7, 8];
      const [taken, remaining] = popRandomItems(array, 3);
      expect(remaining).toHaveLength(5);
    });

    it('devrait retourner tous les éléments si count > array.length', () => {
      const array = [1, 2, 3];
      const [taken, remaining] = popRandomItems(array, 10);
      expect(taken).toHaveLength(3);
      expect(remaining).toHaveLength(0);
    });

    it('devrait retourner un tableau vide si count <= 0', () => {
      const array = [1, 2, 3, 4, 5];
      const [taken, remaining] = popRandomItems(array, 0);
      expect(taken).toHaveLength(0);
      expect(remaining).toHaveLength(5);
    });

    it('devrait retourner des tableaux vides pour un tableau vide', () => {
      const [taken, remaining] = popRandomItems([], 3);
      expect(taken).toHaveLength(0);
      expect(remaining).toHaveLength(0);
    });
  });
});

// ============================================================================
// Tests des fonctions de lookup
// ============================================================================

describe('Fonctions de lookup', () => {
  describe('getRoleByKey', () => {
    it('devrait retourner le rôle pilote', () => {
      const role = getRoleByKey('pilot');
      expect(role.key).toBe('pilot');
    });

    it('devrait retourner le rôle par défaut (pilot) pour une clé invalide', () => {
      const role = getRoleByKey('invalid');
      expect(role.key).toBe('pilot');
    });

    it('devrait retourner le rôle navigateur', () => {
      const role = getRoleByKey('navigator');
      expect(role.key).toBe('navigator');
    });
  });

  describe('getCitizenCategoryByKey', () => {
    it('devrait retourner la catégorie moyenne', () => {
      const category = getCitizenCategoryByKey('average');
      expect(category.key).toBe('average');
    });

    it('devrait retourner la catégorie par défaut (average) pour une clé invalide', () => {
      const category = getCitizenCategoryByKey('invalid');
      expect(category.key).toBe('average');
    });

    it('devrait retourner la catégorie exceptionnel', () => {
      const category = getCitizenCategoryByKey('exceptional');
      expect(category.key).toBe('exceptional');
    });
  });

  describe('getExperienceLevelByKey', () => {
    it('devrait retourner le niveau recruit', () => {
      const level = getExperienceLevelByKey('recruit');
      expect(level.key).toBe('recruit');
    });

    it('devrait retourner le niveau par défaut (regular) pour une clé invalide', () => {
      const level = getExperienceLevelByKey('invalid');
      expect(level.key).toBe('regular');
    });

    it('devrait retourner le niveau elite', () => {
      const level = getExperienceLevelByKey('elite');
      expect(level.key).toBe('elite');
    });
  });

  describe('getGenderByKey', () => {
    it('devrait retourner le genre unspecified', () => {
      const gender = getGenderByKey('unspecified');
      expect(gender.key).toBe('unspecified');
    });

    it('devrait retourner le genre par défaut (unspecified) pour une clé invalide', () => {
      const gender = getGenderByKey('invalid');
      expect(gender.key).toBe('unspecified');
    });

    it('devrait retourner le genre female', () => {
      const gender = getGenderByKey('female');
      expect(gender.key).toBe('female');
    });
  });

  describe('getSkillsForRole', () => {
    it('devrait retourner les compétences pour pilote', () => {
      const skills = getSkillsForRole('pilot');
      expect(skills).toBeDefined();
      expect(skills.length).toBeGreaterThan(0);
      expect(skills).toContain('Pilot-Spacecraft');
    });

    it('devrait retourner les compétences pour médecin', () => {
      const skills = getSkillsForRole('medic');
      expect(skills).toContain('Medic');
    });

    it('devrait retourner les compétences par défaut pour un rôle invalide', () => {
      const skills = getSkillsForRole('invalid');
      expect(skills).toBeDefined();
      expect(skills.length).toBeGreaterThan(0);
    });
  });

  describe('getCharacteristicPrioritiesForRole', () => {
    it('devrait retourner les priorités pour pilote', () => {
      const priorities = getCharacteristicPrioritiesForRole('pilot');
      expect(priorities).toBeDefined();
      expect(priorities.high).toContain('DEX');
      expect(priorities.high).toContain('INT');
    });

    it('devrait retourner les priorités par défaut pour un rôle invalide', () => {
      const priorities = getCharacteristicPrioritiesForRole('invalid');
      expect(priorities).toBeDefined();
    });
  });
});

// ============================================================================
// Tests de validation des options
// ============================================================================

describe('validateOptions', () => {
  it('devrait retourner les options par défaut pour un objet vide', () => {
    const validated = validateOptions({});
    expect(validated.citizenCategory).toBe(DEFAULT_OPTIONS.citizenCategory);
    expect(validated.experience).toBe(DEFAULT_OPTIONS.experience);
    expect(validated.role).toBe(DEFAULT_OPTIONS.role);
    expect(validated.gender).toBe(DEFAULT_OPTIONS.gender);
  });

  it('devrait accepter des options valides', () => {
    const validated = validateOptions({
      citizenCategory: 'exceptional',
      experience: 'elite',
      role: 'engineer',
      gender: 'female'
    });
    expect(validated.citizenCategory).toBe('exceptional');
    expect(validated.experience).toBe('elite');
    expect(validated.role).toBe('engineer');
    expect(validated.gender).toBe('female');
  });

  it('devrait corriger une catégorie de citoyen invalide', () => {
    const validated = validateOptions({
      citizenCategory: 'invalid',
      experience: 'regular',
      role: 'pilot',
      gender: 'male'
    });
    expect(validated.citizenCategory).toBe(DEFAULT_OPTIONS.citizenCategory);
  });

  it('devrait corriger un niveau d\'expérience invalide', () => {
    const validated = validateOptions({
      citizenCategory: 'average',
      experience: 'invalid',
      role: 'pilot',
      gender: 'male'
    });
    expect(validated.experience).toBe(DEFAULT_OPTIONS.experience);
  });

  it('devrait corriger un rôle invalide', () => {
    const validated = validateOptions({
      citizenCategory: 'average',
      experience: 'regular',
      role: 'invalid',
      gender: 'male'
    });
    expect(validated.role).toBe(DEFAULT_OPTIONS.role);
  });

  it('devrait corriger un genre invalide', () => {
    const validated = validateOptions({
      citizenCategory: 'average',
      experience: 'regular',
      role: 'pilot',
      gender: 'invalid'
    });
    expect(validated.gender).toBe(DEFAULT_OPTIONS.gender);
  });

  it('devrait conserver les options personnalisées valides', () => {
    const validated = validateOptions({
      citizenCategory: 'aboveAverage',
      experience: 'veteran',
      role: 'gunner',
      gender: 'male',
      createActor: true,
      actorName: 'Test NPC',
      openCreatedActor: false
    });
    expect(validated.createActor).toBe(true);
    expect(validated.actorName).toBe('Test NPC');
    expect(validated.openCreatedActor).toBe(false);
  });
});

// ============================================================================
// Tests de génération des caractéristiques
// ============================================================================

describe('generateCharacteristics', () => {
  it('devrait générer des caractéristiques pour la catégorie average', () => {
    const result = generateCharacteristics('average', 'pilot');
    expect(result.characteristics).toBeDefined();
    expect(result.upp).toBeDefined();
    expect(result.category).toBeDefined();
    expect(result.category.key).toBe('average');
  });

  it('devrait générer des caractéristiques pour la catégorie exceptional', () => {
    const result = generateCharacteristics('exceptional', 'pilot');
    expect(result.category.key).toBe('exceptional');
    expect(result.characteristics.STR).toBeGreaterThanOrEqual(7);
    expect(result.characteristics.DEX).toBeGreaterThanOrEqual(7);
    expect(result.characteristics.END).toBeGreaterThanOrEqual(7);
  });

  it('devrait générer des caractéristiques pour la catégorie belowAverage', () => {
    const result = generateCharacteristics('belowAverage', 'pilot');
    expect(result.category.key).toBe('belowAverage');
    // Les valeurs devraient être plus basses
    const values = Object.values(result.characteristics);
    const avg = values.reduce((a, b) => a + b, 0) / values.length;
    expect(avg).toBeLessThan(7);
  });

  it('devrait générer un code UPP de 6 caractères', () => {
    const result = generateCharacteristics('average', 'pilot');
    expect(result.upp).toHaveLength(6);
    expect(result.upp).toMatch(/^[0-9A-F]{6}$/);
  });

  it('devrait générer toutes les caractéristiques (STR, DEX, END, INT, EDU, SOC)', () => {
    const result = generateCharacteristics('average', 'pilot');
    expect(result.characteristics.STR).toBeDefined();
    expect(result.characteristics.DEX).toBeDefined();
    expect(result.characteristics.END).toBeDefined();
    expect(result.characteristics.INT).toBeDefined();
    expect(result.characteristics.EDU).toBeDefined();
    expect(result.characteristics.SOC).toBeDefined();
  });

  it('devrait attribuer des valeurs plus élevées aux caractéristiques prioritaires', () => {
    // Pour le pilote, DEX et INT sont prioritaires (high)
    const priorities = getCharacteristicPrioritiesForRole('pilot');
    const result = generateCharacteristics('average', 'pilot');
    
    // DEX et INT devraient avoir des valeurs plus élevées que SOC et END
    const dexValue = result.characteristics.DEX;
    const intValue = result.characteristics.INT;
    const socValue = result.characteristics.SOC;
    const endValue = result.characteristics.END;
    
    // Note: Cela peut échouer occasionnellement à cause de l'aléatoire
    // mais devrait passer dans la plupart des cas
    expect(dexValue + intValue).toBeGreaterThanOrEqual(socValue + endValue);
  });

  it('devrait générer des caractéristiques même avec une catégorie invalide', () => {
    const result = generateCharacteristics('invalid', 'pilot');
    expect(result.characteristics).toBeDefined();
    // Devrait utiliser la catégorie par défaut (average)
    expect(result.category.key).toBe('average');
  });

  it('devrait générer des caractéristiques même avec un rôle invalide', () => {
    const result = generateCharacteristics('average', 'invalid');
    expect(result.characteristics).toBeDefined();
  });
});

// ============================================================================
// Tests de génération des compétences
// ============================================================================

describe('generateSkills', () => {
  it('devrait générer des compétences pour un rôle et un niveau', () => {
    const skills = generateSkills('pilot', 'regular');
    expect(skills).toBeDefined();
    expect(Array.isArray(skills)).toBe(true);
    expect(skills.length).toBeGreaterThan(0);
  });

  it('devrait générer des compétences avec des niveaux', () => {
    const skills = generateSkills('pilot', 'regular');
    skills.forEach(skill => {
      expect(skill.name).toBeDefined();
      expect(skill.level).toBeDefined();
      expect(typeof skill.level).toBe('number');
    });
  });

  it('devrait trier les compétences par niveau décroissant', () => {
    const skills = generateSkills('pilot', 'regular');
    for (let i = 0; i < skills.length - 1; i++) {
      expect(skills[i].level).toBeGreaterThanOrEqual(skills[i + 1].level);
    }
  });

  it('devrait générer plus de compétences de niveau élevé pour elite', () => {
    const eliteSkills = generateSkills('pilot', 'elite');
    const regularSkills = generateSkills('pilot', 'regular');
    
    const eliteLevel3 = eliteSkills.filter(s => s.level === 3).length;
    const regularLevel3 = regularSkills.filter(s => s.level === 3).length;
    
    // Elite devrait avoir plus de compétences de niveau 3
    expect(eliteLevel3).toBeGreaterThanOrEqual(regularLevel3);
  });

  it('devrait supprimer les doublons exacts de compétence', () => {
    const skills = generateSkills('pilot', 'regular');
    const skillNames = skills.map(s => s.name);
    const uniqueNames = new Set(skillNames);
    
    // Le générateur dédoublonne les noms complets mais conserve
    // les spécialisations (Pilot-Spacecraft, Pilot-Small Craft, ...)
    expect(skillNames.length).toBe(uniqueNames.size);
  });

  it('devrait générer des compétences avec le rôle par défaut pour un rôle invalide', () => {
    const skills = generateSkills('invalid', 'regular');
    expect(skills).toBeDefined();
    expect(skills.length).toBeGreaterThan(0);
  });

  it('devrait générer des compétences avec le niveau par défaut pour un niveau invalide', () => {
    const skills = generateSkills('pilot', 'invalid');
    expect(skills).toBeDefined();
    expect(skills.length).toBeGreaterThan(0);
  });
});

// ============================================================================
// Tests de conversion de compétences
// ============================================================================

describe('convertSkillToMgt2eFormat', () => {
  it('devrait convertir Pilot-Spacecraft', () => {
    expect(convertSkillToMgt2eFormat('Pilot-Spacecraft')).toBe('pilot.spacecraft');
  });

  it('devrait convertir Gun Combat', () => {
    expect(convertSkillToMgt2eFormat('Gun Combat')).toBe('guncombat');
  });

  it('devrait convertir une compétence inconnue', () => {
    const result = convertSkillToMgt2eFormat('Unknown-Skill');
    expect(result).toBe('unknown.skill');
  });

  it('devrait gérer les espaces', () => {
    expect(convertSkillToMgt2eFormat('Gun Combat')).toBe('guncombat');
  });

  it('devrait gérer les tirets', () => {
    expect(convertSkillToMgt2eFormat('Pilot-Small Craft')).toBe('pilot.smallCraft');
  });
});

// ============================================================================
// Tests de génération complète du PNJ
// ============================================================================

describe('generateTravellerNpc', () => {
  it('devrait générer un PNJ avec des options par défaut', () => {
    const npc = generateTravellerNpc();
    expect(npc.success).toBe(true);
    expect(npc.type).toBe('traveller-npc');
    expect(npc.name).toBeDefined();
    expect(npc.name.firstName).toBeDefined();
    expect(npc.name.surname).toBeDefined();
    expect(npc.name.fullName).toBeDefined();
    expect(npc.role).toBeDefined();
    expect(npc.citizenCategory).toBeDefined();
    expect(npc.experience).toBeDefined();
    expect(npc.gender).toBeDefined();
    expect(npc.characteristics).toBeDefined();
    expect(npc.upp).toBeDefined();
    expect(npc.skills).toBeDefined();
    expect(npc.skillsForActor).toBeDefined();
    expect(npc.display).toBeDefined();
  });

  it('devrait générer un PNJ avec des options personnalisées', () => {
    const npc = generateTravellerNpc({
      citizenCategory: 'exceptional',
      experience: 'elite',
      role: 'engineer',
      gender: 'female',
      firstName: 'Jane',
      surname: 'Doe'
    });
    
    expect(npc.name.firstName).toBe('Jane');
    expect(npc.name.surname).toBe('Doe');
    expect(npc.name.fullName).toBe('Jane Doe');
    expect(npc.role.key).toBe('engineer');
    expect(npc.citizenCategory.key).toBe('exceptional');
    expect(npc.experience.key).toBe('elite');
    expect(npc.gender.key).toBe('female');
  });

  it('devrait générer un nom aléatoire si aucun nom n\'est fourni', () => {
    const npc = generateTravellerNpc({
      gender: 'male'
    });
    
    expect(npc.name.firstName).toBeDefined();
    expect(npc.name.surname).toBeDefined();
    expect(npc.name.fullName).toContain(' ');
    expect(NAME_CATALOGS.maleNames).toContain(npc.name.firstName);
    expect(NAME_CATALOGS.surnames).toContain(npc.name.surname);
  });

  it('devrait générer un nom féminin', () => {
    const npc = generateTravellerNpc({
      gender: 'female'
    });
    
    expect(NAME_CATALOGS.femaleNames).toContain(npc.name.firstName);
  });

  it('devrait générer un nom non-genré', () => {
    const npc = generateTravellerNpc({
      gender: 'unspecified'
    });
    
    expect(NAME_CATALOGS.nonGenderedNames).toContain(npc.name.firstName);
  });

  it('devrait inclure des informations d\'affichage', () => {
    const npc = generateTravellerNpc({
      role: 'pilot',
      citizenCategory: 'average',
      experience: 'regular',
      gender: 'male'
    });
    
    expect(npc.display.roleLabel).toBe('Pilote');
    expect(npc.display.categoryLabel).toBe('Moyenne');
    expect(npc.display.experienceLabel).toBe('Régulier');
    expect(npc.display.genderLabel).toBe('Masculin');
    expect(npc.display.characteristicLabels).toBeDefined();
  });

  it('devrait avoir un UPP valide', () => {
    const npc = generateTravellerNpc();
    expect(npc.upp).toMatch(/^[0-9A-F]{6}$/);
  });

  it('devrait avoir des compétences au format mgt2e', () => {
    const npc = generateTravellerNpc();
    expect(npc.skillsForActor).toBeDefined();
    expect(Array.isArray(npc.skillsForActor)).toBe(true);
    npc.skillsForActor.forEach(skill => {
      expect(skill.name).toBeDefined();
      expect(skill.level).toBeDefined();
    });
  });
});

// ============================================================================
// Tests du ModuleCache
// ============================================================================

describe('ModuleCache', () => {
  let cache;

  beforeEach(() => {
    cache = new ModuleCache('test-module', 1000);
  });

  describe('getOrFetch', () => {
    it('devrait fetch et cacher une valeur', async () => {
      const fetchFn = jest.fn().mockResolvedValue('test-value');
      const result = await cache.getOrFetch('key1', fetchFn);
      
      expect(result).toBe('test-value');
      expect(fetchFn).toHaveBeenCalledTimes(1);
    });

    it('devrait retourner la valeur du cache sans refetch', async () => {
      const fetchFn = jest.fn().mockResolvedValue('test-value');
      
      await cache.getOrFetch('key1', fetchFn);
      const result = await cache.getOrFetch('key1', fetchFn);
      
      expect(result).toBe('test-value');
      expect(fetchFn).toHaveBeenCalledTimes(1); // Pas appelé une deuxième fois
    });

    it('devrait gérer les erreurs de fetch', async () => {
      const error = new Error('Fetch error');
      const fetchFn = jest.fn().mockRejectedValue(error);
      
      await expect(cache.getOrFetch('key1', fetchFn)).rejects.toThrow('Fetch error');
      expect(fetchFn).toHaveBeenCalledTimes(1);
    });

    it('devrait forcer le rafraîchissement avec forceRefresh', async () => {
      const fetchFn = jest.fn()
        .mockResolvedValueOnce('value1')
        .mockResolvedValueOnce('value2');
      
      await cache.getOrFetch('key1', fetchFn);
      const result = await cache.getOrFetch('key1', fetchFn, { forceRefresh: true });
      
      expect(result).toBe('value2');
      expect(fetchFn).toHaveBeenCalledTimes(2);
    });

    it('devrait cloner profondément les résultats', async () => {
      const original = { nested: { value: 1 } };
      const fetchFn = jest.fn().mockResolvedValue(original);
      
      const result1 = await cache.getOrFetch('key1', fetchFn);
      const result2 = await cache.getOrFetch('key1', fetchFn);
      
      result1.nested.value = 2;
      
      expect(result2.nested.value).toBe(1);
      expect(original.nested.value).toBe(1);
    });
  });

  describe('clear', () => {
    it('devrait supprimer une entrée spécifique', async () => {
      const fetchFn = jest.fn().mockResolvedValue('test-value');
      
      await cache.getOrFetch('key1', fetchFn);
      cache.clear('key1');
      
      await cache.getOrFetch('key1', fetchFn);
      expect(fetchFn).toHaveBeenCalledTimes(2); // Fetch à nouveau car cache vidé
    });
  });

  describe('clearAll', () => {
    it('devrait vider tout le cache', async () => {
      const fetchFn = jest.fn().mockResolvedValue('test-value');
      
      await cache.getOrFetch('key1', fetchFn);
      await cache.getOrFetch('key2', fetchFn);
      cache.clearAll();
      
      await cache.getOrFetch('key1', fetchFn);
      await cache.getOrFetch('key2', fetchFn);
      expect(fetchFn).toHaveBeenCalledTimes(4); // Fetch à nouveau pour les deux clés
    });
  });

  describe('has', () => {
    it('devrait retourner true pour une clé en cache', async () => {
      const fetchFn = jest.fn().mockResolvedValue('test-value');
      
      await cache.getOrFetch('key1', fetchFn);
      expect(cache.has('key1')).toBe(true);
    });

    it('devrait retourner false pour une clé non en cache', () => {
      expect(cache.has('nonexistent')).toBe(false);
    });
  });

  describe('get', () => {
    it('devrait retourner la valeur du cache', async () => {
      const fetchFn = jest.fn().mockResolvedValue('test-value');
      
      await cache.getOrFetch('key1', fetchFn);
      const result = cache.get('key1');
      
      expect(result).toBe('test-value');
    });

    it('devrait retourner null pour une clé non en cache', () => {
      expect(cache.get('nonexistent')).toBeNull();
    });
  });
});

// ============================================================================
// Tests de TravellerNpcError
// ============================================================================

describe('TravellerNpcError', () => {
  it('devrait créer une erreur avec message et code', () => {
    const error = new TravellerNpcError('Test error', 'TEST_ERROR');
    expect(error.message).toBe('Test error');
    expect(error.code).toBe('TEST_ERROR');
    expect(error.name).toBe('TravellerNpcError');
    expect(error.isTravellerNpcError).toBe(true);
    expect(error.details).toEqual({});
    expect(error.timestamp).toBeDefined();
  });

  it('devrait créer une erreur avec des détails', () => {
    const details = { foo: 'bar' };
    const error = new TravellerNpcError('Test error', 'TEST_ERROR', details);
    expect(error.details).toEqual(details);
  });

  it('devrait créer une erreur à partir d\'une erreur existante', () => {
    const original = new Error('Original error');
    const error = TravellerNpcError.from(original, 'WRAPPED_ERROR');
    
    expect(error.message).toBe('Original error');
    expect(error.code).toBe('WRAPPED_ERROR');
    expect(error.details.originalError).toBe(original);
  });

  it('devrait retourner l\'erreur existante si c\'est déjà une TravellerNpcError', () => {
    const original = new TravellerNpcError('Original', 'ORIG');
    const error = TravellerNpcError.from(original, 'WRAPPED');
    
    expect(error).toBe(original);
  });

  it('devrait gérer undefined comme erreur originale', () => {
    const error = TravellerNpcError.from(undefined, 'UNKNOWN');
    expect(error.message).toBe('Erreur inconnue');
    expect(error.code).toBe('UNKNOWN');
  });
});

// ============================================================================
// Tests des données de configuration
// ============================================================================

describe('Données de configuration', () => {
  describe('CITIZEN_CATEGORY_LIST', () => {
    it('devrait contenir toutes les catégories', () => {
      expect(CITIZEN_CATEGORY_LIST).toHaveLength(4);
      expect(CITIZEN_CATEGORY_LIST).toContain(CITIZEN_CATEGORY.BELOW_AVERAGE);
      expect(CITIZEN_CATEGORY_LIST).toContain(CITIZEN_CATEGORY.AVERAGE);
      expect(CITIZEN_CATEGORY_LIST).toContain(CITIZEN_CATEGORY.ABOVE_AVERAGE);
      expect(CITIZEN_CATEGORY_LIST).toContain(CITIZEN_CATEGORY.EXCEPTIONAL);
    });
  });

  describe('EXPERIENCE_LEVEL_LIST', () => {
    it('devrait contenir tous les niveaux', () => {
      expect(EXPERIENCE_LEVEL_LIST).toHaveLength(6);
      expect(EXPERIENCE_LEVEL_LIST).toContain(EXPERIENCE_LEVEL.RECRUIT);
      expect(EXPERIENCE_LEVEL_LIST).toContain(EXPERIENCE_LEVEL.ROOKIE);
      expect(EXPERIENCE_LEVEL_LIST).toContain(EXPERIENCE_LEVEL.INTERMEDIATE);
      expect(EXPERIENCE_LEVEL_LIST).toContain(EXPERIENCE_LEVEL.REGULAR);
      expect(EXPERIENCE_LEVEL_LIST).toContain(EXPERIENCE_LEVEL.VETERAN);
      expect(EXPERIENCE_LEVEL_LIST).toContain(EXPERIENCE_LEVEL.ELITE);
    });
  });

  describe('ROLE_LIST', () => {
    it('devrait contenir tous les rôles', () => {
      expect(ROLE_LIST.length).toBeGreaterThan(10);
      expect(ROLE_LIST).toContain(ROLE.PILOT);
      expect(ROLE_LIST).toContain(ROLE.NAVIGATOR);
      expect(ROLE_LIST).toContain(ROLE.ENGINEER);
      expect(ROLE_LIST).toContain(ROLE.MEDIC);
    });
  });

  describe('GENDER_LIST', () => {
    it('devrait contenir tous les genres', () => {
      expect(GENDER_LIST).toHaveLength(3);
      expect(GENDER_LIST).toContain(GENDER.UNSPECIFIED);
      expect(GENDER_LIST).toContain(GENDER.FEMALE);
      expect(GENDER_LIST).toContain(GENDER.MALE);
    });
  });

  describe('CHARACTERISTIC_LIST', () => {
    it('devrait contenir toutes les caractéristiques', () => {
      expect(CHARACTERISTIC_LIST).toHaveLength(6);
      expect(CHARACTERISTIC_LIST).toContain(CHARACTERISTIC.STR);
      expect(CHARACTERISTIC_LIST).toContain(CHARACTERISTIC.DEX);
      expect(CHARACTERISTIC_LIST).toContain(CHARACTERISTIC.END);
      expect(CHARACTERISTIC_LIST).toContain(CHARACTERISTIC.INT);
      expect(CHARACTERISTIC_LIST).toContain(CHARACTERISTIC.EDU);
      expect(CHARACTERISTIC_LIST).toContain(CHARACTERISTIC.SOC);
    });
  });

  describe('UPP_ORDER', () => {
    it('devrait contenir l\'ordre correct des caractéristiques', () => {
      expect(UPP_ORDER).toEqual(['STR', 'DEX', 'END', 'INT', 'EDU', 'SOC']);
    });
  });

  describe('ERROR_CODES', () => {
    it('devrait contenir tous les codes d\'erreur', () => {
      expect(ERROR_CODES.INVALID_OPTIONS).toBe('INVALID_OPTIONS');
      expect(ERROR_CODES.INVALID_ROLE).toBe('INVALID_ROLE');
      expect(ERROR_CODES.INVALID_CATEGORY).toBe('INVALID_CATEGORY');
      expect(ERROR_CODES.INVALID_EXPERIENCE).toBe('INVALID_EXPERIENCE');
      expect(ERROR_CODES.INVALID_GENDER).toBe('INVALID_GENDER');
      expect(ERROR_CODES.ACTOR_CREATION_FAILED).toBe('ACTOR_CREATION_FAILED');
      expect(ERROR_CODES.MGT2E_NOT_ACTIVE).toBe('MGT2E_NOT_ACTIVE');
      expect(ERROR_CODES.BASE_ACTOR_NOT_FOUND).toBe('BASE_ACTOR_NOT_FOUND');
    });
  });

  describe('DEFAULT_OPTIONS', () => {
    it('devrait avoir des valeurs par défaut valides', () => {
      expect(DEFAULT_OPTIONS.citizenCategory).toBe('average');
      expect(DEFAULT_OPTIONS.experience).toBe('regular');
      expect(DEFAULT_OPTIONS.role).toBe('pilot');
      expect(DEFAULT_OPTIONS.gender).toBe('unspecified');
      expect(DEFAULT_OPTIONS.createActor).toBe(false);
      expect(DEFAULT_OPTIONS.openCreatedActor).toBe(true);
    });
  });

  describe('NAME_CATALOGS', () => {
    it('devrait avoir des catalogues de noms définis', () => {
      expect(NAME_CATALOGS.surnames).toBeDefined();
      expect(NAME_CATALOGS.surnames.length).toBeGreaterThan(0);
      expect(NAME_CATALOGS.nonGenderedNames).toBeDefined();
      expect(NAME_CATALOGS.nonGenderedNames.length).toBeGreaterThan(0);
      expect(NAME_CATALOGS.femaleNames).toBeDefined();
      expect(NAME_CATALOGS.femaleNames.length).toBeGreaterThan(0);
      expect(NAME_CATALOGS.maleNames).toBeDefined();
      expect(NAME_CATALOGS.maleNames.length).toBeGreaterThan(0);
    });
  });

  describe('CHARACTERISTIC_PRIORITIES', () => {
    it('devrait avoir des priorités pour chaque rôle', () => {
      expect(CHARACTERISTIC_PRIORITIES.pilot).toBeDefined();
      expect(CHARACTERISTIC_PRIORITIES.navigator).toBeDefined();
      expect(CHARACTERISTIC_PRIORITIES.engineer).toBeDefined();
      expect(CHARACTERISTIC_PRIORITIES.medic).toBeDefined();
      expect(CHARACTERISTIC_PRIORITIES.marine).toBeDefined();
    });

    it('devrait avoir des groupes high, medium, low', () => {
      const priorities = CHARACTERISTIC_PRIORITIES.pilot;
      expect(priorities.high).toBeDefined();
      expect(priorities.medium).toBeDefined();
      expect(priorities.low).toBeDefined();
      expect(priorities.high.length + priorities.medium.length + priorities.low.length).toBe(6);
    });
  });

  describe('ROLE_SKILLS', () => {
    it('devrait avoir des compétences pour chaque rôle', () => {
      expect(ROLE_SKILLS.pilot).toBeDefined();
      expect(ROLE_SKILLS.navigator).toBeDefined();
      expect(ROLE_SKILLS.engineer).toBeDefined();
      expect(ROLE_SKILLS.medic).toBeDefined();
      expect(ROLE_SKILLS.marine).toBeDefined();
    });

    it('devrait avoir au moins 10 compétences par rôle', () => {
      Object.values(ROLE_SKILLS).forEach(skills => {
        expect(skills.length).toBeGreaterThanOrEqual(10);
      });
    });
  });
});

// ============================================================================
// Tests d\'intégration (si environnement Foundry disponible)
// ============================================================================

// Ces tests sont optionnels et nécessitent un environnement Foundry complet
// Ils sont marqués comme skip par défaut

describe.skip('Intégration Foundry (nécessite environnement Foundry)', () => {
  describe('buildMgt2eCharacteristics', () => {
    it('devrait construire les caractéristiques au format mgt2e', () => {
      const existing = {};
      const characteristics = {
        STR: 8,
        DEX: 9,
        END: 7,
        INT: 10,
        EDU: 8,
        SOC: 6
      };
      
      const result = buildMgt2eCharacteristics(existing, characteristics);
      
      expect(result.STR).toBeDefined();
      expect(result.STR.value).toBe(8);
      expect(result.STR.dm).toBe(calculateDm(8));
    });
  });

  describe('buildMgt2eSkills', () => {
    it('devrait construire les compétences au format mgt2e', () => {
      const existing = {};
      const skills = [
        { name: 'Pilot-Spacecraft', level: 2 },
        { name: 'Gun Combat', level: 1 }
      ];
      
      const result = buildMgt2eSkills(existing, skills);
      
      expect(result).toBeDefined();
    });
  });
});

// ============================================================================
// Export pour les tests manuels
// ============================================================================

if (typeof globalThis !== 'undefined') {
  globalThis.TravellerNpcGeneratorTests = {
    generateCharacteristics,
    generateSkills,
    generateTravellerNpc,
    validateOptions,
    toHex,
    calculateDm,
    pickRandomItem,
    shuffleArray,
    TravellerNpcError,
    ModuleCache,
    runAllTests: () => {
      console.log('Exécutant les tests du générateur de PNJ Traveller...');
      console.log('Tous les tests sont définis. Exécutez avec Jest pour voir les résultats.');
    }
  };
}
