const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const { pool } = require('../db');

class EdecSubmissionService {
    constructor() {
        this.baseUrl = 'https://e-dec-web.ezv.admin.ch/webdec/main.xhtml';
        this.downloadDir = path.join(__dirname, '../../edecpdf');
        this.screenshotDir = path.join(__dirname, '../../screenshots');
        this.statusListeners = new Map();

        [this.downloadDir, this.screenshotDir].forEach(dir => {
            if (!fs.existsSync(dir)) {
                fs.mkdirSync(dir, { recursive: true });
            }
        });
    }

    // ============= GESTION SSE & STATUS =============
    registerListener(sessionToken, listener) {
        this.statusListeners.set(sessionToken, listener);
        console.log(`[SSE] Listener enregistré pour ${sessionToken}`);
    }

    removeListener(sessionToken) {
        this.statusListeners.delete(sessionToken);
        console.log(`[SSE] Listener supprimé pour ${sessionToken}`);
    }

    async updateStatus(sessionToken, status, step = null, progress = null) {
        const conn = await pool.getConnection();
        try {
            console.log(`[STATUS] ${sessionToken} -> ${status} (${progress}%)`);
            
            const errorMessage = status.includes('error') ? String(status) : null;
            await conn.execute(
                `UPDATE declarations SET status = ?, error_message = ? WHERE uuid = ?`,
                [status, errorMessage, sessionToken]
            );

            const listener = this.statusListeners.get(sessionToken);
            if (listener) {
                listener({
                    status,
                    step: step || 'En cours...',
                    progress: progress || 0
                });
            }
        } catch (error) {
            console.error(`[STATUS-ERROR] ${sessionToken}:`, error);
            throw error;
        } finally {
            conn.release();
        }
    }

    // ============= UTILITAIRES =============
    async takeScreenshot(page, name) {
        const screenshotPath = path.join(this.screenshotDir, `${name}_${Date.now()}.png`);
        await page.screenshot({ path: screenshotPath, fullPage: true });
        console.log(`[SCREENSHOT] ${screenshotPath}`);
        return screenshotPath;
    }

    async clickById(page, elementId) {
        console.log(`[CLICK] Tentative de clic sur #${elementId}`);
        const clicked = await page.evaluate((id) => {
            const element = document.getElementById(id);
            if (element) {
                element.click();
                return true;
            }
            return false;
        }, elementId);
        
        if (!clicked) {
            throw new Error(`Élément #${elementId} introuvable`);
        }
        console.log(`[CLICK] ✓ Clic réussi sur #${elementId}`);
    }

    async checkCheckbox(page, checkboxId) {
        console.log(`[CHECKBOX] Tentative de cocher #${checkboxId}`);
        const result = await page.evaluate((id) => {
            const checkbox = document.getElementById(id);
            if (checkbox && checkbox.type === 'checkbox') {
                checkbox.checked = true;
                return checkbox.checked;
            }
            return false;
        }, checkboxId);

        if (!result) {
            throw new Error(`Impossible de cocher la checkbox #${checkboxId}`);
        }
        console.log(`[CHECKBOX] ✓ Checkbox cochée #${checkboxId}`);
    }

    async isPopupVisible(page, popupId) {
        return await page.evaluate((id) => {
            const popup = document.getElementById(id);
            return popup && popup.style.visibility === 'visible';
        }, popupId);
    }

    async waitForUrlChange(page, originalUrl, timeout = 10000) {
        try {
            await page.waitForFunction(
                (expectedUrl) => window.location.href !== expectedUrl,
                originalUrl,
                { timeout }
            );
            console.log(`[URL] ✓ Changement détecté: ${page.url()}`);
            return true;
        } catch {
            console.log(`[URL] ✗ Aucun changement après ${timeout}ms`);
            return false;
        }
    }

    // ============= ÉTAPE 1: UPLOAD XML =============
    async uploadXML(page, xmlFilePath, sessionToken) {
        console.log('[ÉTAPE 1] Début upload XML');
        await this.updateStatus(sessionToken, 'submitting', 'Ouverture formulaire...', 15);

        // Clic sur "Zollanmeldung laden"
        console.log('[EDEC Submit] Clic sur "Zollanmeldung laden"...');
        await page.click('#mainform\\:loadDeclaration');
        await page.waitForTimeout(2000);

        console.log('[EDEC Submit] Attente de la popup de chargement...');
        await page.waitForSelector('#mainform\\:declarationLoadPopup', { 
            state: 'visible',
            timeout: 10000 
        });

        console.log('[EDEC Submit] Recherche de l\'iframe d\'upload...');
        const iframeElement = await page.waitForSelector(
            'iframe#mainform\\:inputFileComponent\\:uploadFrame',
            { state: 'attached', timeout: 10000 }
        );

        const frame = await iframeElement.contentFrame();
        if (!frame) {
            throw new Error('Impossible d\'accéder au contenu de l\'iframe');
        }

        // Upload du XML
        await this.updateStatus(sessionToken, 'submitting', 'Upload du fichier XML...', 30);
        console.log('[EDEC Submit] Upload du fichier XML...');
        const fileInput = await frame.waitForSelector('input[type="file"]', {
            state: 'attached',
            timeout: 10000
        });

        await fileInput.setInputFiles(xmlFilePath);
        
        // Sauvegarde de l'URL avant confirmation
        const urlBeforeConfirm = page.url();
        console.log(`[URL-BEFORE-CONFIRM] ${urlBeforeConfirm}`);

        console.log('[EDEC Submit] Clic sur le bouton OK...');
        await page.click('a#mainform\\:confirmButton');
        
        // Vérification que l'upload a réussi (changement d'URL ou élément visible)
        console.log('[EDEC Submit] Vérification succès upload...');
        
        const uploadSuccess = await Promise.race([
            this.waitForUrlChange(page, urlBeforeConfirm, 10000),
            page.waitForSelector('a#j_id49\\:j_id69', { timeout: 10000 }).then(() => 'button_found'),
            page.waitForSelector('span.iceOutFrmt', { timeout: 10000 }).then(() => 'confirmation_found')
        ]);

        if (!uploadSuccess) {
            await this.takeScreenshot(page, `upload_failed_${sessionToken}`);
            const currentUrl = page.url();
            console.log(`[UPLOAD-ERROR] Aucun changement détecté. URL actuelle: ${currentUrl}`);
            throw new Error('Échec de l\'upload du XML - aucune confirmation détectée');
        }

        console.log('[EDEC Submit] ✓ Upload XML réussi');
        console.log(`[LANDING PAGE 2] URL actuelle: ${page.url()}`);
    }

    // ============= ÉTAPE 2: ENVOI & GESTION ERREURS =============
    async submitAndHandleErrors(page, sessionToken) {
        console.log('[ÉTAPE 2] Début envoi et gestion erreurs');
        await this.updateStatus(sessionToken, 'processing', 'Envoi de la déclaration...', 50);

        let maxAttempts = 3;
        let attempt = 0;

        while (attempt < maxAttempts) {
            attempt++;
            console.log(`[ENVOI] Tentative ${attempt}/${maxAttempts}`);

            // Clic sur le bouton Senden
            const urlBeforeSend = page.url();
            await this.clickById(page, 'j_id49:j_id69');
            
            // Attendre 4 secondes pour laisser le temps à la page de réagir
            await page.waitForTimeout(4000);

            // IMPORTANT: Vérifier d'abord si l'URL a changé (succès direct)
            const currentUrl = page.url();
            if (currentUrl !== urlBeforeSend) {
                console.log('[ENVOI] ✓ URL changée immédiatement après Senden - Succès!');
                console.log(`[ENVOI] Ancienne URL: ${urlBeforeSend}`);
                console.log(`[ENVOI] Nouvelle URL: ${currentUrl}`);
                return true;
            }

            // Si pas de changement d'URL, vérifier si popup d'invraisemblance apparaît
            const inplausibleVisible = await this.isPopupVisible(page, 'mainform:inplausibleDeclarationPopup');
            
            if (inplausibleVisible) {
                console.log('[ENVOI] Popup d\'invraisemblance détectée, fermeture...');
                await this.clickById(page, 'mainform:cancelButton');
                await page.waitForTimeout(2000);

                // Vérifier les erreurs de validation
                await this.updateStatus(sessionToken, 'processing', 'Correction des erreurs...', 60 + (attempt * 5));
                const errorResult = await this.checkAndFixValidationErrors(page, sessionToken);
                
                if (!errorResult.fixed) {
                    throw new Error('Erreurs de validation non résolues');
                }

                // Si l'URL a changé pendant la correction = SUCCÈS!
                if (errorResult.urlChanged) {
                    console.log('[ÉTAPE 2] ✓ URL changée pendant la correction, passage à l\'étape 3');
                    return true;
                }

                // Réessayer l'envoi (continue = retour au début de la boucle while)
                console.log('[ENVOI] Erreurs corrigées, retour au début de la boucle...');
                continue;
            }

            // Si on arrive ici sans popup et sans changement d'URL, réessayer
            console.log('[ENVOI] Pas de popup ni de changement d\'URL, nouvelle tentative...');
        }

        throw new Error(`Échec après ${maxAttempts} tentatives d'envoi`);
    }

    // ============= GESTION DES ERREURS DE VALIDATION =============
    async checkAndFixValidationErrors(page, sessionToken) {
		console.log('[ERREURS] Vérification erreurs de validation...');
		
		const errorIconSelector = '#mainform\\:tree-d-1-0-c img[src*="exclamation.gif"][title*="Maske nicht korrekt ausgefüllt"]';
		await page.waitForTimeout(3000);

		const errorIcons = await page.$$(errorIconSelector);
		
		if (!errorIcons || errorIcons.length === 0) {
			console.log('[ERREURS] ✓ Aucune erreur détectée');
			return { fixed: true, urlChanged: false };
		}

		console.log(`[ERREURS] ${errorIcons.length} erreur(s) détectée(s)`);
		await this.takeScreenshot(page, `errors_detected_${sessionToken}`);

		// FIX ICI: Utiliser une boucle au lieu de .map()
		const errorIds = [];
		for (const icon of errorIcons) {
			const id = await icon.getAttribute('id');
			if (id) errorIds.push(id);
		}

        // Traiter chaque erreur
        for (let i = 0; i < errorIds.length; i++) {
            const errorId = errorIds[i];
            console.log(`[ERREURS] Traitement erreur ${i + 1}/${errorIds.length}: ${errorId}`);
            
            const result = await this.fixErrorById(page, errorId, sessionToken, i);
            
            // Si l'URL a changé pendant la correction, c'est un succès direct!
            if (result === 'SUCCESS_URL_CHANGED') {
                console.log('[ERREURS] ✓ Correction réussie avec changement d\'URL!');
                return { fixed: true, urlChanged: true };
            }
            
            if (!result) {
                console.log(`[ERREURS] ✗ Impossible de corriger ${errorId}`);
                return { fixed: false, urlChanged: false };
            }

            console.log(`[ERREURS] ✓ Correction de ${errorId} terminée`);
            await page.waitForTimeout(1000);
        }

        console.log('[ERREURS] ✓ Toutes les erreurs traitées');
        return { fixed: true, urlChanged: false };
    }

    async fixErrorById(page, errorId, sessionToken, errorIndex) {
        // Switch case basé sur l'ID de l'erreur
        switch (errorId) {
            case 'mainform:tree:n-1-0-1:j_id121':
                return await this.fixR125bError(page, sessionToken, errorIndex);
            
            // Ajouter d'autres cas ici pour d'autres types d'erreurs
            
            default:
                console.log(`[ERREURS] Type d'erreur non géré: ${errorId}`);
                return false;
        }
    }

	async fixR125bError(page, sessionToken, errorIndex) {
		try {
			console.log('[R125b] Début correction erreur R125b');
			
			// Clic sur le lien de l'erreur
			await this.clickById(page, 'mainform:tree:n-1-0-1:link');

			// Clic pour ouvrir le popup de détail
			await this.clickById(page, 'mainform:j_id224');
			
			// Attente explicite que le popup soit visible AVANT d'agir à l'intérieur
			await page.locator('#mainform\\:plausiPopup').waitFor({ state: 'visible', timeout: 15000 });
			console.log('[R125b] Popup de plausibilité détecté et visible.');

			// Cocher la checkbox de correction
			// On cible la checkbox qui est DANS le popup
			await this.checkCheckbox(page, 'mainform:j_id367');

			// Clic pour fermer le popup
			await this.clickById(page, 'mainform:plausiPopupCloseButton');
			
			// Attente explicite que le popup soit bien fermé
			await page.locator('#mainform\\:plausiPopup').waitFor({ state: 'hidden', timeout: 15000 });
			console.log('[R125b] Popup de plausibilité fermé.');

			await this.takeScreenshot(page, `r125b_fixed_${sessionToken}_${errorIndex}`);
			
			// Clic sur Senden après la correction
			const urlBeforeClick = page.url();
			console.log('[R125b] Clic sur Senden après correction...');
			await this.clickById(page, 'j_id49:j_id69');

			// Au lieu d'un délai, on attend que l'URL change
			// C'est la confirmation la plus fiable que l'action a réussi
			await page.waitForURL((url) => url.href !== urlBeforeClick, { timeout: 10000 }).catch(() => {
				console.log("[R125b] L'URL n'a pas changé après le clic sur Senden. Le processus continue.");
				// Si l'URL ne change pas, ce n'est pas forcément une erreur, on continue
			});

			// Vérification finale
			const currentUrl = page.url();
			if (currentUrl !== urlBeforeClick) {
				console.log('[R125b] ✓ URL changée après correction! Passage direct à l\'étape 3');
				return 'SUCCESS_URL_CHANGED'; 
			}

			console.log('[R125b] ✓ Erreur R125b corrigée, pas de changement d\'URL pour l\'instant.');
			return true;

		} catch (error) {
			console.error(`[R125b] ✗ Échec de la procédure de correction: ${error.message}`);
			await this.takeScreenshot(page, `r125b_error_${sessionToken}_${errorIndex}`);
			return false;
		}
	}

// ============= ÉTAPE 3: RÉCUPÉRATION DES PDF =============
    async downloadPDFs(page, sessionToken, declarationType) {
        console.log(`[ÉTAPE 3] Récupération des PDF (Type: ${declarationType})`);
        await this.updateStatus(sessionToken, 'processing', 'Téléchargement des PDF...', 90);

        // Extraction du numéro de déclaration
        let declarationNumber = null;
        try {
            // Attendre que les spans soient chargés
            await page.waitForSelector('span.iceOutFrmt', { timeout: 10000 });
            
            // Récupérer TOUS les spans pour trouver celui avec le numéro
            const allSpans = await page.$$('span.iceOutFrmt');
            console.log(`[PDF] ${allSpans.length} span.iceOutFrmt trouvés`);

            for (const span of allSpans) {
                const spanText = await span.textContent();
                console.log(`[PDF] Analyse span: ${spanText.substring(0, 100)}`);
                
                // Chercher le pattern CHWI ou CHWE
                const match = spanText.match(/(\d{2}CHW[EI]\d+)/);
                if (match) {
                    declarationNumber = match[1];
                    console.log(`[PDF] ✓ Numéro de déclaration trouvé: ${declarationNumber}`);
                    break;
                }
            }

            if (!declarationNumber) {
                console.warn('[PDF] ⚠ Aucun numéro de déclaration trouvé dans les spans');
            }
        } catch (error) {
            console.warn(`[PDF] Impossible d'extraire le numéro: ${error.message}`);
        }

        // Téléchargement des PDF selon le type
        await page.waitForSelector('a[href*=".pdf"]', { timeout: 15000 });
        const pdfLinks = await page.$$('a.downloadLink[href*=".pdf"]');

        console.log(`[PDF] ${pdfLinks.length} lien(s) PDF trouvé(s)`);

        let listePath = null;
        let bulletinPath = null;

        if (declarationType === 'EXPORT') {
            // EXPORT : 1 seul PDF (Liste d'exportation)
            if (pdfLinks.length < 1) {
                await this.takeScreenshot(page, `insufficient_pdfs_export_${sessionToken}`);
                throw new Error(`Aucun PDF trouvé pour l'export`);
            }

            listePath = path.join(this.downloadDir, `${sessionToken}_liste_exportation.pdf`);

            console.log('[PDF] Téléchargement Liste d\'exportation...');
            const downloadPromise = page.waitForEvent('download', { timeout: 30000 });
            await pdfLinks[0].click();
            const download = await downloadPromise;
            await download.saveAs(listePath);
            console.log(`[PDF] ✓ Liste d'exportation: ${listePath}`);

        } else {
            // IMPORT : 2 PDF (Liste d'importation + Bulletin de délivrance)
            if (pdfLinks.length < 2) {
                await this.takeScreenshot(page, `insufficient_pdfs_import_${sessionToken}`);
                throw new Error(`Seulement ${pdfLinks.length} PDF trouvé(s) pour l'import, 2 attendus`);
            }

            listePath = path.join(this.downloadDir, `${sessionToken}_liste_importation.pdf`);
            bulletinPath = path.join(this.downloadDir, `${sessionToken}_bulletin_delivrance.pdf`);

            // PDF 1: Liste d'importation
            console.log('[PDF] Téléchargement Liste d\'importation...');
            const downloadPromise1 = page.waitForEvent('download', { timeout: 30000 });
            await pdfLinks[0].click();
            const download1 = await downloadPromise1;
            await download1.saveAs(listePath);
            console.log(`[PDF] ✓ Liste d'importation: ${listePath}`);

            // PDF 2: Bulletin de délivrance
            console.log('[PDF] Téléchargement Bulletin de délivrance...');
            await page.waitForTimeout(1000);
            const downloadPromise2 = page.waitForEvent('download', { timeout: 30000 });
            await pdfLinks[1].click();
            const download2 = await downloadPromise2;
            await download2.saveAs(bulletinPath);
            console.log(`[PDF] ✓ Bulletin de délivrance: ${bulletinPath}`);
        }

        // Sauvegarde en base de données
        await this.saveToDatabase(sessionToken, declarationNumber || 'UNKNOWN', listePath, bulletinPath);

        console.log('[ÉTAPE 3] ✓ PDF récupérés avec succès');

        return {
            success: true,
            declarationNumber: declarationNumber || 'UNKNOWN',
            listePath,
            bulletinPath
        };
    }

    async saveToDatabase(sessionToken, declarationNumber, listePath, bulletinPath) {
        const conn = await pool.getConnection();
        try {
            await conn.execute(`
                UPDATE declarations 
                SET 
                    declaration_number = ?,
                    liste_importation_path = ?,
                    bulletin_delivrance_path = ?,
                    status = 'submitted',
                    submission_date = NOW(),
                    error_message = NULL
                WHERE uuid = ?
            `, [declarationNumber, listePath, bulletinPath, sessionToken]);

            const listener = this.statusListeners.get(sessionToken);
            if (listener) {
                listener({
                    status: 'submitted',
                    step: 'Déclaration soumise avec succès',
                    progress: 100,
                    declarationNumber,
                    listePath,
                    bulletinPath
                });
            }
        } finally {
            conn.release();
        }
    }

    // ============= MÉTHODE PRINCIPALE =============
    async submitDeclaration(xmlFilePath, sessionToken) {
		let browser = null;
		let page = null;

		try {
			console.log(`[DÉBUT] Soumission pour ${sessionToken}`);
			await this.updateStatus(sessionToken, 'submitting', 'Initialisation...', 5);

			// NOUVEAU : Récupérer le type de déclaration depuis la BD
			const conn = await pool.getConnection();
			let declarationType = 'IMPORT'; // Valeur par défaut
			try {
				const [rows] = await conn.execute(
					'SELECT declaration_type FROM declarations WHERE uuid = ?',
					[sessionToken]
				);
				if (rows && rows.length > 0) {
					declarationType = rows[0].declaration_type;
					console.log(`[TYPE] Déclaration type: ${declarationType}`);
				}
			} finally {
				conn.release();
			}

			// Lancement du navigateur
			browser = await chromium.launch({
				headless: true,
				args: [
					'--no-sandbox',
					'--disable-setuid-sandbox',
					'--disable-dev-shm-usage',
					'--disable-gpu'
				]
			});

			const context = await browser.newContext({
				acceptDownloads: true,
				userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
				viewport: { width: 1280, height: 720 }
			});

			page = await context.newPage();
			page.on('console', msg => console.log('[BROWSER]', msg.text()));
			page.on('pageerror', error => console.log('[BROWSER-ERROR]', error));

			// Navigation initiale
			console.log('[NAVIGATION] Accès au portail...');
			await page.goto(this.baseUrl, { waitUntil: 'networkidle', timeout: 60000 });
			await page.waitForTimeout(2000);

			// ÉTAPE 1: Upload XML
			await this.uploadXML(page, xmlFilePath, sessionToken);

			// ÉTAPE 2: Envoi avec gestion des erreurs
			await this.submitAndHandleErrors(page, sessionToken);

			// ÉTAPE 3: Récupération des PDF (AVEC LE TYPE)
			const result = await this.downloadPDFs(page, sessionToken, declarationType);

			await this.takeScreenshot(page, `success_${sessionToken}`);
			console.log('[FIN] ✓ Soumission terminée avec succès');

			return result;

		} catch (error) {
			console.error('[ERREUR FATALE]', error);

			if (page) {
				await this.takeScreenshot(page, `fatal_error_${sessionToken}`);
			}

			await this.updateStatus(
				sessionToken,
				'submission_error',
				`Échec: ${error.message}`,
				100
			);

			throw error;

		} finally {
			if (browser) {
				await browser.close();
			}
		}
	}
}

module.exports = new EdecSubmissionService();
