#!/usr/bin/env python3
"""
GENESIS BROWSER CONTROLLER
===========================
Unified resilient multi-leveled advanced browser automation with failsafe capabilities.

Multi-Level Architecture:
    Level 1: Playwright (Primary) - Fastest, most reliable
    Level 2: Selenium WebDriver - Wide browser support
    Level 3: CDP (Chrome DevTools Protocol) - Direct control
    Level 4: HTTP Client - Headless API fallback
    Level 5: Cached/Archive - Wayback Machine fallback

Failsafe Features:
    - Automatic level escalation on failure
    - Request retry with exponential backoff
    - Proxy rotation support
    - Anti-detection measures
    - Session persistence and recovery
    - Screenshot capture on errors
    - Automatic CAPTCHA detection
    - Resource timeout management

Usage:
    controller = BrowserController()
    result = await controller.navigate("https://example.com")
    content = await controller.get_content()
"""

import json
import asyncio
import os
import re
import time
import hashlib
import random
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional, Callable, Tuple
from enum import Enum
from contextlib import asynccontextmanager
import logging
from core.ghl_synergy_bridge import GHLSynergyBridge
from core.vision_worker import VisionWorker


logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class BrowserLevel(Enum):
    """Browser automation levels (priority order)."""
    PLAYWRIGHT = 1      # Primary - fastest
    SELENIUM = 2        # Wide support
    CDP_DIRECT = 3      # Direct Chrome DevTools
    HTTP_CLIENT = 4     # Headless requests
    ARCHIVE = 5         # Wayback Machine fallback


class NavigationStatus(Enum):
    """Navigation result status."""
    SUCCESS = "success"
    TIMEOUT = "timeout"
    ERROR = "error"
    BLOCKED = "blocked"
    CAPTCHA = "captcha"
    RATE_LIMITED = "rate_limited"
    NOT_FOUND = "not_found"


@dataclass
class BrowserConfig:
    """Browser configuration with validation logic."""
    headless: bool = True
    timeout_ms: int = 30000
    viewport_width: int = 1920
    viewport_height: int = 1080
    user_agent: Optional[str] = None
    proxy: Optional[str] = None
    cookies_file: Optional[Path] = None
    screenshot_on_error: bool = True
    max_retries: int = 3
    retry_delay_ms: int = 1000
    anti_detection: bool = True

    def __post_init__(self):
        """Validate configuration parameters."""
        # Viewport validation (Sanitize extreme values)
        self.viewport_width = max(800, min(self.viewport_width, 3840))
        self.viewport_height = max(600, min(self.viewport_height, 2160))
        
        # Timeout validation (5s to 300s)
        self.timeout_ms = max(5000, min(self.timeout_ms, 300000))
        
        # Retry validation
        self.max_retries = max(0, min(self.max_retries, 10))
        self.retry_delay_ms = max(100, min(self.retry_delay_ms, 10000))
        
        # Path validation
        if self.cookies_file and not isinstance(self.cookies_file, Path):
            self.cookies_file = Path(self.cookies_file)


@dataclass
class NavigationResult:
    """Result from navigation attempt."""
    url: str
    status: NavigationStatus
    level_used: BrowserLevel
    content: Optional[str] = None
    html: Optional[str] = None
    title: Optional[str] = None
    status_code: int = 0
    duration_ms: int = 0
    screenshot_path: Optional[Path] = None
    cookies: Dict[str, str] = field(default_factory=dict)
    headers: Dict[str, str] = field(default_factory=dict)
    error: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class ElementResult:
    """Result from element interaction."""
    selector: str
    found: bool
    text: Optional[str] = None
    attributes: Dict[str, str] = field(default_factory=dict)
    visible: bool = False
    clickable: bool = False


class BrowserBackend(ABC):
    """Abstract base class for browser backends."""

    @abstractmethod
    def name(self) -> str:
        """Backend name."""
        pass

    @abstractmethod
    def level(self) -> BrowserLevel:
        """Backend priority level."""
        pass

    @abstractmethod
    async def is_available(self) -> bool:
        """Check if backend is available."""
        pass

    @abstractmethod
    async def initialize(self, config: BrowserConfig) -> bool:
        """Initialize the browser."""
        pass

    @abstractmethod
    async def navigate(self, url: str) -> NavigationResult:
        """Navigate to URL."""
        pass

    @abstractmethod
    async def get_content(self) -> str:
        """Get page content."""
        pass

    @abstractmethod
    async def find_element(self, selector: str) -> Optional[ElementResult]:
        """Find element by selector."""
        pass

    @abstractmethod
    async def click(self, selector: str) -> bool:
        """Click element."""
        pass

    @abstractmethod
    async def type_text(self, selector: str, text: str) -> bool:
        """Type text into element."""
        pass

    @abstractmethod
    async def screenshot(self, path: Path) -> bool:
        """Take screenshot."""
        pass

    @abstractmethod
    async def evaluate(self, script: str) -> Any:
        """Evaluate JavaScript in the page context."""
        pass

    @abstractmethod
    async def add_init_script(self, script: str):
        """Add script to be evaluated on every navigation."""
        pass

    @abstractmethod
    async def close(self):
        """Close browser."""
        pass


class PlaywrightBackend(BrowserBackend):
    """Playwright browser backend (Level 1 - Primary)."""

    def __init__(self):
        self._browser = None
        self._context = None
        self._page = None
        self._available = None

    def name(self) -> str:
        return "Playwright"

    def level(self) -> BrowserLevel:
        return BrowserLevel.PLAYWRIGHT

    async def is_available(self) -> bool:
        if self._available is None:
            try:
                from playwright.async_api import async_playwright
                self._available = True
            except ImportError:
                self._available = False
        return self._available

    async def initialize(self, config: BrowserConfig) -> bool:
        """
        Initialize Playwright backend.

        UVS-H13: Proper resource cleanup on initialization failure.
        """
        if not await self.is_available():
            return False

        # UVS-H13: Track partial initialization for cleanup
        init_playwright = False
        init_browser = False
        init_context = False

        try:
            from playwright.async_api import async_playwright

            self._playwright = await async_playwright().start()
            init_playwright = True

            self._browser = await self._playwright.chromium.launch(
                headless=config.headless
            )
            init_browser = True

            context_options = {
                "viewport": {
                    "width": config.viewport_width,
                    "height": config.viewport_height
                }
            }

            if config.user_agent:
                context_options["user_agent"] = config.user_agent
            elif config.anti_detection:
                context_options["user_agent"] = self._generate_user_agent()

            if config.proxy:
                context_options["proxy"] = {"server": config.proxy}

            self._context = await self._browser.new_context(**context_options)
            init_context = True

            # UVS-H25: Inject sparkle script with error handling (CSP-blocked pages won't crash)
            if hasattr(self, 'controller') and hasattr(self.controller, '_sparkle_script'):
                try:
                    await self._context.add_init_script(self.controller._sparkle_script)
                except Exception as sparkle_err:
                    logger.warning(f"Sparkle script injection failed (CSP-blocked?): {sparkle_err}")
                    # Continue on failure - sparkle is enhancement, not critical

            self._page = await self._context.new_page()

            # UVS-H10: Task registry for tracking background tasks
            if not hasattr(self, '_tracked_tasks'):
                self._tracked_tasks = set()

            # CONSOLE LISTENER for user activity
            def handle_console(msg):
                if "GENESIS_USER_ACTIVITY" in msg.text:
                    if hasattr(self, 'controller') and self.controller.on_user_activity:
                        # UVS-H10: Track task and add done callback
                        task = asyncio.create_task(self.controller.on_user_activity())
                        self._tracked_tasks.add(task)

                        def _task_done(t):
                            self._tracked_tasks.discard(t)
                            if t.exception():
                                logger.warning(f"User activity task exception: {t.exception()}")

                        task.add_done_callback(_task_done)

            self._page.on("console", handle_console)

            if config.cookies_file and config.cookies_file.exists():
                await self._load_cookies(config.cookies_file)

            return True
        except Exception as e:
            logger.error(f"Playwright initialization failed: {e}")
            # UVS-H13: Clean up partial initialization to prevent orphaned processes
            if init_context and self._context:
                try:
                    await self._context.close()
                except Exception as cleanup_err:
                    logger.debug(f"Context cleanup error: {cleanup_err}")
                self._context = None
            if init_browser and self._browser:
                try:
                    await self._browser.close()
                except Exception as cleanup_err:
                    logger.debug(f"Browser cleanup error: {cleanup_err}")
                self._browser = None
            if init_playwright and self._playwright:
                try:
                    await self._playwright.stop()
                except Exception as cleanup_err:
                    logger.debug(f"Playwright cleanup error: {cleanup_err}")
                self._playwright = None
            self._page = None
            return False

    async def is_alive(self) -> bool:
        """Check if browser and page are still active."""
        if not self._browser or not self._page:
            return False
        try:
            # Check if browser is connected and page is not closed
            return self._browser.is_connected() and not self._page.is_closed()
        except:
            return False

    def _generate_user_agent(self) -> str:
        """Generate realistic user agent."""
        agents = [
            "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 (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 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
        ]
        return random.choice(agents)

    async def _load_cookies(self, cookies_file: Path):
        """Load cookies from file."""
        try:
            with open(cookies_file, 'r') as f:
                cookies = json.load(f)
                await self._context.add_cookies(cookies)
        except Exception as e:
            logger.warning(f"Failed to load cookies: {e}")

    async def navigate(self, url: str) -> NavigationResult:
        if not self._page:
            return NavigationResult(
                url=url,
                status=NavigationStatus.ERROR,
                level_used=self.level(),
                error="Browser not initialized"
            )

        start = time.time()
        try:
            response = await self._page.goto(url, wait_until="domcontentloaded")
            duration_ms = int((time.time() - start) * 1000)

            status_code = response.status if response else 0
            title = await self._page.title()
            html = await self._page.content()

            # Check for blocks/captchas
            status = NavigationStatus.SUCCESS
            if status_code == 403:
                status = NavigationStatus.BLOCKED
            elif status_code == 429:
                status = NavigationStatus.RATE_LIMITED
            elif status_code == 404:
                status = NavigationStatus.NOT_FOUND
            elif self._detect_captcha(html):
                status = NavigationStatus.CAPTCHA

            return NavigationResult(
                url=url,
                status=status,
                level_used=self.level(),
                html=html,
                title=title,
                status_code=status_code,
                duration_ms=duration_ms
            )

        except asyncio.TimeoutError:
            return NavigationResult(
                url=url,
                status=NavigationStatus.TIMEOUT,
                level_used=self.level(),
                duration_ms=int((time.time() - start) * 1000),
                error="Navigation timeout"
            )
        except Exception as e:
            return NavigationResult(
                url=url,
                status=NavigationStatus.ERROR,
                level_used=self.level(),
                duration_ms=int((time.time() - start) * 1000),
                error=str(e)
            )

    def _detect_captcha(self, html: str) -> bool:
        """Detect CAPTCHA presence."""
        captcha_patterns = [
            'recaptcha', 'captcha', 'hcaptcha', 'challenge-running',
            'cf-turnstile', 'please verify', 'security check'
        ]
        html_lower = html.lower()
        return any(pattern in html_lower for pattern in captcha_patterns)

    async def get_content(self) -> str:
        if not self._page:
            return ""
        return await self._page.content()

    async def find_element(self, selector: str) -> Optional[ElementResult]:
        if not self._page:
            return None

        try:
            element = await self._page.query_selector(selector)
            if not element:
                return ElementResult(selector=selector, found=False)

            text = await element.text_content()
            visible = await element.is_visible()
            clickable = await element.is_enabled()

            return ElementResult(
                selector=selector,
                found=True,
                text=text,
                visible=visible,
                clickable=clickable
            )
        except Exception:
            return ElementResult(selector=selector, found=False)

    async def click(self, selector: str) -> bool:
        if not self._page:
            return False
        try:
            await self._page.click(selector)
            return True
        except Exception as e:
            logger.warning(f"Click failed: {e}")
            return False

    async def type_text(self, selector: str, text: str) -> bool:
        if not self._page:
            return False
        try:
            await self._page.fill(selector, text)
            return True
        except Exception as e:
            logger.warning(f"Type failed: {e}")
            return False

    async def screenshot(self, path: Path) -> bool:
        if not self._page:
            return False
        try:
            await self._page.screenshot(path=str(path))
            return True
        except Exception as e:
            logger.warning(f"Screenshot failed: {e}")
            return False

    async def evaluate(self, script: str) -> Any:
        if not self._page:
            return None
        try:
            return await self._page.evaluate(script)
        except Exception as e:
            logger.warning(f"Evaluation failed: {e}")
            return None

    async def add_init_script(self, script: str):
        if self._context:
            await self._context.add_init_script(script)
        elif self._page:
            await self._page.add_init_script(script)

    async def close(self):
        if self._browser:
            await self._browser.close()
        if hasattr(self, '_playwright') and self._playwright:
            await self._playwright.stop()


class HTTPClientBackend(BrowserBackend):
    """HTTP client backend (Level 4 - Headless fallback)."""

    def __init__(self):
        self._session = None
        self._last_response = None
        self._available = None

    def name(self) -> str:
        return "HTTP Client"

    def level(self) -> BrowserLevel:
        return BrowserLevel.HTTP_CLIENT

    async def is_available(self) -> bool:
        if self._available is None:
            try:
                import httpx
                self._available = True
            except ImportError:
                try:
                    import aiohttp
                    self._available = True
                except ImportError:
                    self._available = False
        return self._available

    async def initialize(self, config: BrowserConfig) -> bool:
        """
        Initialize HTTP client backend.

        UVS-H14: Proper session cleanup on initialization failure.
        """
        if not await self.is_available():
            return False

        session_created = False
        try:
            import httpx

            headers = {
                "User-Agent": config.user_agent or "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
            }

            self._session = httpx.AsyncClient(
                headers=headers,
                timeout=config.timeout_ms / 1000,
                follow_redirects=True,
                http2=True
            )
            session_created = True

            return True
        except Exception as e:
            logger.error(f"HTTP client init failed: {e}")
            # UVS-H14: Clean up partial initialization
            if session_created and self._session:
                try:
                    await self._session.aclose()
                except Exception as cleanup_err:
                    logger.debug(f"Session cleanup error: {cleanup_err}")
                self._session = None
            return False

    async def navigate(self, url: str) -> NavigationResult:
        if not self._session:
            return NavigationResult(
                url=url,
                status=NavigationStatus.ERROR,
                level_used=self.level(),
                error="Client not initialized"
            )

        start = time.time()
        try:
            # UVS-H20: Close previous response before storing new to prevent memory leaks
            if self._last_response:
                try:
                    await self._last_response.aclose()
                except Exception:
                    pass  # Ignore close errors
            response = await self._session.get(url)
            self._last_response = response
            duration_ms = int((time.time() - start) * 1000)

            # Parse title from HTML
            html = response.text
            title_match = re.search(r'<title[^>]*>([^<]+)</title>', html, re.IGNORECASE)
            title = title_match.group(1) if title_match else None

            status = NavigationStatus.SUCCESS
            if response.status_code == 403:
                status = NavigationStatus.BLOCKED
            elif response.status_code == 429:
                status = NavigationStatus.RATE_LIMITED
            elif response.status_code == 404:
                status = NavigationStatus.NOT_FOUND
            elif response.status_code >= 400:
                status = NavigationStatus.ERROR

            return NavigationResult(
                url=url,
                status=status,
                level_used=self.level(),
                html=html,
                title=title,
                status_code=response.status_code,
                duration_ms=duration_ms,
                headers=dict(response.headers)
            )

        except Exception as e:
            return NavigationResult(
                url=url,
                status=NavigationStatus.ERROR,
                level_used=self.level(),
                duration_ms=int((time.time() - start) * 1000),
                error=str(e)
            )

    async def get_content(self) -> str:
        if self._last_response:
            return self._last_response.text
        return ""

    async def find_element(self, selector: str) -> Optional[ElementResult]:
        # HTTP client can't interact with DOM
        return ElementResult(selector=selector, found=False)

    async def click(self, selector: str) -> bool:
        return False  # Not supported

    async def type_text(self, selector: str, text: str) -> bool:
        return False  # Not supported

    async def screenshot(self, path: Path) -> bool:
        return False  # Not supported

    async def evaluate(self, script: str) -> Any:
        return None  # Not supported

    async def add_init_script(self, script: str):
        pass

    async def close(self):
        if self._session:
            await self._session.aclose()


class ArchiveBackend(BrowserBackend):
    """Wayback Machine fallback (Level 5 - Last resort)."""

    def __init__(self):
        self._client = None
        self._last_content = None

    def name(self) -> str:
        return "Wayback Archive"

    def level(self) -> BrowserLevel:
        return BrowserLevel.ARCHIVE

    async def is_available(self) -> bool:
        try:
            import httpx
            return True
        except ImportError:
            return False

    async def initialize(self, config: BrowserConfig) -> bool:
        try:
            import httpx
            self._client = httpx.AsyncClient(timeout=config.timeout_ms / 1000)
            return True
        except Exception:
            return False

    async def navigate(self, url: str) -> NavigationResult:
        """
        Navigate via Wayback Machine archive.

        UVS-H21: Added asyncio.wait_for timeouts for API and content fetches.
        """
        if not self._client:
            return NavigationResult(
                url=url,
                status=NavigationStatus.ERROR,
                level_used=self.level(),
                error="Client not initialized"
            )

        # UVS-H21: Configurable timeouts
        ARCHIVE_API_TIMEOUT = 5.0  # 5 seconds for availability check
        ARCHIVE_FETCH_TIMEOUT = 10.0  # 10 seconds for content fetch

        start = time.time()
        try:
            # Query Wayback Machine API with timeout
            api_url = f"https://archive.org/wayback/available?url={url}"
            try:
                response = await asyncio.wait_for(
                    self._client.get(api_url),
                    timeout=ARCHIVE_API_TIMEOUT
                )
            except asyncio.TimeoutError:
                return NavigationResult(
                    url=url,
                    status=NavigationStatus.TIMEOUT,
                    level_used=self.level(),
                    duration_ms=int((time.time() - start) * 1000),
                    error=f"Archive API timeout after {ARCHIVE_API_TIMEOUT}s"
                )

            data = response.json()

            snapshots = data.get("archived_snapshots", {})
            closest = snapshots.get("closest", {})

            if not closest.get("available"):
                return NavigationResult(
                    url=url,
                    status=NavigationStatus.NOT_FOUND,
                    level_used=self.level(),
                    duration_ms=int((time.time() - start) * 1000),
                    error="No archive snapshot available"
                )

            # Fetch the archived page with timeout
            archive_url = closest["url"]
            try:
                archive_response = await asyncio.wait_for(
                    self._client.get(archive_url),
                    timeout=ARCHIVE_FETCH_TIMEOUT
                )
            except asyncio.TimeoutError:
                return NavigationResult(
                    url=url,
                    status=NavigationStatus.TIMEOUT,
                    level_used=self.level(),
                    duration_ms=int((time.time() - start) * 1000),
                    error=f"Archive fetch timeout after {ARCHIVE_FETCH_TIMEOUT}s"
                )

            self._last_content = archive_response.text
            duration_ms = int((time.time() - start) * 1000)

            # Parse title
            title_match = re.search(r'<title[^>]*>([^<]+)</title>', self._last_content, re.IGNORECASE)
            title = title_match.group(1) if title_match else None

            return NavigationResult(
                url=url,
                status=NavigationStatus.SUCCESS,
                level_used=self.level(),
                html=self._last_content,
                title=title,
                status_code=archive_response.status_code,
                duration_ms=duration_ms,
                metadata={
                    "archive_url": archive_url,
                    "archive_timestamp": closest.get("timestamp")
                }
            )

        except Exception as e:
            return NavigationResult(
                url=url,
                status=NavigationStatus.ERROR,
                level_used=self.level(),
                duration_ms=int((time.time() - start) * 1000),
                error=str(e)
            )

    async def get_content(self) -> str:
        return self._last_content or ""

    async def find_element(self, selector: str) -> Optional[ElementResult]:
        return ElementResult(selector=selector, found=False)

    async def click(self, selector: str) -> bool:
        return False

    async def type_text(self, selector: str, text: str) -> bool:
        return False

    async def screenshot(self, path: Path) -> bool:
        return False

    async def evaluate(self, script: str) -> Any:
        return None  # Not supported

    async def add_init_script(self, script: str):
        pass

    async def close(self):
        if self._client:
            await self._client.aclose()


class BrowserController:
    """
    Unified resilient multi-leveled browser controller.

    Provides automatic failover across multiple browser backends,
    with retry logic, anti-detection, and session persistence.

    VERIFICATION_STAMP (UVS-H07, UVS-H12, UVS-H19)
    - UVS-H07: CSRF token handling
    - UVS-H12: Bounded history with deque
    - UVS-H19: Thread-safe stats updates
    """

    def __init__(
        self,
        config: Optional[BrowserConfig] = None,
        storage_dir: Optional[Path] = None
    ):
        from collections import deque
        import threading

        self.config = config or BrowserConfig()
        self.on_user_activity: Optional[Callable] = None
        self.storage_dir = storage_dir or Path("/mnt/e/genesis-system/data/browser_sessions")
        self.storage_dir.mkdir(parents=True, exist_ok=True)

        self._abort_signal = asyncio.Event()

        # Initialize backends in priority order
        self._backends: List[BrowserBackend] = [
            PlaywrightBackend(),
            HTTPClientBackend(),
            ArchiveBackend()
        ]

        self._active_backend: Optional[BrowserBackend] = None
        self._initialized = False

        # UVS-H19: Thread-safe statistics with lock
        self._stats_lock = threading.Lock()
        self._stats = {
            "total_navigations": 0,
            "successful_navigations": 0,
            "by_level": {level.name: 0 for level in BrowserLevel},
            "errors": 0,
            "retries": 0,
            "captchas_detected": 0
        }

        # UVS-H07: CSRF token cache per domain
        self._csrf_tokens: Dict[str, str] = {}

        # Knowledge Bridge
        self.ghl_bridge = GHLSynergyBridge()
        self.ghl_bridge.load_ghl_context()

        # Vision Worker (Phase B)
        self.vision = VisionWorker(self)

        # UVS-H12: Bounded history with deque (prevents memory leak)
        self._history: deque = deque(maxlen=100)

        # Sparkle Script (Advanced Unified version)
        self._sparkle_script = """
(function() {
    const inject = () => {
        try {
            if (document.getElementById('genesis-sparkle-cursor')) return;
            if (!document.body) {
                setTimeout(inject, 50);
                return;
            }
            
            const cursor = document.createElement('div');
            cursor.id = 'genesis-sparkle-cursor';
            cursor.style.cssText = `
                position: fixed;
                width: 25px;
                height: 25px;
                background: radial-gradient(circle, rgba(0,242,255,1) 0%, rgba(0,242,255,0.2) 70%);
                border: 2px solid #00f2ff;
                border-radius: 50%;
                pointer-events: none;
                z-index: 999999;
                transition: transform 0.1s ease-out, left 0.1s linear, top 0.1s linear;
                box-shadow: 0 0 20px #00f2ff;
                display: none;
                left: 0;
                top: 0;
            `;
            
            const sparkle = document.createElement('div');
            sparkle.innerHTML = '✨';
            sparkle.style.cssText = `
                position: absolute;
                top: -10px;
                left: 20px;
                font-size: 20px;
                animation: sparkle-rotate 2s linear infinite;
            `;
            
            const style = document.createElement('style');
            style.id = 'genesis-sparkle-style';
            style.textContent = `
                @keyframes sparkle-rotate {
                    from { transform: rotate(0deg) scale(1); }
                    50% { transform: rotate(180deg) scale(1.2); }
                    to { transform: rotate(360deg) scale(1); }
                }
                @keyframes genesis-circle {
                    0% { transform: translate(0, 0); }
                    25% { transform: translate(30px, 0); }
                    50% { transform: translate(30px, 30px); }
                    75% { transform: translate(0, 30px); }
                    100% { transform: translate(0, 0); }
                }
                @keyframes genesis-underline {
                    0% { transform: translate(-50px, 20px); }
                    50% { transform: translate(50px, 20px); }
                    100% { transform: translate(-50px, 20px); }
                }
                #genesis-progress-pill {
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    background: rgba(0, 0, 0, 0.8);
                    backdrop-filter: blur(10px);
                    border: 1px solid #00f2ff;
                    border-radius: 20px;
                    padding: 8px 16px;
                    color: #00f2ff;
                    font-family: 'Inter', sans-serif;
                    font-size: 13px;
                    font-weight: 500;
                    z-index: 1000000;
                    display: none;
                    box-shadow: 0 4px 15px rgba(0, 242, 255, 0.3);
                    transition: all 0.3s ease;
                }
            `;
            
            const pill = document.createElement('div');
            pill.id = 'genesis-progress-pill';
            
            document.head.appendChild(style);
            cursor.appendChild(sparkle);
            document.body.appendChild(cursor);
            document.body.appendChild(pill);

            // MUTUAL AUTONOMY: Detect user activity
            let lastUserMove = 0;
            window.addEventListener('mousemove', (e) => {
                if (e.isTrusted) { // Only real user moves
                    const now = Date.now();
                    if (now - lastUserMove > 500) { // Throttle
                        console.log("GENESIS_USER_ACTIVITY: mousemove");
                        lastUserMove = now;
                    }
                }
            });

            window.clearGenesisAnchor = () => {
                if (window.genesisAnchorInterval) {
                    clearInterval(window.genesisAnchorInterval);
                    window.genesisAnchorInterval = null;
                }
                const c = document.getElementById('genesis-sparkle-cursor');
                if (c) {
                    c.style.animation = 'none';
                    c.style.borderColor = '#00f2ff'; 
                }
            };
            
            window.moveGenesisCursor = (x, y) => {
                window.clearGenesisAnchor();
                const c = document.getElementById('genesis-sparkle-cursor');
                if (c) {
                    c.style.display = 'block';
                    c.style.left = x + 'px';
                    c.style.top = y + 'px';
                    c.style.borderColor = '#ff004c'; 
                }
            };
            
            window.anchorGenesisCursor = (selector) => {
                const el = document.querySelector(selector);
                if (!el) return;
                window.clearGenesisAnchor();
                const c = document.getElementById('genesis-sparkle-cursor');
                if (!c) return;
                
                const updatePos = () => {
                    const rect = el.getBoundingClientRect();
                    c.style.display = 'block';
                    c.style.left = (rect.left + rect.width / 2) + 'px';
                    c.style.top = (rect.top + rect.height / 2) + 'px';
                };
                updatePos();
                window.genesisAnchorInterval = setInterval(updatePos, 50);
            };
            
            window.updateGenesisProgress = (text) => {
                const p = document.getElementById('genesis-progress-pill');
                if (p) {
                    if (text) {
                        p.innerText = "ANTIGRAVITY: " + text;
                        p.style.display = 'block';
                    } else {
                        p.style.display = 'none';
                    }
                }
            };

            window.gestureGenesisCursor = (type, selector) => {
                const el = document.querySelector(selector);
                const c = document.getElementById('genesis-sparkle-cursor');
                if (!el || !c) return;
                window.anchorGenesisCursor(selector);
                if (type === 'circle') {
                    c.style.animation = 'genesis-circle 1.5s ease-in-out infinite';
                } else if (type === 'underline') {
                    c.style.animation = 'genesis-underline 1.5s ease-in-out infinite';
                }
            };

            window.resetGenesisUI = () => {
                window.clearGenesisAnchor();
                window.updateGenesisProgress(null);
                const c = document.getElementById('genesis-sparkle-cursor');
                if (c) c.style.display = 'none';
                const p = document.getElementById('genesis-progress-pill');
                if (p) p.style.display = 'none';
            };
        } catch (e) { console.error("Sparkle injection error:", e); }
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', inject);
    } else {
        inject();
    }
})();
"""

        # History
        self._history: List[Dict] = []

    async def initialize(self) -> bool:
        """Initialize the best available browser backend."""
        for backend in self._backends:
            if await backend.is_available():
                # Link controller so backend can trigger callbacks and access init scripts
                backend.controller = self
                if await backend.initialize(self.config):
                    self._active_backend = backend
                    self._initialized = True
                    logger.info(f"Initialized browser with {backend.name()}")
                    return True

        logger.error("No browser backend available")
        return False

    async def get_available_backends(self) -> List[str]:
        """Get list of available backends."""
        available = []
        for backend in self._backends:
            if await backend.is_available():
                available.append(f"{backend.name()} (Level {backend.level().value})")
        return available

    async def navigate_to_ghl_resource(self, resource_name: str) -> NavigationResult:
        """
        Navigates to a GHL resource using knowledge from the synergy bridge.
        Example: resource_name="settings/api"
        """
        endpoint = self.ghl_bridge.get_api_endpoint(resource_name)
        if endpoint:
            # Construct full GHL URL (Assuming base URL is known or passed)
            # This is a simplified example
            target_url = f"https://app.gohighlevel.com{endpoint}"
            return await self.navigate(target_url)
        return NavigationResult(url=resource_name, status=NavigationStatus.ERROR, level_used=BrowserLevel.PLAYWRIGHT, error="Resource not found in KB")

    async def stop_all_actions(self):
        """Signals all pending AI actions to abort immediately."""
        logger.warning("[PROTOCOL] Global Abort Signal Sent.")
        self._abort_signal.set()
        # Briefly wait and clear for next session
        await asyncio.sleep(0.1)
        self._abort_signal.clear()

    async def ensure_browser_alive(self) -> bool:
        """Check if browser is alive and restart if necessary."""
        if self._active_backend:
            if await self._active_backend.is_alive():
                return True
            logger.warning("Browser detected as dead. Attempting restart...")
            self._initialized = False
            return await self.initialize()
        return await self.initialize()

    async def navigate(
        self,
        url: str,
        max_retries: Optional[int] = None,
        retry_delay_ms: Optional[int] = None
    ) -> NavigationResult:
        """
        Navigate to URL with automatic fallback and abort check.

        Tries each backend level in order until success.
        """
        if self._abort_signal.is_set():
            return NavigationResult(url=url, status=NavigationStatus.ERROR, level_used=BrowserLevel.PLAYWRIGHT, error="Aborted")

        if not self._initialized:
            await self.initialize()

        max_retries = max_retries or self.config.max_retries
        retry_delay_ms = retry_delay_ms or self.config.retry_delay_ms

        # UVS-H19: Thread-safe stats update
        self._update_stats("total_navigations")

        # Try each backend in priority order
        last_result = None
        for backend in self._backends:
            if not await backend.is_available():
                continue

            # Retry loop for current backend
            for attempt in range(max_retries):
                try:
                    # Initialize if not the active backend
                    if backend != self._active_backend:
                        if not await backend.initialize(self.config):
                            break
                        self._active_backend = backend

                    result = await backend.navigate(url)

                    # Check if we should retry
                    if result.status in [NavigationStatus.SUCCESS]:
                        # UVS-H19: Thread-safe stats updates
                        self._update_stats("successful_navigations")
                        self._update_stats_by_level(result.level_used.name)
                        self._record_history(result)
                        return result

                    if result.status == NavigationStatus.CAPTCHA:
                        self._update_stats("captchas_detected")
                        # Try next backend for CAPTCHAs
                        last_result = result
                        break

                    if result.status in [NavigationStatus.BLOCKED, NavigationStatus.RATE_LIMITED]:
                        # Exponential backoff
                        delay = retry_delay_ms * (2 ** attempt)
                        await asyncio.sleep(delay / 1000)
                        self._update_stats("retries")
                        continue

                    if result.status == NavigationStatus.NOT_FOUND:
                        # Don't retry 404s
                        last_result = result
                        break

                    last_result = result

                except Exception as e:
                    logger.warning(f"{backend.name()} error: {e}")
                    self._update_stats("errors")
                    last_result = NavigationResult(
                        url=url,
                        status=NavigationStatus.ERROR,
                        level_used=backend.level(),
                        error=str(e)
                    )

        # All backends failed
        if last_result:
            self._record_history(last_result)
            return last_result

        return NavigationResult(
            url=url,
            status=NavigationStatus.ERROR,
            level_used=BrowserLevel.ARCHIVE,
            error="All backends failed"
        )

    async def get_content(self) -> str:
        """Get current page content."""
        if self._active_backend:
            return await self._active_backend.get_content()
        return ""

    async def find_element(self, selector: str) -> Optional[ElementResult]:
        """Find element by CSS selector."""
        if self._active_backend:
            return await self._active_backend.find_element(selector)
        return None

    async def click(self, selector: str) -> bool:
        """Click element."""
        if self._active_backend:
            return await self._active_backend.click(selector)
        return False

    async def type_text(self, selector: str, text: str) -> bool:
        """Type text into element."""
        if self._active_backend:
            return await self._active_backend.type_text(selector, text)
        return False

    async def evaluate(self, script: str) -> Any:
        """Evaluate JavaScript in the page context."""
        if self._active_backend:
            return await self._active_backend.evaluate(script)
        return None

    async def add_init_script(self, script: str):
        """Add a script to be executed on every page navigation."""
        if self._active_backend:
            await self._active_backend.add_init_script(script)

    async def screenshot(self, name: Optional[str] = None) -> Optional[Path]:
        """Take screenshot with retry logic."""
        if not self._active_backend:
             if not await self.ensure_browser_alive():
                 return None

        name = name or f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
        path = self.storage_dir / f"{name}.png"

        for attempt in range(2): # Simple retry for transient capture errors
            try:
                if await self._active_backend.screenshot(path):
                    return path
                # If screenshot fails, check if browser died
                if not await self._active_backend.is_alive():
                    await self.ensure_browser_alive()
            except Exception as e:
                logger.warning(f"Screenshot attempt {attempt+1} failed: {e}")
                await asyncio.sleep(0.5)
        
        return None

    async def extract_text(self, selector: Optional[str] = None) -> str:
        """Extract text content from page or element."""
        html = await self.get_content()
        if not html:
            return ""

        # Remove scripts and styles
        html = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL | re.IGNORECASE)
        html = re.sub(r'<style[^>]*>.*?</style>', '', html, flags=re.DOTALL | re.IGNORECASE)

        # Remove HTML tags
        text = re.sub(r'<[^>]+>', ' ', html)

        # Clean whitespace
        text = re.sub(r'\s+', ' ', text).strip()

        return text

    async def wait_for_selector(
        self,
        selector: str,
        timeout_ms: int = 5000,
        check_interval_ms: int = 100
    ) -> bool:
        """
        Wait for element to appear.

        UVS-H29: Uses asyncio.wait_for for precise timeout regardless of system load.
        """
        async def _poll_for_element():
            while True:
                result = await self.find_element(selector)
                if result and result.found:
                    return True
                await asyncio.sleep(check_interval_ms / 1000)

        try:
            return await asyncio.wait_for(
                _poll_for_element(),
                timeout=timeout_ms / 1000
            )
        except asyncio.TimeoutError:
            return False

    def _record_history(self, result: NavigationResult):
        """
        Record navigation in history.

        UVS-H12: Uses deque with maxlen=100 - no manual trimming needed.
        """
        self._history.append({
            "timestamp": datetime.now().isoformat(),
            "url": result.url,
            "status": result.status.value,
            "level": result.level_used.name,
            "duration_ms": result.duration_ms,
            "title": result.title
        })
        # UVS-H12: deque automatically maintains maxlen, no trimming needed

    def _update_stats(self, key: str, increment: int = 1):
        """
        UVS-H19: Thread-safe stats update.

        Args:
            key: Stats key to update
            increment: Amount to increment by
        """
        with self._stats_lock:
            if key in self._stats:
                self._stats[key] += increment

    def _update_stats_by_level(self, level_name: str):
        """UVS-H19: Thread-safe level stats update."""
        with self._stats_lock:
            if level_name in self._stats["by_level"]:
                self._stats["by_level"][level_name] += 1

    async def extract_csrf_token(self, domain: str = None) -> Optional[str]:
        """
        UVS-H07: Extract CSRF token from current page.

        Searches for common CSRF token patterns in:
        - Meta tags (csrf-token, _csrf, X-CSRF-Token)
        - Hidden form inputs
        - Cookies

        Args:
            domain: Domain to cache token for (auto-detected if None)

        Returns:
            CSRF token if found, None otherwise
        """
        if not self._active_backend:
            return None

        try:
            # Get current domain for caching
            if domain is None:
                current_url = await self.evaluate("window.location.hostname")
                domain = current_url or "unknown"

            # Check cache first
            if domain in self._csrf_tokens:
                return self._csrf_tokens[domain]

            # Try meta tags
            token = await self.evaluate("""
                (() => {
                    const metaNames = ['csrf-token', '_csrf', 'csrf', 'X-CSRF-Token', '_token'];
                    for (const name of metaNames) {
                        const meta = document.querySelector(`meta[name="${name}"]`);
                        if (meta && meta.content) return meta.content;
                    }
                    return null;
                })()
            """)

            if token:
                self._csrf_tokens[domain] = token
                logger.debug(f"CSRF token extracted from meta tag for {domain}")
                return token

            # Try hidden form inputs
            token = await self.evaluate("""
                (() => {
                    const inputNames = ['_token', 'csrf_token', '_csrf', 'csrfmiddlewaretoken'];
                    for (const name of inputNames) {
                        const input = document.querySelector(`input[name="${name}"]`);
                        if (input && input.value) return input.value;
                    }
                    return null;
                })()
            """)

            if token:
                self._csrf_tokens[domain] = token
                logger.debug(f"CSRF token extracted from form input for {domain}")
                return token

            logger.debug(f"No CSRF token found for {domain}")
            return None

        except Exception as e:
            logger.warning(f"CSRF token extraction failed: {e}")
            return None

    def get_csrf_token(self, domain: str) -> Optional[str]:
        """Get cached CSRF token for domain."""
        return self._csrf_tokens.get(domain)

    def clear_csrf_tokens(self):
        """Clear all cached CSRF tokens."""
        self._csrf_tokens.clear()

    async def save_session(self, name: str) -> bool:
        """Save current session state."""
        try:
            session_file = self.storage_dir / f"session_{name}.json"
            # UVS-H19: Thread-safe stats access
            with self._stats_lock:
                stats_copy = dict(self._stats)

            data = {
                "saved_at": datetime.now().isoformat(),
                "active_backend": self._active_backend.name() if self._active_backend else None,
                "history": list(self._history)[-20:],  # Convert deque to list for JSON
                "stats": stats_copy
            }
            with open(session_file, 'w') as f:
                json.dump(data, f, indent=2)
            return True
        except Exception as e:
            logger.error(f"Failed to save session: {e}")
            return False

    async def close(self):
        """
        Close all backends and cancel tracked tasks.

        UVS-H10: Cancel and await all tracked tasks.
        """
        # UVS-H10: Cancel all tracked tasks
        if hasattr(self, '_tracked_tasks') and self._tracked_tasks:
            logger.debug(f"Cancelling {len(self._tracked_tasks)} tracked tasks")
            for task in list(self._tracked_tasks):
                if not task.done():
                    task.cancel()
            # Wait for all tasks to complete
            if self._tracked_tasks:
                await asyncio.gather(*self._tracked_tasks, return_exceptions=True)
            self._tracked_tasks.clear()

        for backend in self._backends:
            try:
                await backend.close()
            except Exception as e:
                logger.warning(f"Error closing {backend.name()}: {e}")

    def get_stats(self) -> Dict[str, Any]:
        """
        Get controller statistics.

        UVS-H19: Thread-safe stats access.
        """
        with self._stats_lock:
            stats_copy = dict(self._stats)
            stats_copy["by_level"] = dict(self._stats["by_level"])

        return {
            **stats_copy,
            "active_backend": self._active_backend.name() if self._active_backend else None,
            "history_entries": len(self._history)
        }


@asynccontextmanager
async def browser_session(config: Optional[BrowserConfig] = None):
    """
    Context manager for browser sessions.

    UVS-H22: Only call close() if initialize() succeeded to avoid cascading errors.
    """
    controller = BrowserController(config)
    initialized = False
    try:
        initialized = await controller.initialize()
        if not initialized:
            raise RuntimeError("Browser initialization failed")
        yield controller
    finally:
        # UVS-H22: Only close if successfully initialized
        if initialized:
            await controller.close()


async def main():
    """CLI for Browser Controller."""
    import argparse

    parser = argparse.ArgumentParser(description="Genesis Browser Controller")
    parser.add_argument("command", choices=["navigate", "backends", "stats", "test"])
    parser.add_argument("--url", help="URL to navigate to")
    parser.add_argument("--selector", help="CSS selector")
    parser.add_argument("--screenshot", action="store_true", help="Take screenshot")
    args = parser.parse_args()

    controller = BrowserController()

    if args.command == "backends":
        backends = await controller.get_available_backends()
        print("Available Browser Backends:")
        print("=" * 40)
        for b in backends:
            print(f"  - {b}")

    elif args.command == "navigate":
        if not args.url:
            print("Usage: --url https://example.com")
            return

        print(f"Navigating to: {args.url}")
        result = await controller.navigate(args.url)

        print(f"\nStatus: {result.status.value}")
        print(f"Level: {result.level_used.name}")
        print(f"Title: {result.title}")
        print(f"Duration: {result.duration_ms}ms")

        if result.error:
            print(f"Error: {result.error}")

        if args.screenshot:
            path = await controller.screenshot()
            if path:
                print(f"Screenshot: {path}")

        await controller.close()

    elif args.command == "stats":
        print("Browser Controller Statistics:")
        print("=" * 40)
        print(json.dumps(controller.get_stats(), indent=2))

    elif args.command == "test":
        print("Testing browser backends...")
        print("=" * 40)

        test_url = "https://example.com"
        result = await controller.navigate(test_url)

        print(f"URL: {test_url}")
        print(f"Status: {result.status.value}")
        print(f"Backend: {result.level_used.name}")
        print(f"Title: {result.title}")

        # Extract some text
        text = await controller.extract_text()
        print(f"Text preview: {text[:200]}...")

        await controller.close()


if __name__ == "__main__":
    asyncio.run(main())
