"""
无限滚动处理器
自动处理无限滚动加载，支持懒加载页面采集
"""
import asyncio
from dataclasses import dataclass, field
from typing import Optional, Callable, Literal
from playwright.async_api import Page
from loguru import logger


@dataclass
class InfiniteScrollConfig:
    """无限滚动配置"""
    direction: Literal["down", "up", "left", "right"] = "down"
    scroll_distance: int = 500
    scroll_interval: float = 0.3  # 秒
    max_scrolls: int = 100
    stop_on_no_change: bool = True
    no_change_threshold: int = 3
    on_before_scroll: Optional[Callable] = None
    on_after_scroll: Optional[Callable] = None


@dataclass
class InfiniteScrollResult:
    """无限滚动结果"""
    total_scrolls: int = 0
    total_distance: int = 0
    reached_max: bool = False
    stopped_by_no_change: bool = False
    duration: float = 0.0  # 毫秒


class InfiniteScrollHandler:
    """
    无限滚动处理器
    自动处理无限滚动加载页面
    """

    def __init__(self, page: Page, config: Optional[InfiniteScrollConfig] = None):
        self.page = page
        self.config = config or InfiniteScrollConfig()
        self._scroll_count = 0
        self._no_change_count = 0
        self._last_content_height = 0

    async def scroll(self) -> InfiniteScrollResult:
        """
        执行无限滚动

        Returns:
            InfiniteScrollResult: 滚动结果
        """
        import time
        start_time = time.time()

        self._scroll_count = 0
        self._no_change_count = 0
        self._last_content_height = 0
        total_distance = 0
        reached_max = False
        stopped_by_no_change = False

        # 初始化内容高度
        self._last_content_height = await self._get_content_height()

        while self._scroll_count < self.config.max_scrolls:
            # 滚动前回调
            if self.config.on_before_scroll:
                if asyncio.iscoroutinefunction(self.config.on_before_scroll):
                    await self.config.on_before_scroll()
                else:
                    self.config.on_before_scroll()

            # 执行滚动
            scrolled = await self._perform_scroll()
            total_distance += scrolled
            self._scroll_count += 1

            # 等待内容加载
            await self._wait_for_content()

            # 滚动后回调
            if self.config.on_after_scroll:
                if asyncio.iscoroutinefunction(self.config.on_after_scroll):
                    await self.config.on_after_scroll()
                else:
                    self.config.on_after_scroll()

            # 检查是否有新内容
            if self.config.stop_on_no_change:
                current_height = await self._get_content_height()
                if current_height == self._last_content_height:
                    self._no_change_count += 1
                    if self._no_change_count >= self.config.no_change_threshold:
                        stopped_by_no_change = True
                        break
                else:
                    self._no_change_count = 0
                    self._last_content_height = current_height

            # 等待间隔
            await asyncio.sleep(self.config.scroll_interval)

        if self._scroll_count >= self.config.max_scrolls:
            reached_max = True

        duration = (time.time() - start_time) * 1000

        logger.debug(
            f"无限滚动完成: scrolls={self._scroll_count}, "
            f"distance={total_distance}, duration={duration:.0f}ms"
        )

        return InfiniteScrollResult(
            total_scrolls=self._scroll_count,
            total_distance=total_distance,
            reached_max=reached_max,
            stopped_by_no_change=stopped_by_no_change,
            duration=duration,
        )

    async def scroll_until_end(
        self,
        max_scrolls: int = 100,
        scroll_delay: float = 0.3,
        stop_condition: Optional[Callable[[], bool]] = None,
    ) -> InfiniteScrollResult:
        """
        滚动到页面底部

        Args:
            max_scrolls: 最大滚动次数
            scroll_delay: 滚动间隔（秒）
            stop_condition: 停止条件函数

        Returns:
            InfiniteScrollResult: 滚动结果
        """
        config = InfiniteScrollConfig(
            max_scrolls=max_scrolls,
            scroll_interval=scroll_delay,
        )
        if stop_condition:
            config.on_before_scroll = lambda: None

        old_config = self.config
        self.config = config
        result = await self.scroll()
        self.config = old_config
        return result

    async def scroll_and_collect(
        self,
        selector: str,
        max_items: int = 1000,
        max_scrolls: int = 100,
    ) -> list:
        """
        滚动并收集元素

        Args:
            selector: 元素选择器
            max_items: 最大收集数量
            max_scrolls: 最大滚动次数

        Returns:
            list: 收集的元素文本列表
        """
        collected = []
        seen = set()

        async def collect():
            elements = self.page.locator(selector)
            count = await elements.count()
            for i in range(count):
                if len(collected) >= max_items:
                    return True  # 停止滚动
                text = await elements.nth(i).text_content()
                if text and text not in seen:
                    seen.add(text)
                    collected.append(text)
            return False

        config = InfiniteScrollConfig(
            max_scrolls=max_scrolls,
            on_after_scroll=collect,
        )

        old_config = self.config
        self.config = config
        await self.scroll()
        self.config = old_config

        return collected

    async def _perform_scroll(self) -> int:
        """执行单次滚动"""
        direction = self.config.direction
        distance = self.config.scroll_distance

        scripts = {
            "down": f"window.scrollBy(0, {distance})",
            "up": f"window.scrollBy(0, -{distance})",
            "left": f"window.scrollBy(-{distance}, 0)",
            "right": f"window.scrollBy({distance}, 0)",
        }

        await self.page.evaluate(scripts[direction])
        return distance

    async def _get_content_height(self) -> int:
        """获取内容高度"""
        return await self.page.evaluate("""() => {
            const body = document.body;
            const html = document.documentElement;

            return Math.max(
                body.scrollHeight,
                body.offsetHeight,
                html.clientHeight,
                html.scrollHeight,
                html.offsetHeight
            );
        }""")

    async def _wait_for_content(self):
        """等待内容加载"""
        try:
            await self.page.wait_for_load_state("networkidle", timeout=2000)
        except Exception:
            pass

    async def scroll_to_bottom(self):
        """滚动到页面底部"""
        await self.page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
        logger.debug("滚动到页面底部")

    async def scroll_to_top(self):
        """滚动到页面顶部"""
        await self.page.evaluate("window.scrollTo(0, 0)")
        logger.debug("滚动到页面顶部")

    async def scroll_to_element(self, selector: str):
        """滚动到指定元素"""
        element = self.page.locator(selector)
        await element.scroll_into_view_if_needed()
        logger.debug(f"滚动到元素: {selector}")

    async def is_at_bottom(self) -> bool:
        """检测是否到达底部"""
        return await self.page.evaluate("""() => {
            const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
            const scrollHeight = document.documentElement.scrollHeight;
            const clientHeight = document.documentElement.clientHeight;

            return scrollTop + clientHeight >= scrollHeight - 100;
        }""")

    async def is_at_top(self) -> bool:
        """检测是否到达顶部"""
        return await self.page.evaluate("window.pageYOffset === 0")

    async def get_scroll_position(self) -> dict:
        """获取当前滚动位置"""
        return await self.page.evaluate("""() => ({
            x: window.pageXOffset || document.documentElement.scrollLeft,
            y: window.pageYOffset || document.documentElement.scrollTop,
        })""")

    async def scroll_to(self, x: int, y: int):
        """滚动到指定位置"""
        await self.page.evaluate(f"window.scrollTo({x}, {y})")

    async def smooth_scroll_to(self, x: int, y: int):
        """平滑滚动到指定位置"""
        await self.page.evaluate(
            "window.scrollTo({ left: %d, top: %d, behavior: 'smooth' })" % (x, y)
        )

    async def get_scroll_progress(self) -> float:
        """获取滚动进度（0-1）"""
        return await self.page.evaluate("""() => {
            const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
            const scrollHeight = document.documentElement.scrollHeight;
            const clientHeight = document.documentElement.clientHeight;

            return scrollTop / (scrollHeight - clientHeight);
        }""")


def create_infinite_scroll_handler(
    page: Page,
    config: Optional[InfiniteScrollConfig] = None
) -> InfiniteScrollHandler:
    """创建无限滚动处理器实例"""
    return InfiniteScrollHandler(page, config)
