'use strict';
/**
 * realPcContext.js — Configuración y patches para "Real PC Mode".
 *
 * ── Diseño ─────────────────────────────────────────────────────────────────
 *
 * CloakBrowser v0.3.30 con --fingerprint=SEED:
 *   - GPU: auto-generada del seed → FAKE (ej. RTX 4060, RTX 4070 aleatorio)
 *   - Pantalla: auto-generada del seed → puede ser 2400x1350 (bug conocido)
 *   - Canvas/audio/fonts: determinísticos del seed → CONSISTENTES ✓
 *
 * CloakBrowser v0.3.30 sin --fingerprint (stealthArgs:false):
 *   - GPU: passthrough nativo → GPU REAL ✓
 *   - Pantalla: hardware real (con posible bug de DPI en Windows)
 *   - Canvas/audio/fonts: seed random en cada startup → INCONSISTENTES ✗
 *
 * Solución: --fingerprint=SEED + flags explícitos para GPU y pantalla.
 *   - GPU real: detectada una vez (sin seed) y cacheada en .cb_gpu
 *   - Pantalla CSS: detectada del sistema y cacheada en .cb_screen
 *   - Canvas/audio/fonts: consistentes por seed fijo por cuenta
 *
 * ── Archivos en cada sessionDir ─────────────────────────────────────────
 *   .cb_seed    → número 10000-99999, generado random, fijo de por vida
 *   .cb_screen  → "WIDTHxHEIGHT" en CSS pixels (ej. "1920x1080")
 *   .cb_gpu     → línea con vendor|renderer de la GPU real del sistema
 */

const path      = require('path');
const fs        = require('fs');
const os        = require('os');
const { spawnSync } = require('child_process');

const REAL_CPU_COUNT = os.cpus().length;

// ── Persistencia de valores por sesión ──────────────────────────────────────

function readFile(p) {
    try { return fs.readFileSync(p, 'utf8').trim(); } catch (_) { return null; }
}
function writeFile(p, content) {
    try { fs.writeFileSync(p, content, 'utf8'); } catch (_) {}
}

// ── Seed ────────────────────────────────────────────────────────────────────

function loadOrCreateSeed(sessionDir) {
    const p = path.join(sessionDir, '.cb_seed');
    const stored = parseInt(readFile(p) || '', 10);
    if (stored >= 10000 && stored <= 99999) return stored;
    const seed = Math.floor(Math.random() * 90000) + 10000;
    try { fs.mkdirSync(sessionDir, { recursive: true }); } catch (_) {}
    writeFile(p, String(seed));
    return seed;
}

// ── Pantalla (CSS pixels) ───────────────────────────────────────────────────

let _screenCache = null;

/**
 * Detecta el tamaño de pantalla en CSS pixels.
 * Orden de prioridad:
 *  1. Electron screen API (main process)
 *  2. Archivo .cb_screen en el sessionDir
 *  3. PowerShell GetSystemMetrics (corregido por DPI)
 *  4. Default 1920x1080
 */
function detectCssScreenSize(sessionDir) {
    if (_screenCache) return _screenCache;

    // 1. Electron (main-worker.js context)
    try {
        const { screen: es } = require('electron');
        const d = es.getPrimaryDisplay();
        _screenCache = { width: d.size.width, height: d.size.height };
        return _screenCache;
    } catch (_) {}

    // 2. Archivo guardado en sesión (scriptTaskHandler context — fue guardado por link-account)
    if (sessionDir) {
        const stored = readFile(path.join(sessionDir, '.cb_screen'));
        if (stored) {
            const m = stored.match(/^(\d+)x(\d+)$/);
            if (m) {
                _screenCache = { width: +m[1], height: +m[2] };
                return _screenCache;
            }
        }
    }

    // 3. PowerShell: System.Windows.Forms.Screen → siempre CSS pixels (lógicos)
    // No usar GetSystemMetrics directamente: PowerShell es system-DPI-aware
    // y devuelve pixels físicos (2400 en vez de 1920 a 125% DPI).
    // Windows.Forms.Screen.Bounds abstrae el DPI y devuelve unidades lógicas.
    try {
        const ps = `
$ErrorActionPreference='SilentlyContinue'
Add-Type -AssemblyName System.Windows.Forms
$s=[System.Windows.Forms.Screen]::PrimaryScreen.Bounds
Write-Output "$($s.Width)x$($s.Height)"`.trim();
        const r = spawnSync('powershell',
            ['-NoProfile', '-NonInteractive', '-WindowStyle', 'Hidden', '-Command', ps],
            { timeout: 8000, encoding: 'utf8', windowsHide: true }
        );
        if (r.status === 0 && r.stdout) {
            const m = r.stdout.trim().match(/^(\d+)x(\d+)$/);
            if (m && +m[1] >= 800 && +m[1] <= 7680) {
                _screenCache = { width: +m[1], height: +m[2] };
                return _screenCache;
            }
        }
    } catch (_) {}

    // 4. Fallback universal
    _screenCache = { width: 1920, height: 1080 };
    return _screenCache;
}

function saveScreenToSession(sessionDir, width, height) {
    try { fs.mkdirSync(sessionDir, { recursive: true }); } catch (_) {}
    writeFile(path.join(sessionDir, '.cb_screen'), `${width}x${height}`);
}

// ── GPU real (passthrough detection) ──────────────────────────────────────

/**
 * Detecta la GPU real del sistema y la cachea en <sessionDir>/.cb_gpu.
 *
 * IMPORTANTE: NO usar CloakBrowser para la detección — su binario spoofa la GPU
 * incluso sin --fingerprint (seed random auto-generado). Usar:
 *   1. Electron BrowserWindow oculto (si estamos en contexto Electron)
 *   2. Plain playwright-core Chromium (no tiene spoofing de GPU)
 *
 * @param {string} sessionDir
 * @returns {Promise<{vendor: string, renderer: string}|null>}
 */
async function detectRealGpu(sessionDir) {
    // ── Leer caché ───────────────────────────────────────────────────────────
    const gpuFile = path.join(sessionDir, '.cb_gpu');
    const stored = readFile(gpuFile);
    if (stored && stored.includes('|')) {
        const [vendor, ...rest] = stored.split('|');
        return { vendor: vendor.trim(), renderer: rest.join('|').trim() };
    }

    function cacheGpu(gpu) {
        if (!gpu || !gpu.vendor || !gpu.renderer) return null;
        try { fs.mkdirSync(sessionDir, { recursive: true }); } catch (_) {}
        writeFile(gpuFile, `${gpu.vendor}|${gpu.renderer}`);
        return gpu;
    }

    const GL_SCRIPT = `(function(){
        try {
            var c=document.createElement('canvas');
            var g=c.getContext('webgl')||c.getContext('experimental-webgl');
            if(!g)return null;
            var e=g.getExtension('WEBGL_debug_renderer_info');
            if(!e)return null;
            return{vendor:g.getParameter(e.UNMASKED_VENDOR_WEBGL),renderer:g.getParameter(e.UNMASKED_RENDERER_WEBGL)};
        }catch(_){return null;}
    })()`;

    // ── Estrategia 1: Electron hidden BrowserWindow (más rápido, mismo proceso) ──
    try {
        const { BrowserWindow } = require('electron');
        const gpu = await new Promise((resolve, reject) => {
            const win = new BrowserWindow({
                show: false, width: 16, height: 16,
                webPreferences: { contextIsolation: false, nodeIntegration: false },
            });
            const tid = setTimeout(() => { try { win.destroy(); } catch (_) {} reject(new Error('timeout')); }, 6000);
            win.loadURL('about:blank');
            win.webContents.once('did-finish-load', async () => {
                clearTimeout(tid);
                try {
                    const result = await win.webContents.executeJavaScript(GL_SCRIPT);
                    try { win.destroy(); } catch (_) {}
                    resolve(result);
                } catch (e) { try { win.destroy(); } catch (_) {} reject(e); }
            });
        });
        if (gpu && gpu.vendor && gpu.renderer) return cacheGpu(gpu);
    } catch (_) {}

    // ── Estrategia 2: Plain playwright-core Chromium (sin spoofing de ningún tipo) ──
    // NO usar CloakBrowser — su binario spoofa GPU incluso sin --fingerprint.
    const detectDir = path.join(os.tmpdir(), `_gpu_${Date.now()}`);
    try {
        fs.mkdirSync(detectDir, { recursive: true });
        let chromium;
        try { ({ chromium } = require('playwright-core')); }
        catch (_) { ({ chromium } = require('playwright')); }

        let ctx;
        try {
            ctx = await chromium.launchPersistentContext(detectDir, {
                headless: false,    // headless puede usar SwiftShader; ventana real = GPU real
                args: ['--no-sandbox', '--ignore-gpu-blocklist'],
                viewport: { width: 16, height: 16 },
            });
            const page = ctx.pages()[0] || await ctx.newPage();
            const gpu = await page.evaluate(() => {
                try {
                    const c = document.createElement('canvas');
                    const g = c.getContext('webgl') || c.getContext('experimental-webgl');
                    if (!g) return null;
                    const e = g.getExtension('WEBGL_debug_renderer_info');
                    if (!e) return null;
                    return { vendor: g.getParameter(e.UNMASKED_VENDOR_WEBGL), renderer: g.getParameter(e.UNMASKED_RENDERER_WEBGL) };
                } catch (_) { return null; }
            });
            if (gpu && gpu.vendor && gpu.renderer) return cacheGpu(gpu);
        } finally {
            try { await ctx?.close(); } catch (_) {}
        }
    } catch (_) {
        // GPU detection no disponible — se usará GPU del seed (fake pero consistente)
    } finally {
        try { fs.rmSync(detectDir, { recursive: true, force: true }); } catch (_) {}
    }
    return null;
}

// ── Options base ─────────────────────────────────────────────────────────────

const REAL_PC_LAUNCH_OPTIONS = {
    // stealthArgs: default (añade --fingerprint-platform=windows automáticamente)
    humanize:    true,
    humanPreset: 'careful',
    viewport:    null,
    timezoneId:  'America/Argentina/Buenos_Aires',
    // locale: NO — usar el del sistema (Chrome real tiene múltiples idiomas)
};

// ── getLaunchArgs ────────────────────────────────────────────────────────────

/**
 * Devuelve los args de CloakBrowser para el session dir dado.
 *
 * Estrategia de consistencia:
 *   - --fingerprint=SEED genera GPU, canvas y audio como un bloque determinístico.
 *     Overridear solo la GPU (sin tocar canvas/audio) rompe la consistencia y
 *     PixelScan lo detecta. Por eso NO se inyecta GPU real aquí.
 *   - --fingerprint-screen-width/height sí se puede overridear sin afectar canvas.
 *   - --fingerprint-hardware-concurrency ídem.
 *   - --fingerprint-noise=false → canvas/audio siempre producen el mismo hash
 *     en cada lectura (sin ruido por render), lo que pasa la consistencia de PixelScan.
 *
 * @param {string} sessionDir — mismo valor que userDataDir
 * @param {{width:number,height:number}=} screenSizeOverride — CSS pixels desde Electron
 * @returns {string[]}
 */
function getLaunchArgs(sessionDir, screenSizeOverride) {
    const seed = loadOrCreateSeed(sessionDir);

    // Pantalla en CSS pixels
    let screen;
    if (screenSizeOverride) {
        screen = screenSizeOverride;
        saveScreenToSession(sessionDir, screen.width, screen.height);
    } else {
        screen = detectCssScreenSize(sessionDir);
    }

    return [
        '--no-sandbox',
        `--fingerprint=${seed}`,
        '--fingerprint-noise=false',
        `--fingerprint-hardware-concurrency=${REAL_CPU_COUNT}`,
        `--fingerprint-screen-width=${screen.width}`,
        `--fingerprint-screen-height=${screen.height}`,
    ];
}

// ── addInitScript (fallback para lo que los args no cubren) ──────────────────

function buildInitScript(realCpuCount) {
    return `(function() {
        // Fallback por si el binario ignora --fingerprint-screen-width/height
        // (en teoría no debería pasar, pero por seguridad)
        try {
            var dpr = window.devicePixelRatio || 1;
            if (screen.width > 1920) {
                var scaleFactor = null;
                if (dpr > 1.01) { scaleFactor = dpr; }
                if (!scaleFactor && screen.availWidth > 0 && screen.availWidth < screen.width) {
                    var r2 = screen.width / screen.availWidth;
                    var sc = [1.1, 1.25, 1.5, 1.75, 2.0];
                    for (var i = 0; i < sc.length; i++) {
                        if (Math.abs(r2 - sc[i]) < 0.06) { scaleFactor = sc[i]; break; }
                    }
                }
                if (!scaleFactor) {
                    var m = { '2400x1350':1.25, '2880x1620':1.5, '3840x2160':2.0 };
                    if (m[screen.width+'x'+screen.height]) scaleFactor = m[screen.width+'x'+screen.height];
                }
                if (scaleFactor && scaleFactor > 1.01) {
                    var lw=Math.round(screen.width/scaleFactor), lh=Math.round(screen.height/scaleFactor);
                    var law=Math.round(screen.availWidth/scaleFactor), lah=Math.round(screen.availHeight/scaleFactor);
                    Object.defineProperty(screen,'width',      {get:function(){return lw;}, configurable:true});
                    Object.defineProperty(screen,'height',     {get:function(){return lh;}, configurable:true});
                    Object.defineProperty(screen,'availWidth', {get:function(){return law;},configurable:true});
                    Object.defineProperty(screen,'availHeight',{get:function(){return lah;},configurable:true});
                }
            }
        } catch(e) {}

        // Fallback hardwareConcurrency (ya debería venir correcto por --fingerprint-hardware-concurrency)
        try {
            var realCores = ${realCpuCount};
            if (navigator.hardwareConcurrency !== realCores) {
                Object.defineProperty(navigator, 'hardwareConcurrency', {
                    get: function(){ return realCores; }, configurable: true
                });
            }
        } catch(e) {}
    })();`;
}

async function applyRealPcPatches(context) {
    await context.addInitScript(buildInitScript(REAL_CPU_COUNT));
}

// ── Chromium loader (Patchright → Playwright fallback) ──────────────────────

/**
 * Devuelve el objeto `chromium` de Patchright si está disponible,
 * con fallback a playwright y playwright-core.
 *
 * Patchright es un drop-in replacement de Playwright que:
 *  - Elimina el leak de Runtime.enable (CDP)
 *  - Elimina el leak de Console.enable (CDP)
 *  - Quita --enable-automation y agrega --disable-blink-features=AutomationControlled
 *  - Pasa PixelScan, CreepJS, Cloudflare, Datadome, Fingerprint.com, etc.
 *
 * @returns {import('playwright').BrowserType}
 */
function getChromium() {
    try { return require('patchright').chromium; } catch (_) {}
    try { return require('playwright').chromium; } catch (_) {}
    try { return require('playwright-core').chromium; } catch (_) {}
    throw new Error('[xponencIA] No se encontró ningún driver de browser. Asegurate de que patchright esté instalado.');
}

// ── Script complementario para launchers Patchright/Playwright ───────────────
// Nota: Patchright parchea navigator.webdriver a nivel de flags de Chrome
// (--disable-blink-features=AutomationControlled), por lo que ese patch es
// redundante aquí. Lo dejamos como safety net y para cuando se use playwright puro.

const PLAYWRIGHT_STEALTH_SCRIPT = `(function() {
    // ── Regla principal: NO modificar propiedades nativas si ya tienen el valor correcto.
    // Cada Object.defineProperty crea un descriptor custom en el objeto (o su prototipo)
    // que PixelScan detecta vía Object.getOwnPropertyDescriptor → "spoofing detected".
    // Solo parchear lo que realmente está roto.

    // ── navigator.webdriver ────────────────────────────────────────────────────
    // Con --disable-blink-features=AutomationControlled Chrome lo pone en false.
    // Con Patchright bundled Chromium ya es undefined por el patch binario.
    // Solo crear el getter custom si el valor es explícitamente true (modo automation
    // sin flags de mitigación), de lo contrario los getters nativos son indetectables.
    try {
        if (navigator.webdriver === true) {
            Object.defineProperty(navigator, 'webdriver', {
                get: function() { return undefined; },
                configurable: true,
            });
        }
    } catch(e) {}

    // ── window.chrome ──────────────────────────────────────────────────────────
    // Chrome (tanto bundled como sistema) lo expone nativamente.
    // Solo parchear como fallback si por alguna razón está ausente.
    try {
        if (!window.chrome) { window.chrome = { runtime: {} }; }
    } catch(e) {}

    // ── Permissions API ────────────────────────────────────────────────────────
    // NO parchear: reemplazar navigator.permissions.query con una función custom
    // hace que .toString() deje de decir "[native code]" → detectable por PixelScan.
    // El estado real de los permisos es lo más creíble; Instagram no necesita
    // que notifications esté en "granted".

    // ── screen.width/height ────────────────────────────────────────────────────
    // Google Chrome con channel:'chrome' + viewport:null ya reporta en CSS pixels
    // (screen.width=1536, availWidth=1536) → ratio ≈ 1 → NO tocar.
    //
    // Patchright + Chromium bundled evita Emulation.setDeviceMetricsOverride → Chrome
    // reporta screen.width en píxeles físicos (1920) mientras availWidth está en CSS
    // (1536) → ratio = 1.25 > 1.05 → parchear. En ese caso el getter custom es
    // necesario (mal menor), pero solo se activa cuando hay un mismatch real.
    try {
        var _rawW = screen.width;
        var _aw   = Math.round(screen.availWidth);
        if (_aw > 0 && (_rawW / _aw) > 1.05) {
            var _rawH  = screen.height;
            var _ah    = Math.round(screen.availHeight);
            var _ratio = _rawW / _aw;
            var _sw = _aw;
            var _sh = Math.round(_rawH / _ratio);
            try {
                Object.defineProperty(screen, 'width',       { get: function(){ return _sw; }, configurable: true });
                Object.defineProperty(screen, 'height',      { get: function(){ return _sh; }, configurable: true });
                Object.defineProperty(screen, 'availWidth',  { get: function(){ return _aw; }, configurable: true });
                Object.defineProperty(screen, 'availHeight', { get: function(){ return _ah; }, configurable: true });
            } catch(_e1) {
                try {
                    Object.defineProperty(Screen.prototype, 'width',       { get: function(){ return _sw; }, configurable: true });
                    Object.defineProperty(Screen.prototype, 'height',      { get: function(){ return _sh; }, configurable: true });
                    Object.defineProperty(Screen.prototype, 'availWidth',  { get: function(){ return _aw; }, configurable: true });
                    Object.defineProperty(Screen.prototype, 'availHeight', { get: function(){ return _ah; }, configurable: true });
                } catch(_e2) {}
            }
        }
        // ratio ≈ 1.0 → ya en CSS pixels, getters nativos intactos.
    } catch(e) {}
})();`;

// ── Patches adicionales para modo headless ───────────────────────────────────
// Solo se inyectan cuando headless=true. En headful estas propiedades ya tienen
// los valores correctos (getters nativos intactos → PixelScan safe).
//
// Señales que headless expone y headful no:
//   document.hasFocus()       → siempre false  (no hay UI thread con foco)
//   document.visibilityState  → 'hidden'        (no hay ventana visible)
//   document.hidden           → true
//   window.outerWidth/Height  → 0               (no hay chrome del browser)
const HEADLESS_PATCH_SCRIPT = `(function() {
    // hasFocus: en headless siempre false; parcheamos a true para simular tab activa
    try {
        document.hasFocus = function() { return true; };
    } catch(e) {}

    // visibilityState / hidden: en headless la "ventana" no existe
    try {
        Object.defineProperty(document, 'visibilityState', {
            get: function() { return 'visible'; }, configurable: true,
        });
        Object.defineProperty(document, 'hidden', {
            get: function() { return false; }, configurable: true,
        });
    } catch(e) {}

    // outerWidth/Height: en headless son 0 (sin chrome del browser).
    // Usamos innerWidth/Height como base + offset típico de la barra de Chrome (85px).
    try {
        if (window.outerWidth === 0) {
            Object.defineProperty(window, 'outerWidth', {
                get: function() { return window.innerWidth; }, configurable: true,
            });
            Object.defineProperty(window, 'outerHeight', {
                get: function() { return window.innerHeight + 85; }, configurable: true,
            });
        }
    } catch(e) {}
})();`;

/**
 * Aplica patches complementarios de anti-detección (funciona con Patchright y Playwright puro).
 * @param {import('playwright').BrowserContext} context
 * @param {boolean} [headless=false] — si true, agrega los patches específicos de headless
 */
async function applyPlaywrightStealth(context, headless = false) {
    await context.addInitScript(PLAYWRIGHT_STEALTH_SCRIPT);
    if (headless) {
        await context.addInitScript(HEADLESS_PATCH_SCRIPT);
    }
}

module.exports = {
    REAL_PC_LAUNCH_OPTIONS,
    getLaunchArgs,
    loadOrCreateSeed,
    detectRealGpu,
    detectCssScreenSize,
    saveScreenToSession,
    applyRealPcPatches,
    applyPlaywrightStealth,
    getChromium,
    REAL_CPU_COUNT,
};
