import os
import platform
import time
import asyncio
import threading
from typing import Literal, Optional, Union, TYPE_CHECKING, List
from enum import Enum
from dataclasses import dataclass
from playwright.async_api import async_playwright, Browser, Playwright, BrowserContext

from executor.logger import logger, _job_context_var

# 延迟导入避免循环依赖
if TYPE_CHECKING:
    from executor.playwright.base_page import BasePage


def _get_data_dir(subdir: str = "") -> str:
    """
    获取数据目录（固定使用 data 目录）
    
    Args:
        subdir: 子目录名（如 'videos', 'traces', 'browser_user_data'）
    
    Returns:
        数据目录路径
    """
    # Docker 环境
    if os.path.exists('/.dockerenv'):
        base = "/app/data"
    else:
        # 本地环境，使用执行器根目录下的 data
        executor_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        base = os.path.join(executor_root, "data")
    
    if subdir:
        return os.path.join(base, subdir)
    return base

# ============================================
# 浏览器池配置常量
# ============================================
MAX_BROWSER_POOL_SIZE = 5           # 浏览器池最大数量
BROWSER_IDLE_TIMEOUT = 1800         # 浏览器空闲超时时间（秒，默认30分钟）
POOL_CLEANUP_INTERVAL = 60          # 池清理检查间隔（秒）


class UserAgent(Enum):
    """预定义的User-Agent常量"""
    CHROME_WINDOWS = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    CHROME_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"
    CHROME_LINUX = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    IPHONE_13 = "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1"
    IPAD = "Mozilla/5.0 (iPad; CPU OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1"
    ANDROID = "Mozilla/5.0 (Linux; Android 11; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"

@dataclass
class BrowserPoolItem:
    """浏览器池中的单个浏览器实例"""
    browser_id: int                      # 浏览器编号（0-4）
    context: BrowserContext              # Persistent Context实例
    playwright: Playwright               # Playwright实例
    user_data_dir: str                   # 持久化目录
    is_using: bool = False               # 是否正在使用
    last_used_time: float = 0.0         # 最后使用时间
    created_time: float = 0.0            # 创建时间
    is_healthy: bool = True              # 健康状态
    current_task_id: Optional[str] = None  # 当前使用的任务ID
    
    def __post_init__(self):
        if self.last_used_time == 0.0:
            self.last_used_time = time.time()
        if self.created_time == 0.0:
            self.created_time = time.time()


class EnvironmentDetector:
    """环境检测器"""
    
    @staticmethod
    def is_docker() -> bool:
        """检测是否在Docker容器内运行"""
        if os.path.exists('/.dockerenv'):
            return True
        try:
            with open('/proc/1/cgroup', 'r') as f:
                return 'docker' in f.read()
        except Exception:
            pass
        if os.getenv('DOCKER_CONTAINER'):
            return True
        return False
    
    @staticmethod
    def get_os() -> str:
        return platform.system().lower()


class BrowserManager:
    """浏览器管理器 - 集成浏览器池功能"""
    
    # ============================================
    # 类级别的浏览器池（所有实例共享）
    # ============================================
    _browser_pool: List[BrowserPoolItem] = []
    _pool_lock: asyncio.Lock = None  # 并发锁（延迟初始化）
    _pool_lock_init_lock = threading.Lock()  # 用于保护_pool_lock初始化的线程锁
    _pending_creations: int = 0  # 未完成的浏览器创建数量（确保池上限计算正确）
    
    def __init__(
        self,
        # 浏览器配置
        headless: Optional[bool] = None,                                    # 是否使用无头模式（None时根据环境自动判断）
        user_agent: Union[UserAgent, str, None] = UserAgent.CHROME_WINDOWS, # User-Agent配置
        storage_state: Optional[str] = None,                                # 存储状态文件路径
        # Context配置
        record_video: bool = False,                                         # 是否录制视频
        record_trace: bool = False,                                         # 是否记录trace
        # 存储配置
        videos_dir: Optional[str] = None,                                   # 录屏保存目录
        traces_dir: Optional[str] = None,                                   # Trace保存目录
        # 其他Playwright Context参数
        **context_kwargs
    ):
        # 浏览器池相关实例变量
        self._pool_item: Optional[BrowserPoolItem] = None  # 当前使用的池项
        self._task_pages: List = []  # 当前任务创建的页面（用于清理）
        self._trace_started: bool = False  # Trace是否已启动
        
        # Playwright实例（从池获取）
        self.playwright: Optional[Playwright] = None
        self.browser: Optional[Browser] = None
        self.context: Optional[BrowserContext] = None
        
        # Context配置
        self.record_video = record_video
        self.record_trace = record_trace
        self.viewport = {"width": 1920, "height": 1080}                     # 默认视口大小
        self.locale = "zh-CN"                                               # 默认语言环境
        self.timezone_id = "Asia/Shanghai"                                  # 默认时区
        
        # User-Agent配置
        if isinstance(user_agent, UserAgent):
            self.user_agent = user_agent.value
        elif isinstance(user_agent, str):
            self.user_agent = user_agent
        else:
            self.user_agent = None
        
        # 存储状态配置
        self.storage_state = storage_state
        
        # 其他Context参数
        self.context_kwargs = context_kwargs
        
        # 环境检测
        self.is_docker = EnvironmentDetector.is_docker()
        self.os_type = EnvironmentDetector.get_os()
        
        # 确定headless模式
        if headless is None:
            self.headless = self.is_docker
        else:
            self.headless = headless
        
        # 录屏保存目录
        if videos_dir is None:
            self.videos_dir = _get_data_dir("videos")
        else:
            self.videos_dir = videos_dir
        # Trace保存目录
        self.traces_dir = traces_dir or _get_data_dir("traces")
        os.makedirs(self.videos_dir, exist_ok=True)
        os.makedirs(self.traces_dir, exist_ok=True)
        
        # 预生成统一任务标识（确保 trace 和 video 使用相同的 标识）
        self._task_flag = self._generate_task_flag()
        
        # 记录最近的Context产生的文件（trace和video zip）
        self._last_context_files = {
            "trace_path": None,
            "video_path": None
        }
        
        logger.REMOTE.info(f"🚀 浏览器初始化:")
        logger.REMOTE.info(f"  └─ 任务标识: {self._task_flag}")
        logger.REMOTE.info(f"  └─ 运行环境: {'Docker' if self.is_docker else '本地'}")
        logger.REMOTE.info(f"  └─ 操作系统: {self.os_type}")
        logger.REMOTE.info(f"  └─ 无头模式: {self.headless}")
        logger.REMOTE.info(f"  └─ 开启录屏: {self.record_video}")
        if self.record_video:
            logger.REMOTE.info(f"    └─ 录屏文件名: {self._task_flag}.zip")
            logger.REMOTE.info(f"    └─ 保存目录: {self.videos_dir}")
        logger.REMOTE.info(f"  └─ 开启Trace: {self.record_trace}")
        if self.record_trace:
            logger.REMOTE.info(f"    └─ Trace文件名: {self._task_flag}.zip")
            logger.REMOTE.info(f"    └─ 保存目录: {self.traces_dir}")
    
    def _get_launch_args(self) -> list:
        """根据环境生成浏览器启动参数"""
        args = [
            # 基础参数
            "--no-first-run",
            "--no-default-browser-check",
            "--window-position=0,0",
            f"--window-size={self.viewport['width']},{self.viewport['height']}",
            
            # 反自动化检测参数
            "--disable-blink-features=AutomationControlled",
            "--disable-infobars",
            "--exclude-switches=enable-automation",
            "--disable-extensions",
            
            # 隐私和安全参数
            "--disable-component-extensions-with-background-pages",
            "--disable-default-apps",
            "--disable-features=TranslateUI",
            "--disable-ipc-flooding-protection",
            "--disable-popup-blocking",
            
            # 性能优化参数
            "--disable-hang-monitor",
            "--disable-prompt-on-repost",
            "--disable-sync",
            "--metrics-recording-only",
            "--no-service-autorun",
            "--password-store=basic",
            "--disable-background-networking",
            
            # 下载和安全浏览
            "--disable-save-password-bubble",
            "--safebrowsing-disable-download-protection",
            "--disable-client-side-phishing-detection",
            "--safebrowsing-disable-auto-update",
            "--disable-features=SafeBrowsingEnhanced",
        ]
        
        # Docker环境特殊参数
        if self.is_docker:
            args.extend([
                "--no-sandbox",
                "--disable-dev-shm-usage",
                "--disable-gpu",
                "--disable-software-rasterizer",
            ])
        
        return args
    
    def _get_pool_browser_dir(self, browser_id: int) -> str:
        """
        生成浏览器池的持久化目录
        使用与videos/traces同级的browser_user_data目录
        """
        base_dir = _get_data_dir("browser_user_data")
        
        # 确保目录存在
        os.makedirs(base_dir, exist_ok=True)
        
        return os.path.join(base_dir, f"browser_{browser_id}")
    
    @classmethod
    def _get_next_browser_id(cls) -> int:
        """获取下一个可用的浏览器ID"""
        if not cls._browser_pool:
            return 0
        
        # 找到未使用的最小ID
        used_ids = {item.browser_id for item in cls._browser_pool}
        for i in range(MAX_BROWSER_POOL_SIZE):
            if i not in used_ids:
                return i
        
        # 理论上不应该到这里（已在调用处检查上限）
        return len(cls._browser_pool)
    
    async def _start_trace_if_needed(self):
        """如果需要且未启动，则启动Trace记录"""
        if self.record_trace and not self._trace_started and self.context:
            trace_path = os.path.join(self.traces_dir, f"{self._task_flag}.zip")
            
            try:
                # 添加超时保护，防止trace启动卡死
                await asyncio.wait_for(
                    self.context.tracing.start(screenshots=True, snapshots=True, sources=True),
                    timeout=5.0  # 5秒超时
                )
                self._last_context_files["trace_path"] = trace_path
                self._trace_started = True
                logger.REMOTE.debug(f"  └─ Trace记录已开启: {self._task_flag}.zip")
            except asyncio.TimeoutError:
                logger.REMOTE.warning(f"⚠️ Trace启动超时(5秒)，跳过trace记录: {self._task_flag}.zip")
                self.record_trace = False  # 禁用trace避免后续问题
                # Trace超时可能导致context不稳定，标记为不健康
                if self._pool_item:
                    self._pool_item.is_healthy = False
                    logger.REMOTE.warning(f"  └─ 标记Browser-{self._pool_item.browser_id}为不健康（trace超时）")
            except Exception as e:
                logger.REMOTE.warning(f"⚠️ Trace启动失败，跳过trace记录: {e}")
                self.record_trace = False  # 禁用trace避免后续问题
                # Trace失败可能导致context不稳定，标记为不健康
                if self._pool_item:
                    self._pool_item.is_healthy = False
                    logger.REMOTE.warning(f"  └─ 标记Browser-{self._pool_item.browser_id}为不健康（trace失败）")
    
    async def _check_browser_health(self, item: BrowserPoolItem) -> bool:
        """
        检查浏览器是否健康
        优化：只检查context状态，不创建新页面
        """
        try:
            # 检查context是否还有效
            if item.context is None:
                return False
            
            # 添加超时保护的健康检查
            async def health_check():
                # 尝试获取页面列表（轻量级操作）
                _ = item.context.pages
            
            await asyncio.wait_for(health_check(), timeout=3.0)
            return True
            
        except asyncio.TimeoutError:
            logger.REMOTE.warning(f"Browser-{item.browser_id} 健康检查超时")
            return False
        except Exception as e:
            logger.REMOTE.warning(f"Browser-{item.browser_id} 健康检查失败: {e}")
            return False
    
    async def _create_browser_in_pool(self, browser_id: int) -> BrowserPoolItem:
        """创建一个持久化浏览器并加入池"""
        user_data_dir = self._get_pool_browser_dir(browser_id)
        
        # 启动Playwright
        playwright = await async_playwright().start()
        
        # 构建context选项
        context_options = {
            "headless": self.headless,
            "args": self._get_launch_args(),
            "viewport": self.viewport,
            "locale": self.locale,
            "timezone_id": self.timezone_id,
            "ignore_https_errors": True,
            "permissions": ["geolocation", "notifications", "clipboard-read", "clipboard-write", "microphone", "camera"],
        }
        
        if self.user_agent:
            context_options["user_agent"] = self.user_agent
        
        if self.storage_state:
            context_options["storage_state"] = self.storage_state
        
        if self.record_video:
            context_options["record_video_dir"] = self.videos_dir
            context_options["record_video_size"] = self.viewport
        
        context_options.update(self.context_kwargs)
        
        # 启动持久化Context
        context = await playwright.chromium.launch_persistent_context(
            user_data_dir,
            **context_options
        )
        
        # 创建池项
        item = BrowserPoolItem(
            browser_id=browser_id,
            context=context,
            playwright=playwright,
            user_data_dir=user_data_dir
        )
        
        logger.REMOTE.debug(f"  └─ 持久化Context已创建: Browser-{browser_id} ({user_data_dir})")
        return item
    
    @classmethod
    async def _cleanup_unhealthy_browsers(cls):
        """清理不健康的浏览器"""
        cls._ensure_pool_lock()
        to_remove: List[BrowserPoolItem] = []
        async with cls._pool_lock:
            for item in list(cls._browser_pool):
                if not item.is_healthy and not item.is_using:
                    logger.REMOTE.info(f"🧹 清理不健康浏览器 Browser-{item.browser_id}")
                    to_remove.append(item)
                    cls._browser_pool.remove(item)
        
        for item in to_remove:
            await cls._close_browser_item(item)
    
    @classmethod
    async def _close_browser_item(cls, item: BrowserPoolItem):
        """关闭单个浏览器项（不删除持久化目录）"""
        try:
            if item.context:
                await item.context.close()
            if item.playwright:
                await item.playwright.stop()
            
            logger.REMOTE.debug(f"  └─ Browser-{item.browser_id} 已关闭")
        except Exception as e:
            logger.REMOTE.warning(f"  └─ 关闭Browser-{item.browser_id}时出错: {e}")
    
    @classmethod
    async def _auto_cleanup_loop(cls):
        """
        浏览器池清理任务（单次执行版本）
        
        由 APScheduler 定时调用，每次执行一次清理检查
        不再是无限循环，避免事件循环管理复杂性
        """
        try:
            # 线程安全的锁初始化
            cls._ensure_pool_lock()
            
            # 先在锁内收集需要清理的项，然后在锁外执行关闭操作
            to_remove = []
            async with cls._pool_lock:
                current_time = time.time()
                
                for item in cls._browser_pool:
                    idle_time = current_time - item.last_used_time
                    
                    if not item.is_using and idle_time > BROWSER_IDLE_TIMEOUT:
                        logger.REMOTE.info(
                            f"🧹 清理空闲浏览器 Browser-{item.browser_id} "
                            f"(空闲{idle_time/60:.1f}分钟)"
                        )
                        to_remove.append(item)
                
                # 从池中移除（但还未关闭）
                for item in to_remove:
                    cls._browser_pool.remove(item)
                
                if to_remove:
                    logger.REMOTE.info(f"  └─ 池大小: {len(cls._browser_pool)}/{MAX_BROWSER_POOL_SIZE}")
            
            # 在锁外执行关闭操作，避免阻塞其他任务
            for item in to_remove:
                await cls._close_browser_item(item)
                    
        except Exception as e:
            logger.REMOTE.error(f"❌ 浏览器池清理出错: {e}")
    
    @classmethod
    def _ensure_pool_lock(cls):
        """线程安全地确保池锁已初始化"""
        if cls._pool_lock is None:
            with cls._pool_lock_init_lock:
                # 双重检查，避免重复创建
                if cls._pool_lock is None:
                    cls._pool_lock = asyncio.Lock()
    
    async def start(self):
        """从浏览器池获取或创建浏览器"""
        if self.context:
            logger.REMOTE.warning("Context已存在，无需重复启动")
            return self
        
        # 线程安全的锁初始化
        BrowserManager._ensure_pool_lock()
        
        cleanup_attempted = False
        
        try:
            while True:
                needs_cleanup = False
                reserve_create = False
                reserved_browser_id: Optional[int] = None
                candidate: Optional[BrowserPoolItem] = None
                
                async with BrowserManager._pool_lock:
                    # 1. 查找可用的健康浏览器（仅做占用标记，实际健康检查在锁外）
                    for item in BrowserManager._browser_pool:
                        if not item.is_using and item.is_healthy:
                            item.is_using = True
                            item.last_used_time = time.time()
                            item.current_task_id = self._task_flag
                            candidate = item
                            break
                    else:
                        # 2. 没有可用浏览器，检查池上限（包含待创建数量）
                        total_in_progress = len(BrowserManager._browser_pool) + BrowserManager._pending_creations
                        if total_in_progress >= MAX_BROWSER_POOL_SIZE:
                            needs_cleanup = True
                        else:
                            reserve_create = True
                            reserved_browser_id = BrowserManager._get_next_browser_id()
                            BrowserManager._pending_creations += 1
                
                # 2. 优先处理候选浏览器的健康检查（锁外执行）
                if candidate:
                    if await self._check_browser_health(candidate):
                        self.context = candidate.context
                        self.playwright = candidate.playwright
                        self._pool_item = candidate
                        logger.REMOTE.info(f"✅ 从浏览器池获取 Browser-{candidate.browser_id}")
                        async with BrowserManager._pool_lock:
                            using_count = sum(1 for i in BrowserManager._browser_pool if i.is_using)
                            total = len(BrowserManager._browser_pool)
                        logger.REMOTE.info(f"  └─ 池状态: {using_count}/{total} 使用中")
                        break
                    else:
                        logger.REMOTE.warning(
                            f"⚠️ Browser-{candidate.browser_id} 健康检查失败，标记为不健康并重新尝试"
                        )
                        async with BrowserManager._pool_lock:
                            candidate.is_using = False
                            candidate.is_healthy = False
                            candidate.current_task_id = None
                            candidate.last_used_time = time.time()
                        await BrowserManager._cleanup_unhealthy_browsers()
                        continue
                
                # 3. 需要创建新浏览器（锁外完成耗时操作）
                if reserve_create and reserved_browser_id is not None:
                    try:
                        item = await self._create_browser_in_pool(reserved_browser_id)
                    except Exception:
                        async with BrowserManager._pool_lock:
                            BrowserManager._pending_creations -= 1
                        raise
                    async with BrowserManager._pool_lock:
                        BrowserManager._browser_pool.append(item)
                        BrowserManager._pending_creations -= 1
                        item.is_using = True
                        item.last_used_time = time.time()
                        item.current_task_id = self._task_flag
                    
                    self.context = item.context
                    self.playwright = item.playwright
                    self._pool_item = item
                    logger.REMOTE.info(f"🆕 创建新浏览器 Browser-{reserved_browser_id}")
                    async with BrowserManager._pool_lock:
                        total = len(BrowserManager._browser_pool)
                    logger.REMOTE.info(f"  └─ 池大小: {total}/{MAX_BROWSER_POOL_SIZE}")
                    break
                
                # 4. 池已满，尝试清理后重试
                if needs_cleanup:
                    if cleanup_attempted:
                        async with BrowserManager._pool_lock:
                            in_use = sum(1 for item in BrowserManager._browser_pool if item.is_using)
                            total = len(BrowserManager._browser_pool) + BrowserManager._pending_creations
                        raise RuntimeError(
                            f"浏览器池已达上限（{MAX_BROWSER_POOL_SIZE}个），"
                            f"当前 {in_use}/{total} 个使用中或正在创建。"
                            f"请稍后重试或增加MAX_BROWSER_POOL_SIZE配置。"
                        )
                    cleanup_attempted = True
                    await BrowserManager._cleanup_unhealthy_browsers()
                    # 清理后重试获取
                    continue
            
            # 启动Trace记录（如果需要）- 在锁外执行
            await self._start_trace_if_needed()
            
            return self
        
        except Exception as e:
            logger.REMOTE.error(f"❌ 从浏览器池获取浏览器失败: {e}")
            await self._cleanup()
            raise
    
    def _generate_task_flag(self) -> str:
        """生成统一的任务标识（trace和video都使用此标识）"""
        from datetime import datetime
        now = datetime.now()
        timestamp = now.strftime("%Y%m%d%H%M%S") + f"{now.microsecond // 1000:03d}"
        
        job_context = _job_context_var.get()
        if job_context is not None:
            job_id, task_batch_id = job_context
            if job_id is not None and task_batch_id is not None:
                return f"job-{job_id}_task-{task_batch_id}_{timestamp}"
            elif job_id is not None:
                return f"job-{job_id}_{timestamp}"
        
        return f"task_{timestamp}"
    
    def _track_page(self, page):
        """
        追踪页面：如果页面不在追踪列表中，则添加并标记
        用于确保所有通过BrowserManager访问的页面都被追踪
        """
        if page not in self._task_pages:
            # 标记页面所属任务
            page._task_owner = self._task_flag
            self._task_pages.append(page)
            logger.REMOTE.debug(f"      └─ 追踪页面（当前任务有 {len(self._task_pages)} 个页面）")
    
    async def new_page(self) -> "BasePage":
        """从当前Context创建新页面（自动包装为BasePage）"""
        from executor.playwright.base_page import BasePage
        
        if not self.context:
            raise RuntimeError("必须先调用 start() 初始化Browser")
        
        page = await self.context.new_page()
        
        # 追踪页面
        self._track_page(page)
        
        return BasePage(page, browser_manager=self)
    
    async def get_page(
        self,
        url: Optional[str] = None,
        title: Optional[str] = None,
        index: Optional[int] = None,
        url_match: Literal["exact", "contains", "startswith", "endswith", "regex"] = "exact",
        title_match: Literal["exact", "contains", "startswith", "endswith", "regex"] = "exact"
    ) -> "BasePage":
        """
        获取页面实例 - 支持多种选择方式（自动包装为BasePage）
        
        Args:
            url: 根据URL查找页面
            title: 根据标题查找页面
            index: 根据索引获取页面（0-based）
            url_match: URL匹配模式
            title_match: 标题匹配模式
        
        Returns:
            BasePage: BasePage实例
        """
        from executor.playwright.base_page import BasePage
        
        if not self.context:
            raise RuntimeError("必须先调用 start() 初始化Browser")
        
        # 如果没有页面，创建新页面
        if len(self.context.pages) == 0:
            page = await self.context.new_page()
            # 追踪页面
            self._track_page(page)
            return BasePage(page, browser_manager=self)
        
        # 如果没有任何过滤条件，返回最后一个页面
        if url is None and title is None and index is None:
            page = self.context.pages[-1]
            # 追踪页面（可能是其他方式创建的）
            self._track_page(page)
            return BasePage(page, browser_manager=self)
        
        # 如果只指定了索引
        if url is None and title is None and index is not None:
            if 0 <= index < len(self.context.pages):
                page = self.context.pages[index]
                # 追踪页面
                self._track_page(page)
                return BasePage(page, browser_manager=self)
            else:
                raise ValueError(f"索引 {index} 超出范围，当前有 {len(self.context.pages)} 个页面")
        
        # 根据URL和/或标题过滤页面
        matched_pages = []
        
        for i, page in enumerate(self.context.pages):
            # 检查URL匹配
            url_matched = True
            if url is not None:
                page_url = page.url
                url_matched = self._match_string(page_url, url, url_match)
            
            # 检查标题匹配
            title_matched = True
            if title is not None:
                page_title = await page.title()
                title_matched = self._match_string(page_title, title, title_match)
            
            # 如果都匹配，添加到结果
            if url_matched and title_matched:
                matched_pages.append((i, page))
        
        if not matched_pages:
            raise ValueError(
                f"未找到匹配的页面 - "
                f"URL: {url} ({url_match}), "
                f"Title: {title} ({title_match})"
            )
        
        # 如果指定了索引，从匹配结果中获取
        if index is not None:
            if 0 <= index < len(matched_pages):
                page_index, page = matched_pages[index]
                # 追踪页面
                self._track_page(page)
                return BasePage(page, browser_manager=self)
            else:
                raise ValueError(
                    f"匹配索引 {index} 超出范围，找到 {len(matched_pages)} 个匹配页面"
                )
        
        # 否则返回第一个匹配的页面
        page_index, page = matched_pages[0]
        # 追踪页面
        self._track_page(page)
        return BasePage(page, browser_manager=self)
    
    def _match_string(
        self, 
        text: str, 
        pattern: str, 
        match_type: Literal["exact", "contains", "startswith", "endswith", "regex"]
    ) -> bool:
        """字符串匹配辅助方法"""
        import re
        
        if match_type == "exact":
            return text == pattern
        elif match_type == "contains":
            return pattern in text
        elif match_type == "startswith":
            return text.startswith(pattern)
        elif match_type == "endswith":
            return text.endswith(pattern)
        elif match_type == "regex":
            try:
                return bool(re.search(pattern, text))
            except re.error as e:
                logger.REMOTE.warning(f"正则表达式错误: {e}")
                return False
    
    def get_trace_path(self) -> Optional[str]:
        """获取trace文件路径"""
        return self._last_context_files.get("trace_path")
    
    def get_video_path(self) -> Optional[str]:
        """获取录屏压缩包路径"""
        return self._last_context_files.get("video_path")
        
    async def _cleanup(self):
        """清理任务资源（只处理当前任务的页面，不关闭浏览器）"""
        # 1. 收集当前任务创建的页面的录屏对象引用
        pages_with_videos = []
        if self.context and self.record_video and self._task_pages:
            for page in self._task_pages:
                try:
                    # 检查页面是否已关闭
                    if not page.is_closed() and page.video:
                        pages_with_videos.append((page, page.video))
                except Exception:
                    # 页面可能已经被关闭或失效
                    pass
        
        # 2. 停止Trace并保存（只保存当前任务的trace）
        if self.context and self.record_trace and self._trace_started:
            trace_path = self._last_context_files.get("trace_path")
            if trace_path:
                try:
                    await self.context.tracing.stop(path=trace_path)
                    logger.REMOTE.debug(f"  └─ Trace已保存: {trace_path}")
                    self._trace_started = False
                except Exception as e:
                    logger.REMOTE.warning(f"  └─ 保存Trace时出错: {e}")
        
        # 3. 处理当前任务的录屏文件
        if pages_with_videos:
            try:
                import shutil
                import tempfile
                
                # 创建临时目录存放录屏文件
                temp_dir = tempfile.mkdtemp(prefix="browser_videos_")
                temp_video_files = []
                
                # 创建异步任务列表
                video_tasks = []
                
                # 收集并创建异步视频处理任务
                for idx, (page, video) in enumerate(pages_with_videos):
                    try:
                        # 使用页面真实序号命名（从1开始）
                        new_filename = f"{self._task_flag}_{idx + 1}.webm"
                        new_path = os.path.join(temp_dir, new_filename)
                        
                        # 1. 先关闭页面（触发视频录制结束）
                        await page.close()
                        
                        # 2. 创建异步视频处理任务（不等待）
                        task = asyncio.create_task(
                            self._process_video_async(video, new_path, new_filename)
                        )
                        video_tasks.append((task, new_path, new_filename))
                        
                    except Exception as e:
                        logger.REMOTE.warning(f"    └─ 处理页面 {idx + 1} 的录屏时出错: {e}")
                
                # 等待所有视频处理任务完成
                for task, new_path, new_filename in video_tasks:
                    try:
                        success = await task
                        if success and os.path.exists(new_path):
                            temp_video_files.append(new_path)
                            logger.REMOTE.debug(f"    └─ 录屏已收集: {new_filename}")
                    except Exception as e:
                        logger.REMOTE.warning(f"    └─ 异步处理录屏 {new_filename} 时出错: {e}")
                
                # 压缩所有录屏到一个zip文件
                if temp_video_files:
                    video_path = os.path.join(self.videos_dir, f"{self._task_flag}.zip")
                    
                    # 使用极致压缩（compresslevel=9）
                    with __import__('zipfile').ZipFile(video_path, 'w', __import__('zipfile').ZIP_DEFLATED, compresslevel=9) as zf:
                        for video_file in temp_video_files:
                            zf.write(video_file, os.path.basename(video_file))
                    
                    self._last_context_files["video_path"] = video_path
                    logger.REMOTE.debug(f"    └─ 录屏已压缩: {len(temp_video_files)} 个文件 -> {self._task_flag}.zip")
                    
                    # 清理临时目录
                    shutil.rmtree(temp_dir)
            except Exception as e:
                logger.REMOTE.warning(f"    └─ 处理录屏文件时出错: {e}")
        
        # 4. 清空任务页面列表
        self._task_pages.clear()
    
    async def _process_video_async(self, video, new_path: str, new_filename: str) -> bool:
        """
        异步处理视频文件（保存并删除原始文件）
        
        Args:
            video: Playwright Video 对象
            new_path: 新的文件路径
            new_filename: 新的文件名
            
        Returns:
            bool: 成功返回true，失败返回false
        """
        try:
            # 等待视频完全写入后再返回
            await video.save_as(new_path)
            
            # 删除原始随机命名的录屏文件
            await video.delete()
            
            return True
        except Exception as e:
            logger.REMOTE.warning(f"    └─ 异步处理录屏 {new_filename} 失败: {e}")
            return False
        
        # 注意：不关闭context、browser、playwright，因为要归还到池
    
    async def stop(self):
        """停止管理器并归还浏览器到池"""
        logger.REMOTE.info("🧹 清理任务资源并归还浏览器...")
        
        try:
            # 1. 处理Trace和录屏
            await self._cleanup()
        except Exception as e:
            logger.REMOTE.error(f"  └─ 清理任务资源时出错: {e}")
        finally:
            # 2. 归还浏览器到池（即使清理失败也要归还）
            if self._pool_item:
                pool_item = self._pool_item
                try:
                    BrowserManager._ensure_pool_lock()
                    if not pool_item.is_healthy:
                        logger.REMOTE.info(
                            f"  └─ 检测到 Browser-{pool_item.browser_id} 不健康，直接移出池并关闭"
                        )
                        async with BrowserManager._pool_lock:
                            if pool_item in BrowserManager._browser_pool:
                                BrowserManager._browser_pool.remove(pool_item)
                        await BrowserManager._close_browser_item(pool_item)
                    else:
                        async with BrowserManager._pool_lock:
                            pool_item.is_using = False
                            pool_item.last_used_time = time.time()
                            pool_item.current_task_id = None
                            using_count = sum(1 for i in BrowserManager._browser_pool if i.is_using)
                            total = len(BrowserManager._browser_pool)
                        logger.REMOTE.info(f"  └─ 🔄 归还浏览器 Browser-{pool_item.browser_id} 到池")
                        logger.REMOTE.info(f"    └─ 池状态: {using_count}/{total} 使用中")
                except Exception as e:
                    logger.REMOTE.error(f"  └─ 归还浏览器时出错: {e}")
                finally:
                    # 清除引用（即使归还失败也要清除）
                    self._pool_item = None
                    self.context = None
                    self.playwright = None
                    self.browser = None
    
    async def __aenter__(self):
        """支持async with语法 - 自动启动浏览器"""
        await self.start()
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """自动清理资源"""
        await self.stop()
    
    @classmethod
    async def shutdown_pool(cls):
        """
        优雅关闭整个浏览器池
        用于程序退出时调用
        """
        logger.REMOTE.info("🛑 关闭浏览器池...")
        
        # 如果没有浏览器池，直接返回
        if not cls._browser_pool:
            logger.REMOTE.info("✅ 浏览器池为空，无需关闭")
            return
        
        # 线程安全的锁初始化
        cls._ensure_pool_lock()
        
        # 先在锁内收集所有项并清空池，然后在锁外关闭
        items_to_close = []
        async with cls._pool_lock:
            items_to_close = list(cls._browser_pool)
            cls._browser_pool.clear()
        
        # 在锁外关闭所有浏览器，避免长时间持有锁
        for item in items_to_close:
            await cls._close_browser_item(item)
        
        logger.REMOTE.info("✅ 浏览器池已关闭")
