"""
选择器增强模块
支持多种选择器类型和链式定位
"""
from typing import Optional, Union, List, Dict, Any, Callable
from playwright.async_api import Page, Locator
from loguru import logger
from enum import Enum
from functools import lru_cache
import time


class SelectorType(Enum):
    """选择器类型"""
    CSS = "css"           # CSS选择器
    XPATH = "xpath"       # XPath
    TEXT = "text"         # 文本匹配
    ROLE = "role"         # ARIA角色
    PLACEHOLDER = "placeholder"  # 占位符
    LABEL = "label"       # 标签
    TEST_ID = "test-id"   # 测试ID
    ALT_TEXT = "alt-text" # 图片alt文本
    TITLE = "title"       # title属性


class SelectorCache:
    """
    选择器缓存

    缓存选择器结果，提升重复定位性能。
    """

    def __init__(self, max_size: int = 100, ttl: float = 5.0):
        """
        初始化缓存

        Args:
            max_size: 最大缓存数量
            ttl: 缓存有效期（秒）
        """
        self.max_size = max_size
        self.ttl = ttl
        self._cache: Dict[str, Dict[str, Any]] = {}
        self._access_order: List[str] = []

    def get(self, key: str) -> Optional[Locator]:
        """
        获取缓存

        Args:
            key: 缓存键

        Returns:
            Locator 或 None
        """
        if key not in self._cache:
            return None

        entry = self._cache[key]

        # 检查是否过期
        if time.time() - entry["timestamp"] > self.ttl:
            self.delete(key)
            return None

        return entry["locator"]

    def set(self, key: str, locator: Locator) -> None:
        """
        设置缓存

        Args:
            key: 缓存键
            locator: Locator 对象
        """
        # LRU 淘汰
        if len(self._cache) >= self.max_size:
            oldest_key = self._access_order.pop(0)
            del self._cache[oldest_key]

        self._cache[key] = {
            "locator": locator,
            "timestamp": time.time(),
        }
        self._access_order.append(key)

    def delete(self, key: str) -> None:
        """删除缓存"""
        if key in self._cache:
            del self._cache[key]
            self._access_order.remove(key)

    def clear(self) -> None:
        """清空缓存"""
        self._cache.clear()
        self._access_order.clear()

    def get_stats(self) -> Dict[str, Any]:
        """获取缓存统计"""
        return {
            "size": len(self._cache),
            "max_size": self.max_size,
            "ttl": self.ttl,
        }


class SmartSelector:
    """
    智能选择器
    支持多种选择器类型和链式定位
    """

    def __init__(self, page: Page, enable_cache: bool = True, cache_ttl: float = 5.0):
        self.page = page
        self._enable_cache = enable_cache
        self._cache = SelectorCache(ttl=cache_ttl) if enable_cache else None

    def _get_cache_key(self, selector: str, selector_type: str = "default") -> str:
        """生成缓存键"""
        return f"{selector_type}:{selector}"

    def _get_cached(self, key: str) -> Optional[Locator]:
        """获取缓存的选择器"""
        if not self._enable_cache or not self._cache:
            return None
        return self._cache.get(key)

    def _set_cache(self, key: str, locator: Locator) -> None:
        """设置选择器缓存"""
        if self._enable_cache and self._cache:
            self._cache.set(key, locator)

    def css(self, selector: str) -> Locator:
        """CSS选择器"""
        key = self._get_cache_key(selector, "css")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.locator(selector)
        self._set_cache(key, locator)
        return locator

    def xpath(self, selector: str) -> Locator:
        """XPath选择器"""
        key = self._get_cache_key(selector, "xpath")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.locator(f"xpath={selector}")
        self._set_cache(key, locator)
        return locator

    def text(self, text: str, exact: bool = False) -> Locator:
        """
        文本选择器

        Args:
            text: 要匹配的文本
            exact: 是否精确匹配
        """
        key = self._get_cache_key(f"{text}:{exact}", "text")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.get_by_text(text, exact=exact)
        self._set_cache(key, locator)
        return locator

    def role(self, role: str, name: Optional[str] = None) -> Locator:
        """
        ARIA角色选择器

        Args:
            role: ARIA角色 (button, link, textbox, etc.)
            name: 可访问名称
        """
        key = self._get_cache_key(f"{role}:{name}", "role")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.get_by_role(role, name=name)
        self._set_cache(key, locator)
        return locator

    def placeholder(self, text: str) -> Locator:
        """占位符选择器"""
        key = self._get_cache_key(text, "placeholder")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.get_by_placeholder(text)
        self._set_cache(key, locator)
        return locator

    def label(self, text: str) -> Locator:
        """标签选择器"""
        key = self._get_cache_key(text, "label")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.get_by_label(text)
        self._set_cache(key, locator)
        return locator

    def test_id(self, test_id: str) -> Locator:
        """测试ID选择器"""
        key = self._get_cache_key(test_id, "test_id")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.get_by_test_id(test_id)
        self._set_cache(key, locator)
        return locator

    def alt_text(self, text: str) -> Locator:
        """图片alt文本选择器"""
        key = self._get_cache_key(text, "alt_text")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.get_by_alt_text(text)
        self._set_cache(key, locator)
        return locator

    def title(self, text: str) -> Locator:
        """title属性选择器"""
        key = self._get_cache_key(text, "title")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.get_by_title(text)
        self._set_cache(key, locator)
        return locator

    def chain(self, *selectors: str) -> Locator:
        """
        链式选择器

        Args:
            *selectors: 选择器链

        Returns:
            Locator: 链式定位结果

        Example:
            selector.chain("div.container", "ul.list", "li.item")
            等价于: page.locator("div.container >> ul.list >> li.item")
        """
        chain_str = " >> ".join(selectors)
        key = self._get_cache_key(chain_str, "chain")
        cached = self._get_cached(key)
        if cached:
            return cached

        locator = self.page.locator(chain_str)
        self._set_cache(key, locator)
        return locator

    def filter(
        self,
        parent: str,
        child: str,
        has_text: Optional[str] = None,
        has_not_text: Optional[str] = None,
    ) -> Locator:
        """
        过滤选择器

        Args:
            parent: 父元素选择器
            child: 子元素选择器
            has_text: 必须包含的文本
            has_not_text: 不能包含的文本
        """
        locator = self.page.locator(parent).locator(child)

        if has_text:
            locator = locator.filter(has_text=has_text)
        if has_not_text:
            locator = locator.filter(has_not_text=has_not_text)

        return locator

    def nth(self, selector: str, index: int) -> Locator:
        """
        第N个元素

        Args:
            selector: 选择器
            index: 索引（从0开始）
        """
        return self.page.locator(selector).nth(index)

    def first(self, selector: str) -> Locator:
        """第一个匹配元素"""
        return self.page.locator(selector).first

    def last(self, selector: str) -> Locator:
        """最后一个匹配元素"""
        return self.page.locator(selector).last

    async def smart_find(self, hint: str) -> Optional[Locator]:
        """
        智能查找元素
        尝试多种方式查找元素

        优化策略顺序：
        1. 精确文本匹配（最快）
        2. 模糊文本匹配
        3. 角色匹配
        4. 占位符/标签
        5. CSS选择器
        6. XPath（最慢）

        Args:
            hint: 查找提示（文本、ID、class等）
        """
        # 先检查缓存
        cache_key = self._get_cache_key(hint, "smart_find")
        cached = self._get_cached(cache_key)
        if cached:
            # 验证缓存的 locator 是否仍然有效
            try:
                count = await cached.count()
                if count > 0:
                    logger.debug(f"智能查找命中缓存: hint={hint}")
                    return cached.first
            except:
                pass

        strategies = [
            # 1. 精确文本匹配（最快）
            lambda: self.text(hint, exact=True),
            # 2. 模糊文本匹配
            lambda: self.text(hint),
            # 3. 角色匹配
            lambda: self.role("button", name=hint),
            lambda: self.role("link", name=hint),
            # 4. 占位符
            lambda: self.placeholder(hint),
            # 5. 标签
            lambda: self.label(hint),
            # 6. CSS选择器
            lambda: self.css(hint),
            # 7. XPath（最慢）
            lambda: self.xpath(f"//*[contains(text(), '{hint}')]"),
        ]

        for strategy in strategies:
            try:
                locator = strategy()
                count = await locator.count()
                if count > 0:
                    logger.debug(f"智能查找成功: hint={hint}")
                    self._set_cache(cache_key, locator)
                    return locator.first
            except:
                continue

        logger.warning(f"智能查找失败: hint={hint}")
        return None

    async def all(self, selector: str) -> List[Locator]:
        """获取所有匹配的元素"""
        locator = self.page.locator(selector)
        count = await locator.count()
        return [locator.nth(i) for i in range(count)]

    async def count(self, selector: str) -> int:
        """统计匹配元素数量"""
        return await self.page.locator(selector).count()

    def clear_cache(self) -> None:
        """清空选择器缓存"""
        if self._cache:
            self._cache.clear()
            logger.debug("选择器缓存已清空")

    def get_cache_stats(self) -> Optional[Dict[str, Any]]:
        """获取缓存统计信息"""
        if self._cache:
            return self._cache.get_stats()
        return None

    # ==================== 批量查找优化 ====================

    async def find_all_batch(
        self,
        selectors: List[str],
        timeout: float = 10.0,
    ) -> Dict[str, Optional[Locator]]:
        """
        批量查找元素

        并行查找多个选择器，提升效率。

        Args:
            selectors: 选择器列表
            timeout: 总超时时间

        Returns:
            Dict[str, Optional[Locator]]: 选择器 -> Locator 映射
        """
        import asyncio

        results: Dict[str, Optional[Locator]] = {}

        async def find_one(selector: str) -> tuple[str, Optional[Locator]]:
            try:
                locator = self.page.locator(selector)
                count = await locator.count()
                return (selector, locator.first if count > 0 else None)
            except:
                return (selector, None)

        # 并行执行
        tasks = [find_one(s) for s in selectors]
        outcomes = await asyncio.gather(*tasks)

        for selector, locator in outcomes:
            results[selector] = locator

        return results

    async def wait_for_any(
        self,
        selectors: List[str],
        timeout: float = 30.0,
    ) -> Optional[str]:
        """
        等待任意一个选择器匹配

        返回第一个匹配的选择器。

        Args:
            selectors: 选择器列表
            timeout: 超时时间

        Returns:
            str: 匹配的选择器，如果超时则返回 None
        """
        import asyncio

        async def wait_one(selector: str) -> str:
            locator = self.page.locator(selector)
            await locator.wait_for(state="attached", timeout=timeout * 1000)
            return selector

        try:
            done, pending = await asyncio.wait(
                [asyncio.create_task(wait_one(s)) for s in selectors],
                timeout=timeout,
                return_when=asyncio.FIRST_COMPLETED,
            )

            # 取消未完成的任务
            for task in pending:
                task.cancel()

            if done:
                return done.pop().result()
        except:
            pass

        return None
