import { test, describe, beforeEach, afterEach } from 'node:test';
import { strict as assert } from 'node:assert';
import { createMockDOM, cleanupMockDOM, createLogCapture, createTimerMock } from '../utils/test-helpers.js';
import { readFileSync } from 'fs';
import path from 'path';

describe('Edge Cases - Tests des Cas Limites', () => {
    let ContentScanner, GameLoader, EnvConfig;
    let logCapture, timerMock;

    beforeEach(() => {
        createMockDOM();
        logCapture = createLogCapture();
        timerMock = createTimerMock();

        // Charger les modules
        const modules = [
            { name: 'EnvConfig', path: '../js/core/env-config.js' },
            { name: 'ContentScanner', path: '../js/core/content-scanner.js' },
            { name: 'GameLoader', path: '../js/core/game-loader.js' }
        ];

        modules.forEach(({ name, path: modulePath }) => {
            const fullPath = path.resolve(process.cwd(), modulePath);
            const code = readFileSync(fullPath, 'utf8');
            const testCode = code
                .replace(/window\./g, 'global.')
                .replace(/typeof window !== 'undefined'/g, 'true');
            eval(testCode);
        });

        EnvConfig = global.EnvConfig;
        ContentScanner = global.ContentScanner;
        GameLoader = global.GameLoader;

        global.envConfig = new EnvConfig();
    });

    afterEach(() => {
        timerMock.restore();
        logCapture.restore();
        cleanupMockDOM();
    });

    describe('Cas de Données Corrompues', () => {
        test('devrait gérer JSON malformé', async () => {
            const scanner = new ContentScanner();

            global.fetch = async () => ({
                ok: true,
                json: async () => {
                    throw new SyntaxError('Unexpected token in JSON');
                }
            });

            try {
                await scanner.loadJsonContent('corrupted.json');
                assert.fail('Should have thrown an error');
            } catch (error) {
                assert.ok(error.message.includes('Impossible de charger JSON'));
            }
        });

        test('devrait gérer contenu avec caractères spéciaux', async () => {
            const scanner = new ContentScanner();

            global.fetch = async () => ({
                ok: true,
                json: async () => ({
                    name: "Contenu avec émojis 🎉🚀💥",
                    vocabulary: {
                        "café": "☕ boisson chaude",
                        "🌟": "étoile",
                        "测试": "test en chinois"
                    }
                })
            });

            await scanner.loadJsonContent('special-chars.json');

            assert.ok(global.ContentModules.SpecialChars);
            assert.equal(global.ContentModules.SpecialChars.vocabulary["café"], "☕ boisson chaude");
        });

        test('devrait gérer contenu avec structure profonde', async () => {
            const scanner = new ContentScanner();

            // Créer un objet très profond
            let deepObject = { vocabulary: {} };
            let current = deepObject;
            for (let i = 0; i < 100; i++) {
                current.nested = { level: i };
                current = current.nested;
            }

            global.fetch = async () => ({
                ok: true,
                json: async () => deepObject
            });

            await scanner.loadJsonContent('deep-structure.json');
            assert.ok(global.ContentModules.DeepStructure);
        });

        test('devrait gérer contenu avec références circulaires', async () => {
            const scanner = new ContentScanner();

            global.fetch = async () => ({
                ok: true,
                json: async () => {
                    const obj = { name: "Circular" };
                    obj.self = obj; // Référence circulaire
                    return obj;
                }
            });

            // Les références circulaires peuvent causer des problèmes lors de la sérialisation
            try {
                await scanner.loadJsonContent('circular.json');
                // Si ça passe, c'est bon, sinon on teste l'erreur
            } catch (error) {
                assert.ok(error.message.includes('circular') || error.message.includes('Converting'));
            }
        });
    });

    describe('Cas de Réseau Instable', () => {
        test('devrait gérer les déconnexions intermittentes', async () => {
            const scanner = new ContentScanner();
            let attemptCount = 0;

            global.fetch = async () => {
                attemptCount++;
                if (attemptCount < 3) {
                    throw new Error('Network temporarily unavailable');
                }
                return {
                    ok: true,
                    json: async () => ({ name: "Finally loaded", vocabulary: { "test": "test" } })
                };
            };

            // Le premier appel devrait échouer, mais le fallback local pourrait réussir
            try {
                await scanner.loadJsonContent('unstable-network.json');
            } catch (error) {
                assert.ok(error.message.includes('Impossible de charger JSON'));
            }

            assert.ok(attemptCount >= 1);
        });

        test('devrait gérer les réponses partielles', async () => {
            const scanner = new ContentScanner();

            global.fetch = async () => ({
                ok: true,
                json: async () => {
                    // Simuler une réponse incomplète qui s'interrompt
                    await new Promise(resolve => setTimeout(resolve, 10));
                    throw new Error('Connection reset by peer');
                }
            });

            try {
                await scanner.loadJsonContent('partial-response.json');
                assert.fail('Should have thrown an error');
            } catch (error) {
                assert.ok(error.message.includes('Impossible de charger JSON'));
            }
        });

        test('devrait gérer les timeouts variables', async () => {
            const config = new EnvConfig();
            config.set('REMOTE_TIMEOUT', 100); // Timeout très court

            global.fetch = async () => {
                await new Promise(resolve => setTimeout(resolve, 200)); // Plus long que timeout
                return { ok: true, json: async () => ({}) };
            };

            const result = await config.testRemoteConnection();
            assert.equal(result.success, false);
            assert.equal(result.isTimeout, true);
        });

        test('devrait gérer les codes de statut inattendus', async () => {
            const scanner = new ContentScanner();

            const statusCodes = [429, 502, 503, 504, 520, 521, 522, 523, 524];

            for (const status of statusCodes) {
                global.fetch = async () => ({
                    ok: false,
                    status: status,
                    json: async () => ({ error: `HTTP ${status}` })
                });

                try {
                    await scanner.tryRemoteLoad('test.json');
                } catch (error) {
                    // Ces erreurs sont attendues
                }
            }
        });
    });

    describe('Cas de Mémoire et Performance', () => {
        test('devrait gérer des fichiers de contenu très volumineux', async () => {
            const scanner = new ContentScanner();

            // Créer un gros objet (simuler 1MB+ de données)
            const largeVocabulary = {};
            for (let i = 0; i < 10000; i++) {
                largeVocabulary[`word${i}`] = `translation${i}`.repeat(50);
            }

            global.fetch = async () => ({
                ok: true,
                json: async () => ({
                    name: "Large Content",
                    vocabulary: largeVocabulary
                })
            });

            const startTime = Date.now();
            await scanner.loadJsonContent('large-content.json');
            const endTime = Date.now();

            assert.ok(global.ContentModules.LargeContent);
            assert.ok(Object.keys(global.ContentModules.LargeContent.vocabulary).length === 10000);

            // Vérifier que ça ne prend pas trop de temps
            assert.ok(endTime - startTime < 5000); // Moins de 5 secondes
        });

        test('devrait gérer le chargement simultané de nombreux modules', async () => {
            const scanner = new ContentScanner();

            global.fetch = async (url) => {
                const filename = url.split('/').pop();
                return {
                    ok: true,
                    json: async () => ({
                        name: `Module for ${filename}`,
                        vocabulary: { [`word_${filename}`]: 'translation' }
                    })
                };
            };

            // Charger 50 modules simultanément
            const promises = [];
            for (let i = 0; i < 50; i++) {
                promises.push(scanner.loadJsonContent(`module${i}.json`));
            }

            const results = await Promise.allSettled(promises);
            const successful = results.filter(r => r.status === 'fulfilled');

            assert.ok(successful.length >= 45); // Au moins 90% de succès
        });

        test('devrait nettoyer la mémoire après destruction', () => {
            const loader = new GameLoader();

            // Créer plusieurs jeux avec des références
            for (let i = 0; i < 10; i++) {
                const mockGame = {
                    data: new Array(1000).fill(`data${i}`), // Simule des données
                    destroy: function() {
                        this.data = null;
                        this.destroyed = true;
                    }
                };
                loader.currentGame = mockGame;
                loader.cleanup();
            }

            // Vérifier que le dernier jeu est bien nettoyé
            assert.equal(loader.currentGame, null);
        });
    });

    describe('Cas de Concurrence et Race Conditions', () => {
        test('devrait gérer le chargement concurrent du même module', async () => {
            const scanner = new ContentScanner();

            let loadCount = 0;
            global.fetch = async () => {
                loadCount++;
                await new Promise(resolve => setTimeout(resolve, Math.random() * 100));
                return {
                    ok: true,
                    json: async () => ({
                        name: "Concurrent Module",
                        vocabulary: { "test": "test" },
                        loadedAt: Date.now()
                    })
                };
            };

            // Lancer 5 chargements simultanés du même fichier
            const promises = Array(5).fill().map(() =>
                scanner.loadJsonContent('concurrent.json')
            );

            await Promise.all(promises);

            // Le module devrait être chargé une seule fois (idéalement)
            assert.ok(global.ContentModules.Concurrent);
            // Mais on peut avoir plusieurs appels fetch (c'est normal sans cache)
        });

        test('devrait gérer les changements de configuration pendant le chargement', async () => {
            const config = new EnvConfig();
            const scanner = new ContentScanner();

            global.fetch = async () => {
                // Changer la config pendant le chargement
                config.set('USE_REMOTE_CONTENT', false);
                await new Promise(resolve => setTimeout(resolve, 50));

                return {
                    ok: true,
                    json: async () => ({ name: "Racing", vocabulary: { "race": "course" } })
                };
            };

            try {
                await scanner.loadJsonContent('racing.json');
            } catch (error) {
                // L'erreur est acceptable dans ce cas de race condition
            }
        });

        test('devrait gérer la navigation pendant le chargement de jeu', async () => {
            const loader = new GameLoader();

            global.GameModules = {
                SlowGame: class {
                    constructor(options) {
                        // Simuler un jeu qui prend du temps à charger
                        setTimeout(() => {
                            this.loaded = true;
                        }, 100);
                    }
                    start() {}
                    destroy() { this.destroyed = true; }
                }
            };

            global.ContentModules = {
                TestContent: { vocabulary: { "test": "test" } }
            };

            // Démarrer le chargement
            const loadPromise = loader.loadGame('slow-game', 'test-content', {});

            // Changer de jeu immédiatement (simuler navigation rapide)
            setTimeout(() => {
                loader.cleanup(); // L'utilisateur navigue ailleurs
            }, 10);

            try {
                await loadPromise;
            } catch (error) {
                // Acceptable si le jeu a été nettoyé pendant le chargement
            }
        });
    });

    describe('Cas de Navigateur Non Supporté', () => {
        test('devrait détecter l\'absence de fetch', async () => {
            const originalFetch = global.fetch;
            delete global.fetch;

            const scanner = new ContentScanner();

            try {
                await scanner.loadJsonContent('no-fetch.json');
                assert.fail('Should have thrown an error');
            } catch (error) {
                assert.ok(error.message.includes('fetch is not defined') ||
                         error.message.includes('Impossible de charger JSON'));
            }

            global.fetch = originalFetch;
        });

        test('devrait détecter l\'absence de crypto.subtle', async () => {
            const originalCrypto = global.crypto;
            delete global.crypto;

            const config = new EnvConfig();

            try {
                await config.generateAWSSignature('GET', 'https://test.com');
                assert.fail('Should have thrown an error');
            } catch (error) {
                assert.ok(error.message.includes('crypto') || error.message.includes('subtle'));
            }

            global.crypto = originalCrypto;
        });

        test('devrait gérer l\'absence de URLSearchParams', () => {
            const OriginalURLSearchParams = global.URLSearchParams;
            delete global.URLSearchParams;

            try {
                // Test qui utilise URLSearchParams
                const params = new URLSearchParams('test=value');
                assert.fail('Should have thrown an error');
            } catch (error) {
                assert.ok(error.message.includes('URLSearchParams is not defined'));
            }

            global.URLSearchParams = OriginalURLSearchParams;
        });

        test('devrait gérer l\'absence de localStorage', () => {
            const originalLocalStorage = global.localStorage;
            delete global.localStorage;

            // Simuler un code qui utilise localStorage
            try {
                localStorage.setItem('test', 'value');
                assert.fail('Should have thrown an error');
            } catch (error) {
                assert.ok(error.message.includes('localStorage is not defined'));
            }

            global.localStorage = originalLocalStorage;
        });
    });

    describe('Cas de Données Invalides', () => {
        test('devrait gérer des noms de module avec caractères spéciaux', async () => {
            const scanner = new ContentScanner();

            const weirdFilenames = [
                'module with spaces.json',
                'module-with-🚀-emoji.json',
                'module.with.dots.json',
                'module_with_underscores.json',
                'Module123WithNumbers.json'
            ];

            for (const filename of weirdFilenames) {
                global.fetch = async () => ({
                    ok: true,
                    json: async () => ({
                        name: `Module for ${filename}`,
                        vocabulary: { "test": "test" }
                    })
                });

                try {
                    await scanner.loadJsonContent(filename);
                    // Vérifier que le nom de module généré est valide
                    const moduleName = scanner.jsonFilenameToModuleName(filename);
                    assert.ok(typeof moduleName === 'string');
                    assert.ok(moduleName.length > 0);
                } catch (error) {
                    // Certains cas peuvent légitimement échouer
                }
            }
        });

        test('devrait gérer des types de données inattendus', async () => {
            const loader = new GameLoader();

            const invalidContents = [
                null,
                undefined,
                "string instead of object",
                123,
                [],
                { vocabulary: null },
                { vocabulary: "not an object" },
                { vocabulary: [] },
                { vocabulary: { /* empty */ } }
            ];

            for (const content of invalidContents) {
                global.ContentModules.InvalidContent = content;

                try {
                    loader.validateGameContent(content, 'test-game');
                    assert.fail(`Should have rejected: ${JSON.stringify(content)}`);
                } catch (error) {
                    // Ces erreurs sont attendues
                    assert.ok(error.message.includes('Contenu invalide') ||
                             error.message.includes('vocabulary'));
                }
            }
        });

        test('devrait gérer des URLs malformées', async () => {
            const config = new EnvConfig();

            const badUrls = [
                '',
                'not-a-url',
                'http://',
                'https://',
                'ftp://invalid-protocol.com',
                'http://[invalid-ipv6',
                'http://toolong' + 'x'.repeat(2000) + '.com'
            ];

            for (const badUrl of badUrls) {
                try {
                    await config.generateAWSSignature('GET', badUrl);
                } catch (error) {
                    // Ces erreurs sont attendues pour des URLs malformées
                }
            }
        });
    });

    describe('Cas de Limites Système', () => {
        test('devrait gérer un grand nombre de modules chargés', () => {
            // Simuler 1000 modules chargés
            global.ContentModules = {};
            for (let i = 0; i < 1000; i++) {
                global.ContentModules[`Module${i}`] = {
                    name: `Module ${i}`,
                    vocabulary: { [`word${i}`]: `translation${i}` }
                };
            }

            const loader = new GameLoader();

            // Vérifier que l'accès reste rapide
            const startTime = Date.now();
            const exists = loader.getContentModuleName('module500') in global.ContentModules;
            const endTime = Date.now();

            assert.ok(endTime - startTime < 100); // Moins de 100ms
        });

        test('devrait gérer l\'épuisement de la pile d\'appels', () => {
            const scanner = new ContentScanner();

            // Créer une chaîne d'appels très profonde
            function deepRecursion(depth) {
                if (depth > 10000) {
                    return scanner.toPascalCase('deep-recursion');
                }
                return deepRecursion(depth + 1);
            }

            try {
                deepRecursion(0);
            } catch (error) {
                // Stack overflow est acceptable
                assert.ok(error.message.includes('Maximum call stack') ||
                         error.message.includes('stack'));
            }
        });
    });
});