'use strict';

const { app, BrowserWindow, ipcMain, Menu, Tray, nativeImage, shell } = require('electron');
const path   = require('path');
const fs     = require('fs');
const os     = require('os');
const crypto = require('crypto');
const { spawn, execSync } = require('child_process');

// ── Single instance ───────────────────────────────────────────────────────────
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) { app.quit(); }

let isQuitting    = false;
let mainWindow    = null;
let tray          = null;
let workerProcess = null;

// ── Licencias ─────────────────────────────────────────────────────────────────
let activationWindow = null;
let licensePoller    = null;
let _cachedHwid      = null;

function getWorkerHwid() {
    if (_cachedHwid) return _cachedHwid;
    try {
        const { machineIdSync } = require('node-machine-id');
        _cachedHwid = machineIdSync();
        return _cachedHwid;
    } catch (_) {}
    try {
        const out = execSync(
            'REG QUERY "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid',
            { encoding: 'utf8', timeout: 5000, windowsHide: true }
        );
        const guid = out.split('REG_SZ')[1].replace(/\r+|\n+|\s+/gi, '').toLowerCase();
        _cachedHwid = crypto.createHash('sha256').update(guid).digest('hex');
        return _cachedHwid;
    } catch (_) {}
    _cachedHwid = crypto.createHash('sha256')
        .update(os.hostname() + (os.cpus()[0]?.model || '') + String(os.totalmem()))
        .digest('hex');
    return _cachedHwid;
}

async function checkLicenseWithServer(hwid, serverUrl) {
    try {
        const controller = new AbortController();
        const tid = setTimeout(() => controller.abort(), 8000);
        const res = await fetch(`${serverUrl}/api/license/status?hwid=${encodeURIComponent(hwid)}`, {
            signal: controller.signal,
        });
        clearTimeout(tid);
        if (!res.ok) return null;
        return await res.json();
    } catch (_) {
        return null;
    }
}

function transitionToMain() {
    if (licensePoller) { clearInterval(licensePoller); licensePoller = null; }
    if (activationWindow && !activationWindow.isDestroyed()) {
        activationWindow.close();
    }
    if (!mainWindow) createWindow();
}

function showActivationWindow(hwid) {
    activationWindow = new BrowserWindow({
        width: 580,
        height: 640,
        resizable: false,
        backgroundColor: '#0a0a0f',
        title: 'xponencIA — Activación',
        webPreferences: {
            preload: path.join(__dirname, 'preload-worker.js'),
            contextIsolation: true,
            nodeIntegration: false,
        },
        show: false,
    });

    activationWindow.loadFile(path.join(__dirname, 'activate-worker.html'));
    activationWindow.once('ready-to-show', () => activationWindow.show());
    activationWindow.on('closed', () => {
        activationWindow = null;
        // Si no hay ventana principal y no estamos en transición, salir
        if (!mainWindow && !isQuitting) {
            isQuitting = true;
            app.quit();
        }
    });
    Menu.setApplicationMenu(null);

    // Polling de aprobación cada 30 segundos
    licensePoller = setInterval(async () => {
        const env = readEnv();
        const serverUrl = env.TASK_SERVER_URL;
        if (!serverUrl) return;
        const status = await checkLicenseWithServer(hwid, serverUrl);
        if (status?.estado === 'aprobado') {
            transitionToMain();
        } else if (activationWindow && !activationWindow.isDestroyed()) {
            activationWindow.webContents.send('license-update', {
                estado: status?.estado || 'no_registrado',
            });
        }
    }, 30000);
}

// APP_DIR: directorio del código del app (resources/app/ en producción, raíz del proyecto en dev).
// Contiene archivos del bundle que NO son datos del usuario: version.json, .env.example, etc.
// Nunca se usa para guardar datos del usuario — solo para leer assets del bundle.
const APP_DIR = path.join(__dirname, '..', '..');

// SCRIPT_DIR: directorio de datos del usuario (sesiones, .env, schedule.json).
// Se elige según el modo de ejecución para separar código de datos:
//
//   ZIP portable  → carpeta del .exe  (PORTABLE_EXECUTABLE_DIR, junto a sessions/ y .env)
//   NSIS instalado → %AppData%\xponencIA Worker  (app.getPath('userData'))
//                    Separado de resources/app/ → las reinstalaciones no tocan los datos.
//   Dev (sin empaquetar) → raíz del proyecto (mismo comportamiento anterior)
const SCRIPT_DIR = process.env.PORTABLE_EXECUTABLE_DIR
    || (app.isPackaged ? app.getPath('userData') : APP_DIR);

// Asegurar que el directorio de datos existe (especialmente la primera vez en NSIS/portable).
fs.mkdirSync(SCRIPT_DIR, { recursive: true });

// Si no existe .env en SCRIPT_DIR, inicializarlo desde .env.example del bundle.
// Esto garantiza que el worker tenga TASK_SERVER_URL, BROWSER_ENGINE, etc. desde el
// primer arranque, sin que el usuario tenga que abrir Configuración → Guardar.
{
    const targetEnv = path.join(SCRIPT_DIR, '.env');
    if (!fs.existsSync(targetEnv)) {
        const candidates = [
            path.join(APP_DIR, '.env.example'),
            path.join(APP_DIR, '.env'),
        ];
        for (const src of candidates) {
            if (fs.existsSync(src)) { try { fs.copyFileSync(src, targetEnv); } catch (_) {} break; }
        }
    }
}

// Soporte para dev: WORKER_ENV_FILE puede ser path absoluto o relativo a SCRIPT_DIR
const ENV_PATH = (() => {
    const wef = process.env.WORKER_ENV_FILE;
    if (!wef) return path.join(SCRIPT_DIR, '.env');
    if (path.isAbsolute(wef)) return wef;           // ruta absoluta (dev-worker.js)
    return path.resolve(SCRIPT_DIR, wef);           // relativa al directorio de datos
})();
const SCHEDULE_PATH = path.join(SCRIPT_DIR, 'schedule.json');

// URL del servidor por defecto — igual al placeholder del input de configuración.
// Se escribe en .env al primer arranque si no está definida.
const DEFAULT_SERVER_URL = 'http://72.61.130.23:4010';

// ── Helpers ───────────────────────────────────────────────────────────────────

function readEnv() {
    const vars = {};
    if (!fs.existsSync(ENV_PATH)) return vars;
    fs.readFileSync(ENV_PATH, 'utf8').split('\n').forEach(line => {
        const m = line.match(/^\s*([^#\s][^=]*?)\s*=\s*(.*?)\s*$/);
        if (m) vars[m[1]] = m[2];
    });
    return vars;
}

function writeEnv(updates) {
    let content = fs.existsSync(ENV_PATH) ? fs.readFileSync(ENV_PATH, 'utf8') : '';
    for (const [k, v] of Object.entries(updates)) {
        const re = new RegExp(`^${k}=.*$`, 'm');
        if (re.test(content)) content = content.replace(re, `${k}=${v}`);
        else content += `\n${k}=${v}`;
    }
    fs.writeFileSync(ENV_PATH, content.trim() + '\n', 'utf8');
}

function getVersionJson() {
    // version.json es parte del bundle del app, no datos del usuario → APP_DIR, no SCRIPT_DIR.
    // Si estuviéramos en SCRIPT_DIR (AppData), quedaría desactualizado tras cada update.
    const p = path.join(APP_DIR, 'version.json');
    if (!fs.existsSync(p)) return { version: 'desconocida' };
    try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return { version: 'error' }; }
}

function resolveNodeBin() {
    const candidates = [
        'C:\\Program Files\\nodejs\\node.exe',
        'C:\\Program Files (x86)\\nodejs\\node.exe',
        path.join(SCRIPT_DIR, 'node.exe'),
    ];
    for (const p of candidates) if (fs.existsSync(p)) return p;
    try {
        const { execSync } = require('child_process');
        const found = execSync('where node', { encoding: 'utf8', stdio: ['ignore','pipe','ignore'] })
            .trim().split('\n')[0].trim();
        if (found && fs.existsSync(found)) return found;
    } catch (_) {}
    return process.execPath;
}

const NODE_BIN = resolveNodeBin();

// ── Worker process management ─────────────────────────────────────────────────

function sendToRenderer(channel, data) {
    if (mainWindow && !mainWindow.isDestroyed()) {
        mainWindow.webContents.send(channel, data);
    }
}

function startWorker() {
    if (workerProcess) return { ok: false, error: 'El worker ya está corriendo' };
    const scriptPath = path.join(SCRIPT_DIR, 'worker', 'startWorker.js');
    if (!fs.existsSync(scriptPath)) return { ok: false, error: 'startWorker.js no encontrado' };

    const env = { ...process.env, ...readEnv() };
    if (NODE_BIN === process.execPath) env.ELECTRON_RUN_AS_NODE = '1';
    env.WORKER_HWID = getWorkerHwid();
    // Asegurar que el worker use el mismo directorio de datos que la GUI (donde se guardan las sesiones).
    // Sin esto, workerConfig.js resuelve AppData (ruta de producción) en vez de SCRIPT_DIR,
    // y el worker no encuentra las sesiones guardadas por link-account.
    env.XPONENCIA_DATA_DIR = env.XPONENCIA_DATA_DIR || SCRIPT_DIR;

    workerProcess = spawn(NODE_BIN, [scriptPath], {
        cwd: SCRIPT_DIR,
        env,
        stdio: ['ignore', 'pipe', 'pipe'],
    });

    workerProcess.stdout.on('data', d => {
        for (const line of d.toString().split('\n').filter(l => l.trim()))
            sendToRenderer('worker-log', { level: 'info', msg: line });
    });
    workerProcess.stderr.on('data', d => {
        for (const line of d.toString().split('\n').filter(l => l.trim()))
            sendToRenderer('worker-log', { level: 'error', msg: line });
    });
    workerProcess.on('close', code => {
        workerProcess = null;
        sendToRenderer('worker-log', { level: 'system', msg: `Worker detenido (código: ${code ?? 'N/A'})` });
        if (!isQuitting) {
            sendToRenderer('worker-status', { running: false, reconnecting: true });
            sendToRenderer('worker-log', { level: 'system', msg: 'Reiniciando worker en 5 segundos...' });
            setTimeout(() => {
                if (!isQuitting && !workerProcess) {
                    sendToRenderer('worker-log', { level: 'system', msg: 'Reiniciando...' });
                    startWorker();
                }
            }, 5000);
        } else {
            sendToRenderer('worker-status', { running: false });
        }
    });
    workerProcess.on('error', err => {
        workerProcess = null;
        sendToRenderer('worker-status', { running: false });
        sendToRenderer('worker-log', { level: 'error', msg: `Error del worker: ${err.message}` });
    });

    sendToRenderer('worker-status', { running: true, pid: workerProcess.pid });
    return { ok: true, pid: workerProcess.pid };
}

function stopWorker() {
    if (!workerProcess) return { ok: false, error: 'No hay worker corriendo' };
    workerProcess.kill('SIGTERM');
    return { ok: true };
}

// ── Link account (Playwright login manual) ─────────────────────────────────────

let linkBrowser = null;   // referencia para cancelación

ipcMain.handle('link-account', async (_, { account_type = 'prospectora', expected_username = null } = {}) => {
    const linkEnv            = readEnv();
    const usePlainPlaywright = (linkEnv.BROWSER_ENGINE || process.env.BROWSER_ENGINE) === 'playwright';
    // INJECT_CHROME_COOKIES: por defecto true en Playwright (fingerprint coincide),
    // false en CloakBrowser (ig_did vinculado al Chrome real genera mismatch con el seed).
    const injectCookiesDefault = usePlainPlaywright ? 'true' : 'false';
    const injectCookies = (linkEnv.INJECT_CHROME_COOKIES ?? injectCookiesDefault) !== 'false';

    const sessionsDirName = account_type === 'seguidora' ? 'sessions_seguidoras' : 'sessions';
    const sessionsDir = path.join(SCRIPT_DIR, sessionsDirName);
    fs.mkdirSync(sessionsDir, { recursive: true });

    const tmpName = `.tmp_login_${Date.now()}`;
    const tmpDir  = path.join(sessionsDir, tmpName);
    fs.mkdirSync(tmpDir, { recursive: true });

    console.log(`\n[LINK] ── Vinculando cuenta (tipo: ${account_type}) ──────────────`);
    console.log(`[LINK] Motor: ${usePlainPlaywright ? 'Playwright estándar' : 'CloakBrowser (Real PC Mode)'}`);
    console.log(`[LINK] Cookies Chrome: ${injectCookies ? 'sí (INJECT_CHROME_COOKIES=true)' : 'no (INJECT_CHROME_COOKIES=false)'}`);
    sendToRenderer('link-account-progress', { status: 'opening', msg: 'Abriendo navegador...' });

    // ── a. Lanzar navegador ───────────────────────────────────────────────────
    let context;
    if (usePlainPlaywright) {
        // Patchright (→ fallback playwright) + stealth patch (BROWSER_ENGINE=playwright en .env)
        const { getChromium, applyPlaywrightStealth } = require('../realPcContext');
        const chromium = getChromium();
        const channel  = (linkEnv.BROWSER_CHANNEL || process.env.BROWSER_CHANNEL) || undefined;
        console.log(`[LINK] Lanzando Patchright + canal=${channel || 'chromium'} + stealth...`);
        context = await chromium.launchPersistentContext(tmpDir, {
            headless: false,
            channel,
            args: [
                '--no-sandbox',
                '--disable-blink-features=AutomationControlled',
            ],
            viewport: null,   // null = Chrome decide el viewport según la ventana real
            locale: 'es-AR',
            timezoneId: 'America/Argentina/Buenos_Aires',
        });
        await applyPlaywrightStealth(context);
    } else {
        // CloakBrowser — Real PC Mode (modo por defecto)
        let launchPersistentContext;
        try {
            const cb = await import('cloakbrowser');
            launchPersistentContext = cb.launchPersistentContext;
        } catch (_) {
            return { ok: false, error: 'CloakBrowser no disponible. Reinstalá la aplicación.' };
        }

        const { REAL_PC_LAUNCH_OPTIONS, getLaunchArgs, applyRealPcPatches } = require('../realPcContext');
        // Pantalla en CSS pixels (Electron siempre devuelve valores lógicos, dpi-agnostic)
        const { screen: electronScreen } = require('electron');
        const primaryDisplay = electronScreen.getPrimaryDisplay();
        const cssScreen = { width: primaryDisplay.size.width, height: primaryDisplay.size.height };
        console.log(`[LINK] Pantalla detectada: ${cssScreen.width}x${cssScreen.height} CSS pixels`);
        // GPU: generada por el seed para mantener consistencia con canvas/audio.

        context = await launchPersistentContext({
            userDataDir: tmpDir,
            headless: false,
            ...REAL_PC_LAUNCH_OPTIONS,
            args: getLaunchArgs(tmpDir, cssScreen),
        });
        await applyRealPcPatches(context);
    }

    try {
        linkBrowser = context;

        // ── b. Inyectar cookies de dispositivo de Chrome ─────────────────────
        // En modo Playwright: el fingerprint coincide con Chrome real → seguro inyectar.
        // En modo CloakBrowser: el ig_did está vinculado al fingerprint real → mismatch
        //   con el seed → puede causar captcha. Controlado por INJECT_CHROME_COOKIES en .env.
        sendToRenderer('link-account-progress', { status: 'opening', msg: 'Preparando navegador...' });
        if (injectCookies) {
            try {
                const { extractChromeDeviceCookies } = require('../chromeImporter');
                console.log('[LINK] Leyendo cookies de dispositivo desde Chrome...');
                const { device } = await extractChromeDeviceCookies();
                if (device.length > 0) {
                    await context.addCookies(device);
                    const names = device.map(c => c.name).join(', ');
                    console.log(`[LINK] ✓ Cookies de Chrome inyectadas: ${names}`);
                    sendToRenderer('link-account-progress', {
                        status: 'opening',
                        msg: `✓ Identidad de Chrome importada: ${names}`,
                    });
                } else {
                    console.log('[LINK] ⚠ Chrome no tiene cookies de Instagram — identidad nueva');
                    sendToRenderer('link-account-progress', {
                        status: 'opening',
                        msg: 'Chrome no tiene sesión previa de Instagram — se creará una identidad nueva',
                    });
                }
            } catch (importErr) {
                console.log(`[LINK] ⚠ Chrome no disponible: ${importErr.message}`);
                sendToRenderer('link-account-progress', {
                    status: 'opening',
                    msg: `Navegador listo (Chrome no disponible: ${importErr.message})`,
                });
            }
        } else {
            console.log('[LINK] Cookies de Chrome omitidas (INJECT_CHROME_COOKIES=false)');
            sendToRenderer('link-account-progress', { status: 'opening', msg: 'Navegador listo (identidad limpia)' });
        }

        const page = context.pages()[0] || await context.newPage();

        // ── c. Navegar a Instagram ────────────────────────────────────────────
        // El ig_did ya está inyectado → Instagram ve "dispositivo conocido".
        // Ir directo a instagram.com es más limpio que el rodeo por Google
        // (que causaba redirects google.com → google.com.ar → instagram.com).
        console.log('[LINK] Navegando a instagram.com...');
        await page.goto('https://www.instagram.com/', { waitUntil: 'domcontentloaded' });
        await page.waitForTimeout(800 + Math.floor(Math.random() * 600));

        sendToRenderer('link-account-progress', {
            status: 'waiting',
            msg: expected_username
                ? `Iniciá sesión con @${expected_username} en Instagram.\nSe verificará que el usuario coincida — no cierres esta ventana.`
                : 'Iniciá sesión en Instagram.\nEl usuario se detectará automáticamente — no cierres esta ventana.',
        });

        let detected = null;
        const TIMEOUT = 6 * 60 * 1000;
        const start   = Date.now();

        while (!detected && !page.isClosed() && Date.now() - start < TIMEOUT) {
            await new Promise(r => setTimeout(r, 2000));
            if (page.isClosed()) break;

            const hasHome = await page.$([
                'svg[aria-label*="Inicio"]',
                'svg[aria-label*="Home"]',
                'a[href*="/direct/inbox/"]',
            ].join(',')).catch(() => null);
            if (!hasHome) continue;

            // Método 1: link de perfil con avatar en el nav
            detected = await page.evaluate(() => {
                const links = Array.from(document.querySelectorAll('a[href]'));
                const SKIP = new Set(['/', '/reels/', '/explore/', '/direct/', '/direct/inbox/',
                                      '/stories/', '/tv/', '/accounts/', '#']);
                for (const a of links) {
                    const href = a.getAttribute('href');
                    if (!href || SKIP.has(href) || href.startsWith('/accounts/')) continue;
                    const m = href.match(/^\/([a-z0-9._]+)\/?$/i);
                    if (m && a.querySelector('img')) return m[1];
                }
                return null;
            }).catch(() => null);

            // Método 2: API interna de Instagram
            if (!detected) {
                detected = await page.evaluate(async () => {
                    try {
                        const r = await fetch('/api/v1/accounts/current_user/?edit=true', {
                            credentials: 'include',
                            headers: { 'x-ig-app-id': '936619743392459' },
                        });
                        if (!r.ok) return null;
                        const d = await r.json();
                        return d?.user?.username || null;
                    } catch { return null; }
                }).catch(() => null);
            }
        }

        await context.close().catch(() => {});
        linkBrowser = null;

        if (!detected) {
            fs.rmSync(tmpDir, { recursive: true, force: true });
            return { ok: false, error: 'No se detectó la sesión. Intentá de nuevo.' };
        }

        // Si se esperaba un username específico, verificar que coincida
        if (expected_username && detected.toLowerCase() !== expected_username.toLowerCase()) {
            fs.rmSync(tmpDir, { recursive: true, force: true });
            return { ok: false, error: `Cuenta incorrecta: iniciaste sesión con @${detected} pero se esperaba @${expected_username}.` };
        }

        // Mover sesión al directorio definitivo.
        // Si ya existe una sesión previa (posiblemente contaminada de un intento
        // anterior), la REEMPLAZAMOS con la nueva sesión limpia.
        // El usuario completó el login manualmente → quiere esta sesión, no la vieja.
        const finalDir = path.join(sessionsDir, detected);
        const hadOldSession = fs.existsSync(finalDir);
        if (hadOldSession) {
            fs.rmSync(finalDir, { recursive: true, force: true });
        }
        try {
            fs.renameSync(tmpDir, finalDir);
        } catch (_) {
            // renameSync puede fallar cross-device — fallback a copy+delete
            fs.mkdirSync(finalDir, { recursive: true });
            const cpAll = (src, dst) => {
                for (const e of fs.readdirSync(src, { withFileTypes: true })) {
                    const s = path.join(src, e.name), d = path.join(dst, e.name);
                    e.isDirectory() ? (fs.mkdirSync(d, { recursive: true }), cpAll(s, d)) : fs.copyFileSync(s, d);
                }
            };
            cpAll(tmpDir, finalDir);
            fs.rmSync(tmpDir, { recursive: true, force: true });
        }

        console.log(`[LINK] ✓ Sesión de @${detected} guardada en: ${finalDir}`);
        if (hadOldSession) console.log(`[LINK]   (sesión anterior eliminada y reemplazada)`);
        sendToRenderer('link-account-progress', {
            status: 'saving',
            msg: hadOldSession
                ? `Sesión anterior de @${detected} reemplazada por sesión limpia ✓`
                : `Guardando sesión de @${detected}...`,
        });

        if (expected_username) {
            // Cuenta ya aprobada en el servidor — solo actualizar worker_id sin pasar por el flujo de aprobación
            const env = readEnv();
            const serverUrl = env.TASK_SERVER_URL || 'http://72.61.130.23:4010';
            const workerId  = env.WORKER_ID || require('os').hostname();
            await fetch(`${serverUrl}/api/accounts`, {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify({ username: detected, worker_id: workerId, hwid: getWorkerHwid(), tipo_cuenta: account_type }),
            }).catch(() => {});  // ignorar errores de red, la sesión ya fue guardada
        } else {
            // Cuenta nueva — registrar y esperar aprobación del admin
            const env = readEnv();
            const serverUrl = env.TASK_SERVER_URL || 'http://72.61.130.23:4010';
            const workerId  = env.WORKER_ID || require('os').hostname();
            const res = await fetch(`${serverUrl}/api/accounts`, {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify({ username: detected, enabled: true, worker_id: workerId, hwid: getWorkerHwid(), tipo_cuenta: account_type }),
            });
            const data = await res.json().catch(() => ({}));
            if (!res.ok) {
                return { ok: false, error: `Sesión guardada pero error al registrar: ${data.error || res.status}` };
            }
        }

        return { ok: true, username: detected };

    } catch (err) {
        if (context) await context.close().catch(() => {});
        linkBrowser = null;
        try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (_) {}
        return { ok: false, error: err.message };
    }
});

// ── IPC handlers ──────────────────────────────────────────────────────────────

ipcMain.handle('get-hwid', () => ({ hwid: getWorkerHwid() }));

ipcMain.handle('check-license-status', async () => {
    const env = readEnv();
    const hwid = getWorkerHwid();
    const serverUrl = env.TASK_SERVER_URL;
    if (!serverUrl) return { ok: false, error: 'Sin URL de servidor configurada' };
    const status = await checkLicenseWithServer(hwid, serverUrl);
    if (!status) return { ok: false, error: 'No se pudo conectar al servidor' };
    // Si ya fue aprobado, transicionar de inmediato (sin esperar al poller)
    if (status.estado === 'aprobado') {
        setImmediate(() => transitionToMain());
    }
    return { ok: true, ...status };
});

ipcMain.handle('submit-license-request', async (_, { nombre, email, info_adicional } = {}) => {
    const env = readEnv();
    const hwid = getWorkerHwid();
    const serverUrl = env.TASK_SERVER_URL;
    if (!serverUrl) return { ok: false, error: 'Sin URL de servidor configurada' };
    try {
        const res = await fetch(`${serverUrl}/api/license/request`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ hwid, nombre, email, info_adicional }),
        });
        const data = await res.json();
        return { ok: res.ok, ...data };
    } catch (e) {
        return { ok: false, error: e.message };
    }
});

ipcMain.handle('get-env', () => readEnv());
ipcMain.handle('save-env', (_, updates) => { writeEnv(updates); return { ok: true }; });
ipcMain.handle('get-version', () => getVersionJson());
ipcMain.handle('get-is-dev', () => ({
    dev: !!(process.env.WORKER_ENV_FILE || process.env.NODE_ENV === 'development'),
}));

ipcMain.handle('get-schedule', () => {
    if (!fs.existsSync(SCHEDULE_PATH)) return null;
    try { return JSON.parse(fs.readFileSync(SCHEDULE_PATH, 'utf8')); } catch (_) { return null; }
});
ipcMain.handle('save-schedule', (_, data) => {
    fs.writeFileSync(SCHEDULE_PATH, JSON.stringify(data, null, 2), 'utf8');
    return { ok: true };
});
ipcMain.handle('worker-start', () => startWorker());
ipcMain.handle('worker-stop', () => stopWorker());
ipcMain.handle('worker-is-running', () => ({ running: !!workerProcess }));

// Lista los perfiles de Chrome instalados en la máquina (para importar sesión)
ipcMain.handle('list-chrome-profiles', () => {
    try {
        const { listChromeProfiles } = require('../chromeImporter');
        return { ok: true, profiles: listChromeProfiles() };
    } catch (e) {
        return { ok: false, error: e.message, profiles: [] };
    }
});

/**
 * Diagnostica la sesión de una cuenta: lee las cookies del perfil de CloakBrowser
 * guardado en sessions/<username>/ y muestra qué hay (ig_did, sessionid, etc.).
 * Usa el mismo lector SQLite del chromeImporter pero con la clave del perfil local.
 */
ipcMain.handle('diagnose-session', async (_, { username, account_type }) => {
    const dirName = account_type === 'seguidora' ? 'sessions_seguidoras' : 'sessions';
    const sessionDir = path.join(SCRIPT_DIR, dirName, username);

    if (!fs.existsSync(sessionDir)) {
        return { ok: false, error: 'No existe sesión local para esta cuenta' };
    }

    try {
        // CloakBrowser guarda las cookies en el mismo formato que Chrome:
        // Local State → clave AES encriptada con DPAPI del usuario Windows
        // Network/Cookies (o Cookies) → SQLite con cookies encriptadas
        const { dpApiDecryptFromLocalState, parseSQLiteCookiesWithKey } = require('../sessionDiag');
        const result = await dpApiDecryptFromLocalState(sessionDir);
        return { ok: true, ...result };
    } catch (e) {
        return { ok: false, error: e.message };
    }
});

// Proxy de llamadas a la API del servidor
ipcMain.handle('server-api', async (_, { method, path: apiPath, body }) => {
    const env = readEnv();
    const serverUrl = env.TASK_SERVER_URL || 'http://72.61.130.23:4010';
    try {
        const opts = {
            method: method || 'GET',
            headers: { 'Content-Type': 'application/json' },
        };
        if (body) opts.body = JSON.stringify(body);
        const res  = await fetch(`${serverUrl}${apiPath}`, opts);
        const text = await res.text();
        let data;
        try { data = JSON.parse(text); } catch (_) { data = { _raw: text.slice(0, 200) }; }
        return { ok: res.ok, status: res.status, data };
    } catch (err) {
        return { ok: false, error: err.message };
    }
});

ipcMain.handle('open-env-file', () => {
    shell.openPath(ENV_PATH);
});

ipcMain.handle('open-external', (_, url) => {
    shell.openExternal(url);
});

// Verifica si existe sesión local para un conjunto de usernames
ipcMain.handle('check-local-sessions', (_, usernames) => {
    const result = {};
    for (const username of usernames) {
        const dirs = [
            path.join(SCRIPT_DIR, 'sessions', username),
            path.join(SCRIPT_DIR, 'sessions_seguidoras', username),
        ];
        const found = dirs.find(d => {
            try { return fs.existsSync(d) && fs.readdirSync(d).length > 0; } catch (_) { return false; }
        });
        result[username] = found ? 'ok' : 'missing';
    }
    return result;
});

// Resetea la sesión local de un username (borra carpeta) — útil para re-login
ipcMain.handle('reset-local-session', (_, { username, account_type }) => {
    const dirName = account_type === 'seguidora' ? 'sessions_seguidoras' : 'sessions';
    const sessionDir = path.join(SCRIPT_DIR, dirName, username);
    try {
        if (fs.existsSync(sessionDir)) {
            fs.rmSync(sessionDir, { recursive: true, force: true });
        }
        return { ok: true };
    } catch (err) {
        return { ok: false, error: err.message };
    }
});

// ── Auto-updater ──────────────────────────────────────────────────────────────
// Descarga la actualización via Node.js (no Chrome) → sin bloqueos de AV por download de browser.
// Extrae con tar.exe (nativo en Windows 10+). Aplica al reiniciar.
// Solo funciona para WORKER_VARIANT=zip (portable ZIP).

const https   = require('https');
const http    = require('http');
const zlib    = require('zlib');

let _pendingUpdatePath    = null;   // ruta del ZIP descargado listo para aplicar
let _pendingUpdateIsPatch = false;  // true = patch ZIP (sin node_modules), false = ZIP completo

ipcMain.handle('auto-update-check', async () => {
    try {
        const env = readEnv();
        const serverUrl = (env.TASK_SERVER_URL || '').replace(/\/$/, '');
        if (!serverUrl) return { available: false, error: 'No hay servidor configurado' };

        const localVer = (() => {
            try { return JSON.parse(fs.readFileSync(path.join(APP_DIR, 'version.json'), 'utf8')).version || '0.0.0'; }
            catch (_) { return '0.0.0'; }
        })();

        const remote = await new Promise((resolve, reject) => {
            const url = serverUrl + '/api/version';
            const mod = url.startsWith('https') ? https : http;
            const req = mod.get(url, { timeout: 8000 }, (res) => {
                let data = '';
                res.on('data', d => data += d);
                res.on('end', () => {
                    try { resolve(JSON.parse(data)); } catch (_) { resolve({}); }
                });
            });
            req.on('error', reject);
            req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
        });

        const remoteVer = remote.version || '0.0.0';
        return { available: remoteVer !== localVer && remoteVer !== 'unknown', localVer, remoteVer, serverUrl };
    } catch (e) {
        return { available: false, error: e.message };
    }
});

ipcMain.handle('auto-update-download', async (event, { serverUrl, usePatch = false }) => {
    const env = readEnv();
    const variant = (env.WORKER_VARIANT || 'zip').trim();

    // Elegir qué descargar:
    //   - patch ZIP (solo código .js, sin node_modules) cuando usePatch=true y variante zip
    //   - ZIP completo cuando usePatch=false, o cuando la variante es setup/exe
    //   - instalador .exe cuando variant=setup
    let releaseFile;
    if (variant !== 'zip') {
        releaseFile = 'latest-setup.exe';
    } else if (usePatch) {
        releaseFile = 'latest-win-patch.zip';
    } else {
        releaseFile = 'latest-win.zip';
    }
    const downloadUrl = serverUrl + '/releases/' + releaseFile;

    const tmpDir = path.join(os.tmpdir(), 'xponencia-update');
    fs.mkdirSync(tmpDir, { recursive: true });
    const tmpFile = path.join(tmpDir, releaseFile);

    return new Promise((resolve) => {
        const mod = downloadUrl.startsWith('https') ? https : http;
        const req = mod.get(downloadUrl, { timeout: 120000 }, (res) => {
            if (res.statusCode !== 200) {
                return resolve({ ok: false, error: `HTTP ${res.statusCode}`, releaseFile });
            }
            const total = parseInt(res.headers['content-length'] || '0', 10);
            let received = 0;
            const out = fs.createWriteStream(tmpFile);
            res.on('data', chunk => {
                received += chunk.length;
                if (total > 0) {
                    const pct = Math.round(received / total * 100);
                    if (mainWindow) mainWindow.webContents.send('update-progress', { pct, received, total });
                }
            });
            res.pipe(out);
            out.on('finish', () => {
                _pendingUpdatePath = tmpFile;
                _pendingUpdateIsPatch = usePatch && variant === 'zip';
                resolve({ ok: true, path: tmpFile, variant, isPatch: _pendingUpdateIsPatch, releaseFile });
            });
            out.on('error', (e) => resolve({ ok: false, error: e.message }));
        });
        req.on('error', (e) => resolve({ ok: false, error: e.message }));
        req.on('timeout', () => { req.destroy(); resolve({ ok: false, error: 'timeout' }); });
    });
});

ipcMain.handle('auto-update-apply', async () => {
    if (!_pendingUpdatePath || !fs.existsSync(_pendingUpdatePath)) {
        return { ok: false, error: 'No hay actualización descargada' };
    }

    const zipPath = _pendingUpdatePath;
    const env = readEnv();
    const variant = (env.WORKER_VARIANT || 'zip').trim();

    if (variant === 'zip') {
        // Para ZIP portable: extraer con tar.exe (Windows 10+) y reiniciar.
        // PATCH vs FULL:
        //   - FULL:  extraer en APP_DIR con --strip-components=1 (estructura full ZIP: topdir/resources/app/)
        //   - PATCH: extraer en APP_DIR con --strip-components=1 (estructura patch ZIP: app/worker/ etc.)
        //            Esto sobreescribe solo los archivos de código. node_modules/ queda intacto.
        const scriptPath = path.join(os.tmpdir(), 'xponencia-apply-update.bat');
        const appDir    = APP_DIR.replace(/\//g, '\\');
        // Para el patch, extrae en APP_DIR directamente (estructura: app/ → se strip a /)
        // Para el full, extrae en resources/../ (estructura: appname/resources/app/ → 2 strips + nombre)
        // En ambos casos usamos --strip-components=1 y destino apropiado.
        const zipPathW  = zipPath.replace(/\//g, '\\');
        const exePath   = app.getPath('exe').replace(/\//g, '\\');
        const isPatch   = _pendingUpdateIsPatch;

        // El patch ZIP tiene estructura: app/worker/, app/src/, etc.
        // Con --strip-components=1 y destino=APP_DIR → extrae worker/, src/, etc. directamente en APP_DIR
        // El full ZIP tiene estructura: appname/resources/app/ → necesitamos 3 componentes strip
        // y el destino debe ser APP_DIR/../../../ para que resources/app/ quede en el lugar correcto.
        // Simplificamos: siempre extraer sobre APP_DIR, el patch ya está listo, el full también.
        const extractDest = appDir;
        const stripCount  = 1; // ambos ZIPs tienen 1 nivel de directorio raíz

        const bat = `@echo off
timeout /t 3 /nobreak > nul
echo Aplicando actualizacion xponencIA (${isPatch ? 'patch' : 'completa'})...
cd /d "${extractDest}"
tar -xf "${zipPathW}" --strip-components=${stripCount} --overwrite 2>nul
if errorlevel 1 (
    echo tar fallo, intentando PowerShell...
    powershell -Command "Expand-Archive -Path '${zipPathW}' -DestinationPath '${extractDest}' -Force"
)
del "${zipPathW}" 2>nul
echo Actualizacion aplicada. Reiniciando...
start "" "${exePath}"
del "%~f0"
`;
        fs.writeFileSync(scriptPath, bat, 'utf8');
        app.relaunch({ execPath: 'cmd.exe', args: ['/c', scriptPath] });
        app.exit(0);
        return { ok: true, isPatch };
    } else {
        // Para instalador .exe: abrir el instalador y salir
        shell.openPath(zipPath);
        setTimeout(() => app.quit(), 1500);
        return { ok: true };
    }
});

// ── Window ────────────────────────────────────────────────────────────────────

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 960,
        height: 720,
        minWidth: 800,
        minHeight: 560,
        backgroundColor: '#0a0a0f',
        title: 'xponencIA Worker',
        webPreferences: {
            preload: path.join(__dirname, 'preload-worker.js'),
            contextIsolation: true,
            nodeIntegration: false,
        },
        show: false,
    });

    mainWindow.loadFile(path.join(__dirname, 'index.html'));
    mainWindow.once('ready-to-show', () => {
        mainWindow.show();
        // Auto-iniciar el worker al abrir la GUI
        startWorker();
    });

    mainWindow.on('close', (e) => {
        if (!isQuitting) {
            e.preventDefault();
            mainWindow.hide();
        }
    });
    mainWindow.on('closed', () => { mainWindow = null; });

    Menu.setApplicationMenu(null);
}

function createTray() {
    const iconPath = path.join(__dirname, 'icon.png');
    const icon = fs.existsSync(iconPath)
        ? nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 })
        : nativeImage.createEmpty();

    tray = new Tray(icon);
    tray.setToolTip('xponencIA Worker');
    tray.setContextMenu(Menu.buildFromTemplate([
        { label: 'Abrir panel', click: () => { if (mainWindow) mainWindow.show(); else createWindow(); } },
        { type: 'separator' },
        { label: 'Salir', click: () => { isQuitting = true; app.quit(); } },
    ]));
    tray.on('double-click', () => { if (mainWindow) mainWindow.show(); else createWindow(); });
    tray.setToolTip('xponencIA Worker — corriendo en segundo plano');
}

// ── App lifecycle ─────────────────────────────────────────────────────────────

app.whenReady().then(async () => {
    // ── Garantizar valores clave en .env (por si .env.example no los tenía todos) ──
    // El bloque de SCRIPT_DIR (más arriba) ya copió .env.example si no existía .env.
    // Aquí solo completamos claves que el .env.example podría no tener.
    {
        const existing = readEnv();
        const defaults  = {};
        if (!existing.TASK_SERVER_URL) defaults.TASK_SERVER_URL = DEFAULT_SERVER_URL;
        if (!existing.BROWSER_ENGINE)  defaults.BROWSER_ENGINE  = 'playwright';
        if (!existing.BROWSER_CHANNEL) defaults.BROWSER_CHANNEL = 'chrome';
        if (Object.keys(defaults).length) {
            writeEnv(defaults);
            console.log('[WORKER-GUI] .env completado con defaults:', Object.keys(defaults).join(', '));
        }
    }

    // ── Verificar estado de Chrome al inicio ──────────────────────────────────
    try {
        const { extractChromeDeviceCookies } = require('../chromeImporter');
        const { device } = await extractChromeDeviceCookies();
        if (device.length > 0) {
            const names = device.map(c => c.name).join(', ');
            console.log(`[CHROME] ✓ Cookies de dispositivo disponibles: ${names}`);
            const igDid = device.find(c => c.name === 'ig_did');
            if (igDid) console.log(`[CHROME]   ig_did: ${igDid.value.slice(0, 12)}…`);
        } else {
            console.log('[CHROME] ⚠ Chrome no tiene cookies de Instagram. Abrí instagram.com en Chrome para generar el ig_did.');
        }
    } catch (e) {
        console.log(`[CHROME] ⚠ No se pudo leer Chrome: ${e.message}`);
    }

    // Registrar inicio automático con el sistema (solo en producción)
    if (!process.env.ELECTRON_DEV) {
        app.setLoginItemSettings({ openAtLogin: true, name: 'xponencIA Worker' });
    }

    createTray();

    const env = readEnv();
    // En modo DEBUG se omite la validación de licencia
    if (env.DEBUG_MODE === 'true') {
        createWindow();
        return;
    }

    const hwid = getWorkerHwid();
    const serverUrl = env.TASK_SERVER_URL;
    let approved = false;

    if (serverUrl) {
        const status = await checkLicenseWithServer(hwid, serverUrl);
        approved = (status?.estado === 'aprobado');
    }

    if (approved) {
        createWindow();
    } else {
        showActivationWindow(hwid);
    }
});

app.on('second-instance', () => {
    if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.show(); mainWindow.focus(); }
});

app.on('window-all-closed', () => { /* vive en la bandeja */ });

app.on('before-quit', () => {
    isQuitting = true;
    if (licensePoller) { clearInterval(licensePoller); licensePoller = null; }
    if (linkBrowser) { try { linkBrowser.close(); } catch (_) {} }
    if (workerProcess) { try { workerProcess.kill('SIGKILL'); } catch (_) {} }
});
