"""
动态渲染内容提取器
处理 AJAX 加载、懒加载、动态渲染等内容
"""
from typing import Optional, List, Dict, Any, Callable
from playwright.async_api import Page
from loguru import logger

from .basic import DataExtractor


class DynamicContentExtractor:
    """
    动态渲染内容提取器
    处理 AJAX 加载、懒加载、动态渲染等内容
    """

    def __init__(self, page: Page):
        self.page = page
        self.extractor = DataExtractor(page)

    async def wait_and_extract(
        self,
        selector: str,
        wait_type: str = "visible",
        timeout: int = 10000,
    ) -> Optional[str]:
        """
        等待元素出现后提取

        Args:
            selector: 选择器
            wait_type: 等待类型 visible/attached/hidden
            timeout: 超时时间（毫秒）

        Returns:
            元素文本或None
        """
        try:
            element = self.page.locator(selector)
            await element.wait_for(state=wait_type, timeout=timeout)
            text = await element.text_content()
            return text.strip() if text else None
        except:
            return None

    async def extract_on_scroll(
        self,
        selector: str,
        scroll_pause: float = 0.5,
        max_attempts: int = 5,
    ) -> List[str]:
        """
        滚动触发懒加载后提取 - 批量优化版

        Args:
            selector: 选择器
            scroll_pause: 滚动暂停时间
            max_attempts: 最大尝试次数

        Returns:
            元素文本列表
        """
        for _ in range(max_attempts):
            count_before = await self.page.locator(selector).count()

            # 滚动到底部
            await self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
            await self.page.wait_for_timeout(int(scroll_pause * 1000))

            count_after = await self.page.locator(selector).count()

            # 如果没有新元素，结束
            if count_after == count_before:
                break

        # 批量提取所有元素文本
        try:
            texts = await self.page.locator(selector).evaluate_all(
                "elements => elements.map(el => el.textContent?.trim() || '').filter(t => t)"
            )
            return texts
        except:
            return []

    async def extract_after_network_idle(
        self,
        extract_func: Callable,
        timeout: int = 30000,
    ) -> Any:
        """
        等待网络空闲后提取

        Args:
            extract_func: 提取函数
            timeout: 超时时间

        Returns:
            提取结果
        """
        await self.page.wait_for_load_state("networkidle", timeout=timeout)
        return await extract_func(self.page)

    async def extract_with_retry(
        self,
        selector: str,
        retries: int = 3,
        retry_delay: float = 1.0,
        extract_type: str = "text",
    ) -> Optional[Any]:
        """
        重试提取

        Args:
            selector: 选择器
            retries: 重试次数
            retry_delay: 重试间隔
            extract_type: 提取类型 text/attribute/all

        Returns:
            提取结果
        """
        for attempt in range(retries):
            try:
                element = self.page.locator(selector)

                if extract_type == "text":
                    result = await element.text_content()
                    return result.strip() if result else None
                elif extract_type == "attribute":
                    return await element.get_attribute(extract_type)
                else:
                    # 提取所有信息
                    return {
                        "text": await element.text_content(),
                        "html": await element.inner_html(),
                        "attributes": await element.evaluate("el => Object.values(el.attributes).map(a => [a.name, a.value])"),
                    }
            except Exception as e:
                if attempt < retries - 1:
                    await self.page.wait_for_timeout(int(retry_delay * 1000))
                else:
                    logger.warning(f"提取失败: {selector}, error={e}")
                    return None

    async def extract_shadow_dom(
        self,
        host_selector: str,
        shadow_selector: str,
    ) -> Optional[str]:
        """
        提取 Shadow DOM 内容

        Args:
            host_selector: Shadow DOM 宿主选择器
            shadow_selector: Shadow DOM 内部选择器

        Returns:
            元素文本或None
        """
        try:
            text = await self.page.evaluate(f"""
                () => {{
                    const host = document.querySelector('{host_selector}');
                    if (!host || !host.shadowRoot) return null;
                    const element = host.shadowRoot.querySelector('{shadow_selector}');
                    return element ? element.textContent.trim() : null;
                }}
            """)
            return text
        except:
            return None

    async def extract_iframe_content(
        self,
        iframe_selector: str,
        inner_selector: str,
    ) -> Optional[str]:
        """
        提取 iframe 内容

        Args:
            iframe_selector: iframe选择器
            inner_selector: iframe内部选择器

        Returns:
            元素文本或None
        """
        try:
            frame = self.page.frame_locator(iframe_selector)
            element = frame.locator(inner_selector)
            text = await element.text_content()
            return text.strip() if text else None
        except:
            return None

    async def extract_vue_reactive_data(
        self,
        selector: str,
        data_key: str,
    ) -> Optional[Any]:
        """
        提取 Vue 组件响应式数据

        Args:
            selector: Vue组件根元素选择器
            data_key: 数据键名

        Returns:
            数据值
        """
        try:
            value = await self.page.evaluate(f"""
                () => {{
                    const el = document.querySelector('{selector}');
                    if (!el || !el.__vue__) return null;
                    return el.__vue__.{data_key};
                }}
            """)
            return value
        except:
            return None

    async def extract_react_state(
        self,
        selector: str,
    ) -> Optional[Dict[str, Any]]:
        """
        提取 React 组件状态

        Args:
            selector: React组件根元素选择器

        Returns:
            状态对象
        """
        try:
            state = await self.page.evaluate(f"""
                () => {{
                    const el = document.querySelector('{selector}');
                    if (!el) return null;

                    // 查找 React Fiber
                    const keys = Object.keys(el);
                    const reactKey = keys.find(k => k.startsWith('__reactFiber') || k.startsWith('__reactInternalInstance'));

                    if (!reactKey) return null;

                    const fiber = el[reactKey];
                    return fiber?.memoizedState || fiber?.stateNode?.state || null;
                }}
            """)
            return state
        except:
            return None

    async def wait_for_ajax(
        self,
        timeout: int = 30000,
        check_interval: int = 100,
    ) -> bool:
        """
        等待 AJAX 请求完成

        Args:
            timeout: 超时时间
            check_interval: 检查间隔

        Returns:
            是否成功
        """
        try:
            await self.page.wait_for_function(
                """() => {
                    return typeof jQuery === 'undefined' || jQuery.active === 0;
                }""",
                timeout=timeout,
            )
            return True
        except:
            return False

    async def extract_lazy_images(
        self,
        img_selector: str = "img[data-src], img[data-lazy], img[loading='lazy']",
        scroll_pause: float = 0.5,
    ) -> List[Dict[str, str]]:
        """
        提取懒加载图片的真实 URL - 批量优化版

        Args:
            img_selector: 懒加载图片选择器
            scroll_pause: 滚动暂停时间

        Returns:
            图片信息列表
        """
        # 滚动触发加载
        for _ in range(10):
            await self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
            await self.page.wait_for_timeout(int(scroll_pause * 1000))

        # 批量提取图片信息
        try:
            images = await self.page.locator(img_selector).evaluate_all(
                """elements => elements.map(img => ({
                    src: img.getAttribute('data-src') || img.getAttribute('data-lazy') || img.src,
                    original_src: img.src,
                    alt: img.alt || '',
                }))"""
            )
            return images
        except:
            return []
