# -*- coding: utf-8 -*-
"""
Playwright Stealth Utils - 反检测工具
用于隐藏浏览器自动化特征，绕过平台风控
"""

import random
import hashlib
import time
from typing import Dict, Any, Optional


# Stealth JS 脚本 - 隐藏 webdriver 等自动化特征
STEALTH_JS = """
// 1. 隐藏 webdriver 属性
Object.defineProperty(navigator, 'webdriver', {
    get: () => undefined
});

// 2. 修改 plugins 长度（非零表示真实浏览器）
Object.defineProperty(navigator, 'plugins', {
    get: () => {
        const plugins = [
            { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
            { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
            { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
        ];
        plugins.item = (index) => plugins[index] || null;
        plugins.namedItem = (name) => plugins.find(p => p.name === name) || null;
        plugins.refresh = () => {};
        return plugins;
    }
});

// 3. 修改 languages
Object.defineProperty(navigator, 'languages', {
    get: () => ['zh-CN', 'zh', 'en-US', 'en']
});

// 4. 隐藏自动化相关属性
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_JSON;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Object;

// 5. 伪装 Chrome 属性
window.chrome = {
    app: {
        isInstalled: false,
        InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
        RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' }
    },
    runtime: {
        OnInstalledReason: { CHROME_UPDATE: 'chrome_update', INSTALL: 'install', SHARED_MODULE_UPDATE: 'shared_module_update', UPDATE: 'update' },
        OnRestartRequiredReason: { APP_UPDATE: 'app_update', OS_UPDATE: 'os_update', PERIODIC: 'periodic' },
        PlatformArch: { ARM: 'arm', ARM64: 'arm64', MIPS: 'mips', MIPS64: 'mips64', X86_32: 'x86-32', X86_64: 'x86-64' },
        PlatformNaclArch: { ARM: 'arm', MIPS: 'mips', MIPS64: 'mips64', X86_32: 'x86-32', X86_64: 'x86-64' },
        PlatformOs: { ANDROID: 'android', CROS: 'cros', LINUX: 'linux', MAC: 'mac', OPENBSD: 'openbsd', WIN: 'win' },
        RequestUpdateCheckStatus: { NO_UPDATE: 'no_update', THROTTLED: 'throttled', UPDATE_AVAILABLE: 'update_available' },
        connect: function() { return { onDisconnect: { addListener: function() {} }, onMessage: { addListener: function() {} }, postMessage: function() {} }; },
        sendMessage: function() {}
    },
    csi: function() { return {}; },
    loadTimes: function() { return {}; }
};

// 6. 修复 permissions API
const originalQuery = window.navigator.permissions?.query;
if (originalQuery) {
    window.navigator.permissions.query = (parameters) => {
        if (parameters.name === 'notifications') {
            return Promise.resolve({ state: Notification.permission });
        }
        return originalQuery.call(window.navigator.permissions, parameters);
    };
}

// 7. 伪装 WebGL 指纹
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
    if (parameter === 37445) {
        return 'Intel Inc.';
    }
    if (parameter === 37446) {
        return 'Intel Iris OpenGL Engine';
    }
    return getParameter.call(this, parameter);
};

// 8. 隐藏自动化 iframe
const originalContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow').get;
Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {
    get: function() {
        const window = originalContentWindow.call(this);
        if (window) {
            try {
                Object.defineProperty(window.navigator, 'webdriver', { get: () => undefined });
            } catch (e) {}
        }
        return window;
    }
});

// 9. 伪装设备内存
Object.defineProperty(navigator, 'deviceMemory', {
    get: () => 8
});

// 10. 伪装硬件并发数
Object.defineProperty(navigator, 'hardwareConcurrency', {
    get: () => 8
});

// 11. 伪装电池 API
if (navigator.getBattery) {
    const originalGetBattery = navigator.getBattery;
    navigator.getBattery = function() {
        return Promise.resolve({
            charging: true,
            chargingTime: 0,
            dischargingTime: Infinity,
            level: 1,
            addEventListener: function() {},
            removeEventListener: function() {},
            dispatchEvent: function() { return true; }
        });
    };
}

// 12. 修复 iframe contentWindow
const originalAppendChild = Element.prototype.appendChild;
Element.prototype.appendChild = function(node) {
    if (node.tagName === 'IFRAME') {
        try {
            Object.defineProperty(node, 'setAttribute', {
                value: function(name, value) {
                    if (name === 'src' && value && value.includes('charset=utf-8')) {
                        return;
                    }
                    return Element.prototype.setAttribute.call(this, name, value);
                }
            });
        } catch (e) {}
    }
    return originalAppendChild.call(this, node);
};

console.log('[Stealth] Anti-detection scripts loaded');
"""

# Canvas 指纹噪声脚本
CANVAS_NOISE_JS = """
// Canvas 指纹噪声
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalToBlob = HTMLCanvasElement.prototype.toBlob;
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;

// 添加微小噪声
function addNoise(data) {
    for (let i = 0; i < data.length; i += 4) {
        // 对 RGBA 添加微小随机噪声（-1 到 1）
        data[i] = Math.max(0, Math.min(255, data[i] + Math.floor(Math.random() * 3) - 1));
        data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + Math.floor(Math.random() * 3) - 1));
        data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + Math.floor(Math.random() * 3) - 1));
    }
    return data;
}

HTMLCanvasElement.prototype.toDataURL = function(type) {
    if (this.width > 0 && this.height > 0) {
        try {
            const ctx = this.getContext('2d');
            if (ctx) {
                const imageData = ctx.getImageData(0, 0, this.width, this.height);
                addNoise(imageData.data);
                ctx.putImageData(imageData, 0, 0);
            }
        } catch (e) {}
    }
    return originalToDataURL.apply(this, arguments);
};

CanvasRenderingContext2D.prototype.getImageData = function() {
    const imageData = originalGetImageData.apply(this, arguments);
    addNoise(imageData.data);
    return imageData;
};
"""

# Audio 指纹噪声脚本
AUDIO_NOISE_JS = """
// Audio 指纹噪声
const originalCreateAnalyser = AudioContext.prototype.createAnalyser;
const originalGetFloatFrequencyData = AnalyserNode.prototype.getFloatFrequencyData;

AudioContext.prototype.createAnalyser = function() {
    const analyser = originalCreateAnalyser.call(this);
    const originalGetFloatFrequencyData = analyser.getFloatFrequencyData.bind(analyser);
    analyser.getFloatFrequencyData = function(array) {
        originalGetFloatFrequencyData(array);
        for (let i = 0; i < array.length; i++) {
            array[i] += (Math.random() - 0.5) * 0.001;
        }
    };
    return analyser;
};
"""


def get_user_agent() -> str:
    """获取随机 User-Agent"""
    user_agents = [
        # Chrome on Windows
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
        # Chrome on Mac
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
        # Edge on Windows
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
    ]
    return random.choice(user_agents)


def get_viewport() -> Dict[str, int]:
    """获取随机视口大小"""
    viewports = [
        {"width": 1920, "height": 1080},
        {"width": 1680, "height": 1050},
        {"width": 1440, "height": 900},
        {"width": 1536, "height": 864},
        {"width": 1366, "height": 768},
        {"width": 2560, "height": 1440},
    ]
    return random.choice(viewports)


def get_timezone() -> str:
    """获取时区"""
    return "Asia/Shanghai"


def get_locale() -> str:
    """获取语言区域"""
    return "zh-CN"


def get_screen_info() -> Dict[str, int]:
    """获取屏幕信息"""
    screens = [
        {"screen_width": 1920, "screen_height": 1080, "device_pixel_ratio": 1},
        {"screen_width": 2560, "screen_height": 1440, "device_pixel_ratio": 1},
        {"screen_width": 1680, "screen_height": 1050, "device_pixel_ratio": 1},
        {"screen_width": 1440, "screen_height": 900, "device_pixel_ratio": 2},  # Retina
    ]
    return random.choice(screens)


def get_webgl_vendor_renderer() -> Dict[str, str]:
    """获取 WebGL 供应商和渲染器"""
    configs = [
        {"vendor": "Google Inc. (Intel)", "renderer": "ANGLE (Intel, Intel(R) UHD Graphics 630, OpenGL 4.6)"},
        {"vendor": "Google Inc. (NVIDIA)", "renderer": "ANGLE (NVIDIA, NVIDIA GeForce GTX 1060, OpenGL 4.6)"},
        {"vendor": "Google Inc. (AMD)", "renderer": "ANGLE (AMD, AMD Radeon RX 580, OpenGL 4.6)"},
        {"vendor": "Intel Inc.", "renderer": "Intel Iris OpenGL Engine"},
    ]
    return random.choice(configs)


def get_browser_context_args(
    proxy_config: Optional[Dict[str, Any]] = None,
    user_agent: Optional[str] = None,
    viewport: Optional[Dict[str, int]] = None,
) -> Dict[str, Any]:
    """
    生成浏览器上下文参数
    
    Args:
        proxy_config: 代理配置 {"server": "http://ip:port"}
        user_agent: 自定义 User-Agent
        viewport: 视口大小 {"width": 1920, "height": 1080}
    
    Returns:
        浏览器上下文参数字典
    """
    screen_info = get_screen_info()
    
    args = {
        "user_agent": user_agent or get_user_agent(),
        "viewport": viewport or get_viewport(),
        "locale": get_locale(),
        "timezone_id": get_timezone(),
        "geolocation": {"latitude": 31.2304, "longitude": 121.4737},  # 上海
        "permissions": ["geolocation"],
        "color_scheme": "light",
        "device_scale_factor": screen_info.get("device_pixel_ratio", 1),
        "has_touch": False,
        "is_mobile": False,
        "java_script_enabled": True,
        "ignore_https_errors": True,
    }
    
    if proxy_config and proxy_config.get("server"):
        args["proxy"] = proxy_config
    
    return args


def get_stealth_scripts() -> str:
    """获取所有反检测脚本"""
    return f"""
{STEALTH_JS}

{CANVAS_NOISE_JS}

{AUDIO_NOISE_JS}
"""


def get_webgl_override_script() -> str:
    """获取 WebGL 指纹覆盖脚本"""
    webgl_config = get_webgl_vendor_renderer()
    return f"""
// WebGL 指纹覆盖
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {{
    // UNMASKED_VENDOR_WEBGL
    if (parameter === 37445) {{
        return '{webgl_config["vendor"]}';
    }}
    // UNMASKED_RENDERER_WEBGL
    if (parameter === 37446) {{
        return '{webgl_config["renderer"]}';
    }}
    return getParameter.call(this, parameter);
}};

// WebGL2 支持同样需要覆盖
if (typeof WebGL2RenderingContext !== 'undefined') {{
    const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
    WebGL2RenderingContext.prototype.getParameter = function(parameter) {{
        if (parameter === 37445) {{
            return '{webgl_config["vendor"]}';
        }}
        if (parameter === 37446) {{
            return '{webgl_config["renderer"]}';
        }}
        return getParameter2.call(this, parameter);
    }};
}}
"""


def get_navigator_override_script() -> str:
    """获取 Navigator 属性覆盖脚本"""
    screen_info = get_screen_info()
    return f"""
// Navigator 属性覆盖
Object.defineProperty(screen, 'width', {{
    get: () => {screen_info['screen_width']}
}});
Object.defineProperty(screen, 'height', {{
    get: () => {screen_info['screen_height']}
}});
Object.defineProperty(screen, 'availWidth', {{
    get: () => {screen_info['screen_width']}
}});
Object.defineProperty(screen, 'availHeight', {{
    get: () => {screen_info['screen_height'] - 40}  // 减去任务栏高度
}});
Object.defineProperty(window, 'devicePixelRatio', {{
    get: () => {screen_info.get('device_pixel_ratio', 1)}
}});
"""


def get_all_stealth_scripts() -> str:
    """获取完整的反检测脚本组合"""
    return f"""
{get_stealth_scripts()}

{get_webgl_override_script()}

{get_navigator_override_script()}

// 最终确认
console.log('[Stealth] All anti-detection scripts loaded successfully');
"""


async def inject_stealth_scripts(page) -> None:
    """
    向页面注入反检测脚本
    
    Args:
        page: Playwright page 对象
    """
    stealth_script = get_all_stealth_scripts()
    
    # 在页面加载前注入
    await page.add_init_script(stealth_script)
    
    # 如果页面已经加载，立即执行
    try:
        await page.evaluate(stealth_script)
    except Exception as e:
        # 页面可能还未完全加载，忽略错误
        pass


async def human_like_delay(min_ms: int = 100, max_ms: int = 500) -> None:
    """
    模拟人类操作的延迟
    
    Args:
        min_ms: 最小延迟毫秒数
        max_ms: 最大延迟毫秒数
    """
    delay = random.randint(min_ms, max_ms) / 1000.0
    time.sleep(delay)


async def human_like_type(page, selector: str, text: str, delay_range: tuple = (50, 150)) -> None:
    """
    模拟人类输入
    
    Args:
        page: Playwright page 对象
        selector: 选择器
        text: 要输入的文本
        delay_range: 延迟范围（毫秒）
    """
    element = page.locator(selector)
    await element.click()
    
    for char in text:
        await element.press(char)
        delay = random.randint(delay_range[0], delay_range[1]) / 1000.0
        time.sleep(delay)


async def human_like_scroll(page, distance: int = None) -> None:
    """
    模拟人类滚动
    
    Args:
        page: Playwright page 对象
        distance: 滚动距离（像素），默认随机
    """
    if distance is None:
        distance = random.randint(200, 500)
    
    # 分多次滚动，模拟真实行为
    steps = random.randint(3, 6)
    step_distance = distance // steps
    
    for _ in range(steps):
        await page.evaluate(f"window.scrollBy(0, {step_distance})")
        await human_like_delay(100, 300)
