"""
快捷操作模块 - 提供常用实用功能

包含：
- 页面滚动增强（滚动到底部、滚动加载更多）
- 键盘快捷操作（全选、复制、粘贴）
- 鼠标操作增强（拖拽、悬停链）
- 等待增强（等待文本变化、等待元素消失）
- 截图增强（带时间戳、自动命名）
- 自动重试机制
- iframe 快捷操作
"""
import asyncio
import time
from typing import Optional, List, Callable, Any, Union, TYPE_CHECKING
from dataclasses import dataclass
from loguru import logger

if TYPE_CHECKING:
    from playwright.async_api import Page, Locator, Frame


@dataclass
class ScrollResult:
    """滚动结果"""
    success: bool
    scrolled_distance: int
    reached_bottom: bool
    new_content_loaded: bool


@dataclass
class RetryResult:
    """重试结果"""
    success: bool
    attempts: int
    total_time: float
    last_error: Optional[str] = None


class EnhancedQuickActions:
    """
    增强快捷操作类

    提供高级浏览器操作快捷方法，包括：
    - 页面滚动增强（滚动到底部、滚动加载更多）
    - 键盘快捷操作（全选、复制、粘贴）
    - 鼠标操作增强（拖拽、悬停链）
    - 等待增强（等待文本变化、等待元素消失）
    - 截图增强（带时间戳、自动命名）
    - 自动重试机制
    - iframe 快捷操作

    与 quick.QuickActions 的区别：
    - QuickActions: 提供基础快捷操作（导航、点击、输入等）
    - EnhancedQuickActions: 提供增强操作（滚动加载、键盘快捷键、重试机制等）
    """

    def __init__(self, page: "Page"):
        self._page = page

    # ==================== 页面滚动 ====================

    async def scroll_to_bottom(
        self,
        max_scrolls: int = 50,
        scroll_delay: float = 0.5,
        stop_if_no_change: int = 3,
    ) -> ScrollResult:
        """
        滚动到页面底部

        适用于需要加载更多内容的页面。

        Args:
            max_scrolls: 最大滚动次数
            scroll_delay: 滚动间隔（秒）
            stop_if_no_change: 连续N次无变化则停止

        Returns:
            ScrollResult: 滚动结果
        """
        last_height = 0
        no_change_count = 0
        total_distance = 0

        for i in range(max_scrolls):
            # 获取当前高度
            current_height = await self._page.evaluate("document.body.scrollHeight")

            # 滚动到底部
            await self._page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
            total_distance += current_height - last_height if i > 0 else 0

            # 等待加载
            await asyncio.sleep(scroll_delay)

            # 检查是否有新内容
            new_height = await self._page.evaluate("document.body.scrollHeight")

            if new_height == current_height:
                no_change_count += 1
                if no_change_count >= stop_if_no_change:
                    logger.info(f"滚动完成: {i+1}次, 到达底部")
                    return ScrollResult(
                        success=True,
                        scrolled_distance=total_distance,
                        reached_bottom=True,
                        new_content_loaded=i > 0,
                    )
            else:
                no_change_count = 0

            last_height = current_height

        logger.warning(f"达到最大滚动次数: {max_scrolls}")
        return ScrollResult(
            success=True,
            scrolled_distance=total_distance,
            reached_bottom=False,
            new_content_loaded=True,
        )

    async def scroll_to_top(self, smooth: bool = True) -> None:
        """
        滚动到页面顶部

        Args:
            smooth: 是否平滑滚动
        """
        if smooth:
            await self._page.evaluate("""
                window.scrollTo({
                    top: 0,
                    behavior: 'smooth'
                })
            """)
        else:
            await self._page.evaluate("window.scrollTo(0, 0)")

    async def scroll_into_view(
        self,
        selector: str,
        timeout: float = 10.0,
    ) -> bool:
        """
        滚动到元素可见

        Args:
            selector: 元素选择器
            timeout: 超时时间

        Returns:
            bool: 是否成功
        """
        try:
            locator = self._page.locator(selector)
            await locator.scroll_into_view_if_needed(timeout=timeout * 1000)
            return True
        except Exception as e:
            logger.warning(f"滚动到元素失败: {e}")
            return False

    async def scroll_by(self, x: int = 0, y: int = 500) -> None:
        """
        按距离滚动

        Args:
            x: 水平滚动距离
            y: 垂直滚动距离
        """
        await self._page.evaluate(f"window.scrollBy({x}, {y})")

    # ==================== 键盘快捷操作 ====================

    async def select_all(self) -> None:
        """全选 (Ctrl+A)"""
        await self._page.keyboard.press("Control+A")

    async def copy(self) -> None:
        """复制 (Ctrl+C)"""
        await self._page.keyboard.press("Control+C")

    async def paste(self) -> None:
        """粘贴 (Ctrl+V)"""
        await self._page.keyboard.press("Control+V")

    async def cut(self) -> None:
        """剪切 (Ctrl+X)"""
        await self._page.keyboard.press("Control+X")

    async def undo(self) -> None:
        """撤销 (Ctrl+Z)"""
        await self._page.keyboard.press("Control+Z")

    async def redo(self) -> None:
        """重做 (Ctrl+Y)"""
        await self._page.keyboard.press("Control+Y")

    async def press_enter(self) -> None:
        """按回车键"""
        await self._page.keyboard.press("Enter")

    async def press_escape(self) -> None:
        """按ESC键"""
        await self._page.keyboard.press("Escape")

    async def press_tab(self, times: int = 1) -> None:
        """按Tab键

        Args:
            times: 按几次
        """
        for _ in range(times):
            await self._page.keyboard.press("Tab")

    async def press_backspace(self, times: int = 1) -> None:
        """按退格键

        Args:
            times: 按几次
        """
        for _ in range(times):
            await self._page.keyboard.press("Backspace")

    async def clear_input(self, selector: str) -> None:
        """
        清空输入框（使用 Ctrl+A + Delete）

        Args:
            selector: 输入框选择器
        """
        locator = self._page.locator(selector)
        await locator.click()
        await self.select_all()
        await self._page.keyboard.press("Delete")

    # ==================== 鼠标操作 ====================

    async def drag_and_drop(
        self,
        source_selector: str,
        target_selector: str,
        steps: int = 10,
    ) -> bool:
        """
        拖拽元素

        Args:
            source_selector: 源元素选择器
            target_selector: 目标元素选择器
            steps: 拖拽步数

        Returns:
            bool: 是否成功
        """
        try:
            source = self._page.locator(source_selector)
            target = self._page.locator(target_selector)

            # 获取元素位置
            source_box = await source.bounding_box()
            target_box = await target.bounding_box()

            if not source_box or not target_box:
                return False

            # 计算中心点
            source_x = source_box["x"] + source_box["width"] / 2
            source_y = source_box["y"] + source_box["height"] / 2
            target_x = target_box["x"] + target_box["width"] / 2
            target_y = target_box["y"] + target_box["height"] / 2

            # 执行拖拽
            await self._page.mouse.move(source_x, source_y)
            await self._page.mouse.down()

            # 分步移动
            step_x = (target_x - source_x) / steps
            step_y = (target_y - source_y) / steps

            for i in range(1, steps + 1):
                await self._page.mouse.move(
                    source_x + step_x * i,
                    source_y + step_y * i
                )
                await asyncio.sleep(0.01)

            await self._page.mouse.up()
            return True
        except Exception as e:
            logger.error(f"拖拽失败: {e}")
            return False

    async def hover_sequence(self, selectors: List[str], delay: float = 0.1) -> None:
        """
        依次悬停多个元素

        Args:
            selectors: 选择器列表
            delay: 悬停间隔
        """
        for selector in selectors:
            await self._page.locator(selector).hover()
            await asyncio.sleep(delay)

    # ==================== 等待增强 ====================

    async def wait_for_text_change(
        self,
        selector: str,
        initial_text: str,
        timeout: float = 30.0,
        poll_interval: float = 0.2,
    ) -> bool:
        """
        等待文本变化

        Args:
            selector: 元素选择器
            initial_text: 初始文本
            timeout: 超时时间
            poll_interval: 轮询间隔

        Returns:
            bool: 是否成功
        """
        start_time = time.time()
        locator = self._page.locator(selector)

        while time.time() - start_time < timeout:
            try:
                current_text = await locator.text_content() or ""
                if current_text != initial_text:
                    return True
            except:
                pass
            await asyncio.sleep(poll_interval)

        return False

    async def wait_for_element_count(
        self,
        selector: str,
        expected_count: int,
        timeout: float = 30.0,
    ) -> bool:
        """
        等待元素数量达到预期

        Args:
            selector: 选择器
            expected_count: 预期数量
            timeout: 超时时间

        Returns:
            bool: 是否成功
        """
        try:
            locator = self._page.locator(selector)
            start_time = time.time()

            while time.time() - start_time < timeout:
                count = await locator.count()
                if count == expected_count:
                    return True
                await asyncio.sleep(0.1)

            return False
        except:
            return False

    async def wait_for_url_change(
        self,
        initial_url: str,
        timeout: float = 30.0,
    ) -> bool:
        """
        等待URL变化

        Args:
            initial_url: 初始URL
            timeout: 超时时间

        Returns:
            bool: 是否成功
        """
        start_time = time.time()

        while time.time() - start_time < timeout:
            if self._page.url != initial_url:
                return True
            await asyncio.sleep(0.1)

        return False

    # ==================== 截图增强 ====================

    async def screenshot_with_timestamp(
        self,
        directory: str = "./screenshots",
        prefix: str = "screenshot",
    ) -> str:
        """
        带时间戳的截图

        Args:
            directory: 保存目录
            prefix: 文件名前缀

        Returns:
            str: 文件路径
        """
        import os
        from datetime import datetime

        os.makedirs(directory, exist_ok=True)

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"{prefix}_{timestamp}.png"
        filepath = os.path.join(directory, filename)

        await self._page.screenshot(path=filepath)
        logger.info(f"截图已保存: {filepath}")
        return filepath

    async def element_screenshot(
        self,
        selector: str,
        path: str,
    ) -> bool:
        """
        元素截图

        Args:
            selector: 元素选择器
            path: 保存路径

        Returns:
            bool: 是否成功
        """
        try:
            locator = self._page.locator(selector)
            await locator.screenshot(path=path)
            return True
        except Exception as e:
            logger.error(f"元素截图失败: {e}")
            return False

    # ==================== 自动重试 ====================

    async def retry_action(
        self,
        action: Callable[[], Any],
        max_attempts: int = 3,
        delay: float = 1.0,
        exceptions: tuple = (Exception,),
    ) -> RetryResult:
        """
        自动重试操作

        Args:
            action: 要执行的操作（异步函数）
            max_attempts: 最大尝试次数
            delay: 重试间隔
            exceptions: 需要重试的异常类型

        Returns:
            RetryResult: 重试结果
        """
        start_time = time.time()
        last_error = None

        for attempt in range(1, max_attempts + 1):
            try:
                result = action()
                if asyncio.iscoroutine(result):
                    await result

                return RetryResult(
                    success=True,
                    attempts=attempt,
                    total_time=time.time() - start_time,
                )
            except exceptions as e:
                last_error = str(e)
                logger.warning(f"操作失败 (尝试 {attempt}/{max_attempts}): {e}")
                if attempt < max_attempts:
                    await asyncio.sleep(delay)

        return RetryResult(
            success=False,
            attempts=max_attempts,
            total_time=time.time() - start_time,
            last_error=last_error,
        )

    # ==================== iframe 操作 ====================

    async def get_frames(self) -> List["Frame"]:
        """
        获取所有 iframe

        Returns:
            List[Frame]: iframe 列表
        """
        return self._page.frames

    async def find_in_frames(
        self,
        selector: str,
        timeout: float = 10.0,
    ) -> Optional["Locator"]:
        """
        在所有 iframe 中查找元素

        Args:
            selector: 元素选择器
            timeout: 超时时间

        Returns:
            Locator 或 None
        """
        # 先在主页面查找
        try:
            locator = self._page.locator(selector)
            if await locator.count() > 0:
                return locator
        except:
            pass

        # 在 iframe 中查找
        for frame in self._page.frames:
            try:
                locator = frame.locator(selector)
                if await locator.count() > 0:
                    return locator
            except:
                continue

        return None

    async def switch_to_frame(self, index: int = 0) -> Optional["Frame"]:
        """
        切换到 iframe

        Args:
            index: iframe 索引

        Returns:
            Frame 或 None
        """
        frames = self._page.frames
        if index < len(frames):
            return frames[index]
        return None

    # ==================== 页面状态 ====================

    async def get_page_state(self) -> dict:
        """
        获取页面状态

        Returns:
            dict: 页面状态信息
        """
        return {
            "url": self._page.url,
            "title": await self._page.title(),
            "viewport_size": self._page.viewport_size,
            "scroll_position": await self._page.evaluate(
                "() => ({ x: window.scrollX, y: window.scrollY })"
            ),
            "document_height": await self._page.evaluate("document.body.scrollHeight"),
        }

    async def wait_for_page_idle(
        self,
        timeout: float = 30.0,
        idle_time: float = 0.5,
    ) -> bool:
        """
        等待页面空闲（无网络请求）

        Args:
            timeout: 超时时间
            idle_time: 空闲时间

        Returns:
            bool: 是否成功
        """
        try:
            await self._page.wait_for_load_state("networkidle", timeout=timeout * 1000)
            return True
        except:
            return False
