"""Stealth detection tests for cloakbrowser.

These tests verify that the stealth Chromium binary passes common
bot detection checks. They require network access.
"""

import os
import time

import pytest
from cloakbrowser import launch

PROXY = os.environ.get("CLOAKBROWSER_TEST_PROXY")


@pytest.fixture(scope="module")
def browser():
    """Shared browser instance for stealth tests."""
    b = launch(headless=True, proxy=PROXY)
    yield b
    b.close()


@pytest.fixture
def page(browser):
    """Fresh page for each test."""
    p = browser.new_page()
    yield p
    p.close()


class TestWebDriverDetection:
    """Tests for WebDriver/automation detection signals."""

    def test_navigator_webdriver_false(self, page):
        """navigator.webdriver must be false."""
        page.goto("https://example.com")
        assert page.evaluate("navigator.webdriver") is False

    def test_no_headless_chrome_ua(self, page):
        """User agent must not contain 'HeadlessChrome'."""
        page.goto("https://example.com")
        ua = page.evaluate("navigator.userAgent")
        assert "HeadlessChrome" not in ua
        assert "Chrome/" in ua

    def test_window_chrome_exists(self, page):
        """window.chrome must be an object (not undefined)."""
        page.goto("https://example.com")
        assert page.evaluate("typeof window.chrome") == "object"

    def test_plugins_present(self, page):
        """Must have browser plugins (real Chrome has 5)."""
        page.goto("https://example.com")
        count = page.evaluate("navigator.plugins.length")
        assert count >= 5, f"Expected 5+ plugins (real Chrome), got {count}"

    def test_languages_present(self, page):
        """navigator.languages must be populated."""
        page.goto("https://example.com")
        langs = page.evaluate("navigator.languages")
        assert len(langs) >= 1

    def test_cdp_not_detected(self, page):
        """Chrome DevTools Protocol should not be detectable."""
        page.goto("https://example.com")
        # Common CDP detection: check for Runtime.evaluate artifacts
        has_cdp = page.evaluate("""
            () => {
                try {
                    // Check common CDP leak: window.cdc_
                    const keys = Object.keys(window);
                    return keys.some(k => k.startsWith('cdc_') || k.startsWith('__webdriver'));
                } catch(e) {
                    return false;
                }
            }
        """)
        assert has_cdp is False


class TestBotDetectionSites:
    """Live tests against bot detection services.

    These require network access and may be slow.
    Mark with pytest -m slow to skip in CI.
    """

    @pytest.mark.slow
    def test_bot_sannysoft(self, page):
        """bot.sannysoft.com — all checks should pass (0 failures)."""
        page.goto("https://bot.sannysoft.com", wait_until="networkidle", timeout=30000)
        time.sleep(3)

        results = page.evaluate("""() => {
            const rows = document.querySelectorAll('table tr');
            const failed = [];
            let total = 0;
            rows.forEach(r => {
                const cells = r.querySelectorAll('td');
                if (cells.length >= 2) {
                    total++;
                    const cls = cells[1].className || '';
                    if (cls.includes('failed')) {
                        failed.push(cells[0].innerText.trim());
                    }
                }
            });
            return {total, failed};
        }""")

        failed = results["failed"]
        assert len(failed) == 0, f"Sannysoft failures: {', '.join(failed)}"

    @pytest.mark.slow
    def test_bot_incolumitas(self, page):
        """bot.incolumitas.com — max 1 failure (WEBDRIVER false positive expected)."""
        page.goto("https://bot.incolumitas.com", wait_until="networkidle", timeout=30000)
        time.sleep(12)

        # Known acceptable failures (not browser fingerprint issues):
        # - WEBDRIVER: spec-level false positive across all builds
        # - connectionRTT: detects datacenter/proxy network latency, not browser
        KNOWN_ACCEPTABLE = {"WEBDRIVER", "connectionRTT"}

        results = page.evaluate("""() => {
            const text = document.body.innerText;
            const okMatches = text.match(/"\\w+":\\s*"OK"/g) || [];
            const failMatches = text.match(/"\\w+":\\s*"FAIL"/g) || [];
            const failedTests = failMatches.map(m => m.match(/"(\\w+)"/)[1]);
            return {passed: okMatches.length, failed: failMatches.length, failedTests};
        }""")

        failed_names = results["failedTests"]
        real_failures = [f for f in failed_names if f not in KNOWN_ACCEPTABLE]
        assert len(real_failures) == 0, f"Incolumitas unexpected failures: {', '.join(real_failures)}"

    @pytest.mark.slow
    def test_browserscan(self, page):
        """BrowserScan bot detection — 0 abnormal checks."""
        page.goto("https://www.browserscan.net/bot-detection", wait_until="networkidle", timeout=30000)
        time.sleep(5)

        results = page.evaluate("""() => {
            const text = document.body.innerText;
            const normalMatches = text.match(/Normal/g);
            const abnormalMatches = text.match(/Abnormal/g);
            return {
                normal: normalMatches ? normalMatches.length : 0,
                abnormal: abnormalMatches ? abnormalMatches.length : 0
            };
        }""")

        assert results["abnormal"] == 0, \
            f"BrowserScan: {results['abnormal']} abnormal, {results['normal']} normal"

    @pytest.mark.slow
    def test_device_and_browser_info(self, page):
        """deviceandbrowserinfo.com — isBot must be false."""
        page.goto("https://deviceandbrowserinfo.com/are_you_a_bot", wait_until="domcontentloaded", timeout=30000)
        time.sleep(8)

        results = page.evaluate("""() => {
            const text = document.body.innerText;
            const botMatch = text.match(/"isBot":\\s*(true|false)/);
            const isBot = botMatch ? botMatch[1] === 'true' : null;
            const checks = {};
            ['isBot', 'hasBotUserAgent', 'hasWebdriverTrue', 'isHeadlessChrome',
             'isAutomatedWithCDP', 'hasSuspiciousWeakSignals', 'isPlaywright',
             'hasInconsistentChromeObject'].forEach(p => {
                const match = text.match(new RegExp('"' + p + '":\\s*(true|false)'));
                if (match) checks[p] = match[1] === 'true';
            });
            return {isBot, checks};
        }""")

        assert results["isBot"] is False, f"Detected as bot! Checks: {results['checks']}"

    @pytest.mark.slow
    def test_fingerprintjs(self, page):
        """FingerprintJS — must not be blocked, should see flight data."""
        page.goto("https://demo.fingerprint.com/web-scraping", wait_until="domcontentloaded", timeout=30000)
        time.sleep(8)

        try:
            page.click("button:has-text('Search')", timeout=5000)
            time.sleep(5)
        except Exception:
            pass

        results = page.evaluate("""() => {
            const text = document.body.innerText;
            const hasFlights = text.includes('Price per adult') || text.includes('$');
            const isBlocked = text.includes('request was blocked') || text.includes('bot visit detected');
            return {passed: hasFlights && !isBlocked, isBlocked, hasFlights};
        }""")

        assert not results["isBlocked"], "FingerprintJS blocked us as a bot"
        assert results["passed"], "FingerprintJS: no flight data shown"

    @pytest.mark.slow
    def test_recaptcha_v3(self, page):
        """reCAPTCHA v3 — score must be >= 0.7."""
        page.goto(
            "https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php",
            wait_until="domcontentloaded",
            timeout=60000,
        )
        time.sleep(8)

        results = page.evaluate("""() => {
            const text = document.body.innerText;
            const scoreMatch = text.match(/"score":\\s*(\\d+\\.\\d+)/);
            return {score: scoreMatch ? parseFloat(scoreMatch[1]) : null};
        }""")

        score = results["score"]
        assert score is not None, "Could not extract reCAPTCHA score"
        assert score >= 0.7, f"reCAPTCHA score too low: {score}"


class TestIssueRegressions:
    """Regression tests for specific GitHub issues.

    Uses the shared browser fixture to avoid "Sync API inside asyncio loop"
    errors when pytest-asyncio is active.
    """

    @pytest.mark.slow
    def test_immediate_goto_works(self, browser):
        """Issue #9: page.goto() immediately after launch must not fail.

        User reported reCAPTCHA fails if goto is called too quickly after
        launch. This test verifies that immediate navigation works without
        needing an artificial delay.
        """
        page = browser.new_page()
        # No delay — goto immediately
        page.goto("https://example.com", timeout=30000)
        title = page.title()
        page.close()
        assert "Example Domain" in title, f"Immediate goto failed, title={title}"

    @pytest.mark.slow
    def test_add_init_script_without_proxy(self, browser):
        """Issue #27: add_init_script must work (baseline without proxy).

        The bug is proxy + add_init_script, but we first verify init_script
        alone works so we have a baseline.
        """
        page = browser.new_page()
        page.add_init_script("window.__cloaktest = 42;")
        page.goto("https://example.com", timeout=30000)
        val = page.evaluate("window.__cloaktest")
        page.close()
        assert val == 42, f"add_init_script failed, got {val}"

    @pytest.mark.slow
    def test_add_init_script_with_proxy(self, browser):
        """Issue #27: add_init_script + proxy must not cause ERR_TUNNEL_CONNECTION_FAILED.

        Patchright bug: add_init_script breaks proxy auth. This test guards
        against regression if/when the upstream fix lands. Uses context-level
        proxy to avoid launching a separate browser (event loop conflict).
        """
        proxy = os.environ.get("CLOAKBROWSER_TEST_PROXY")
        if not proxy:
            pytest.skip("CLOAKBROWSER_TEST_PROXY not set")

        ctx = browser.new_context(proxy={"server": proxy})
        page = ctx.new_page()
        page.add_init_script("window.__cloaktest = 99;")
        try:
            page.goto("https://httpbin.org/ip", timeout=30000)
            body = page.evaluate("document.body.innerText")
            val = page.evaluate("window.__cloaktest")
            assert val == 99, f"init_script value wrong: {val}"
            assert "origin" in body, f"Page didn't load through proxy: {body[:100]}"
        except Exception as e:
            err = str(e)
            if "ERR_TUNNEL_CONNECTION_FAILED" in err:
                pytest.xfail("Known patchright bug: add_init_script + proxy auth (issue #27)")
            raise
        finally:
            page.close()
            ctx.close()
