"""
拖拽操作助手
支持各种拖拽场景
"""
import asyncio
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
from playwright.async_api import Page, Locator
from loguru import logger


@dataclass
class DragConfig:
    """拖拽配置"""
    source: str
    target: str
    steps: int = 10
    delay: float = 0.05  # 秒
    use_html5_drag: bool = False


@dataclass
class DragResult:
    """拖拽结果"""
    success: bool
    source_position: Optional[Dict[str, float]] = None
    target_position: Optional[Dict[str, float]] = None
    error: Optional[str] = None


class DragDropHelper:
    """
    拖拽操作助手
    支持各种拖拽场景
    """

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

    async def drag(
        self,
        source: str,
        target: str,
        steps: int = 10,
        delay: float = 0.05,
        use_html5_drag: bool = False,
    ) -> DragResult:
        """
        执行拖拽操作

        Args:
            source: 源元素选择器
            target: 目标元素选择器
            steps: 移动步数
            delay: 步间延迟（秒）
            use_html5_drag: 是否使用 HTML5 拖放 API

        Returns:
            DragResult: 拖拽结果
        """
        try:
            source_element = self.page.locator(source)
            target_element = self.page.locator(target)

            # 确保元素可见
            await source_element.wait_for(state="visible")
            await target_element.wait_for(state="visible")

            if use_html5_drag:
                return await self._html5_drag(source_element, target_element)
            else:
                return await self._mouse_drag(
                    source_element, target_element, steps, delay
                )
        except Exception as e:
            return DragResult(
                success=False,
                error=str(e),
            )

    async def _mouse_drag(
        self,
        source: Locator,
        target: Locator,
        steps: int,
        delay: float,
    ) -> DragResult:
        """使用鼠标拖拽"""
        source_box = await source.bounding_box()
        target_box = await target.bounding_box()

        if not source_box or not target_box:
            return DragResult(
                success=False,
                error="无法获取元素位置",
            )

        source_center = {
            "x": source_box["x"] + source_box["width"] / 2,
            "y": source_box["y"] + source_box["height"] / 2,
        }

        target_center = {
            "x": target_box["x"] + target_box["width"] / 2,
            "y": target_box["y"] + target_box["height"] / 2,
        }

        # 移动到源元素
        await self.page.mouse.move(source_center["x"], source_center["y"])

        # 按下鼠标
        await self.page.mouse.down()

        # 逐步移动到目标位置
        for i in range(1, steps + 1):
            progress = i / steps
            x = source_center["x"] + (target_center["x"] - source_center["x"]) * progress
            y = source_center["y"] + (target_center["y"] - source_center["y"]) * progress

            await self.page.mouse.move(x, y)
            await asyncio.sleep(delay)

        # 释放鼠标
        await self.page.mouse.up()

        logger.debug(
            f"鼠标拖拽完成: ({source_center['x']:.0f}, {source_center['y']:.0f}) -> "
            f"({target_center['x']:.0f}, {target_center['y']:.0f})"
        )

        return DragResult(
            success=True,
            source_position=source_center,
            target_position=target_center,
        )

    async def _html5_drag(
        self,
        source: Locator,
        target: Locator,
    ) -> DragResult:
        """使用 HTML5 拖放 API"""
        source_box = await source.bounding_box()
        target_box = await target.bounding_box()

        if not source_box or not target_box:
            return DragResult(
                success=False,
                error="无法获取元素位置",
            )

        source_center = {
            "x": source_box["x"] + source_box["width"] / 2,
            "y": source_box["y"] + source_box["height"] / 2,
        }

        target_center = {
            "x": target_box["x"] + target_box["width"] / 2,
            "y": target_box["y"] + target_box["height"] / 2,
        }

        # 使用 Playwright 的 drag_to 方法
        await source.drag_to(target)

        logger.debug("HTML5 拖拽完成")

        return DragResult(
            success=True,
            source_position=source_center,
            target_position=target_center,
        )

    async def drag_to_position(
        self,
        source: str,
        x: float,
        y: float,
        steps: int = 10,
        delay: float = 0.05,
    ) -> DragResult:
        """
        拖拽到指定位置

        Args:
            source: 源元素选择器
            x: 目标 X 坐标
            y: 目标 Y 坐标
            steps: 移动步数
            delay: 步间延迟（秒）

        Returns:
            DragResult: 拖拽结果
        """
        source_element = self.page.locator(source)
        source_box = await source_element.bounding_box()

        if not source_box:
            return DragResult(
                success=False,
                error="无法获取源元素位置",
            )

        source_center = {
            "x": source_box["x"] + source_box["width"] / 2,
            "y": source_box["y"] + source_box["height"] / 2,
        }

        try:
            await self.page.mouse.move(source_center["x"], source_center["y"])
            await self.page.mouse.down()

            for i in range(1, steps + 1):
                progress = i / steps
                current_x = source_center["x"] + (x - source_center["x"]) * progress
                current_y = source_center["y"] + (y - source_center["y"]) * progress

                await self.page.mouse.move(current_x, current_y)
                await asyncio.sleep(delay)

            await self.page.mouse.up()

            logger.debug(f"拖拽到位置: ({x:.0f}, {y:.0f})")

            return DragResult(
                success=True,
                source_position=source_center,
                target_position={"x": x, "y": y},
            )
        except Exception as e:
            return DragResult(
                success=False,
                error=str(e),
            )

    async def drag_slider(
        self,
        slider_selector: str,
        percentage: float,
        vertical: bool = False,
    ) -> DragResult:
        """
        拖拽滑块

        Args:
            slider_selector: 滑块选择器
            percentage: 百分比 (0-1)
            vertical: 是否为垂直滑块

        Returns:
            DragResult: 拖拽结果
        """
        slider = self.page.locator(slider_selector)
        slider_box = await slider.bounding_box()

        if not slider_box:
            return DragResult(
                success=False,
                error="无法获取滑块位置",
            )

        start_x = slider_box["x"] + slider_box["width"] / 2
        start_y = slider_box["y"] + slider_box["height"] / 2

        if vertical:
            target_x = start_x
            target_y = slider_box["y"] + slider_box["height"] * (1 - percentage)
        else:
            target_x = slider_box["x"] + slider_box["width"] * percentage
            target_y = start_y

        try:
            await self.page.mouse.move(start_x, start_y)
            await self.page.mouse.down()
            await self.page.mouse.move(target_x, target_y, steps=10)
            await self.page.mouse.up()

            logger.debug(f"滑块拖拽完成: {percentage * 100:.0f}%")

            return DragResult(
                success=True,
                source_position={"x": start_x, "y": start_y},
                target_position={"x": target_x, "y": target_y},
            )
        except Exception as e:
            return DragResult(
                success=False,
                error=str(e),
            )

    async def drag_reorder(
        self,
        container_selector: str,
        from_index: int,
        to_index: int,
    ) -> DragResult:
        """
        拖拽排序

        Args:
            container_selector: 容器选择器
            from_index: 源索引
            to_index: 目标索引

        Returns:
            DragResult: 拖拽结果
        """
        items = self.page.locator(f"{container_selector} > *")
        count = await items.count()

        if from_index >= count or to_index >= count:
            return DragResult(
                success=False,
                error="索引超出范围",
            )

        source_item = items.nth(from_index)
        target_item = items.nth(to_index)

        source_box = await source_item.bounding_box()
        target_box = await target_item.bounding_box()

        if not source_box or not target_box:
            return DragResult(
                success=False,
                error="无法获取元素位置",
            )

        source_center = {
            "x": source_box["x"] + source_box["width"] / 2,
            "y": source_box["y"] + source_box["height"] / 2,
        }

        target_center = {
            "x": target_box["x"] + target_box["width"] / 2,
            "y": target_box["y"] + target_box["height"] / 2,
        }

        try:
            await self.page.mouse.move(source_center["x"], source_center["y"])
            await self.page.mouse.down()
            await self.page.mouse.move(target_center["x"], target_center["y"], steps=10)
            await self.page.mouse.up()

            logger.debug(f"拖拽排序: {from_index} -> {to_index}")

            return DragResult(
                success=True,
                source_position=source_center,
                target_position=target_center,
            )
        except Exception as e:
            return DragResult(
                success=False,
                error=str(e),
            )

    async def drag_multiple(
        self,
        selector: str,
        target_selector: str,
        indices: List[int],
    ) -> List[DragResult]:
        """
        多选拖拽

        Args:
            selector: 源元素选择器
            target_selector: 目标选择器
            indices: 要拖拽的元素索引列表

        Returns:
            List[DragResult]: 拖拽结果列表
        """
        results: List[DragResult] = []
        target = self.page.locator(target_selector)
        target_box = await target.bounding_box()

        if not target_box:
            return [
                DragResult(success=False, error="无法获取目标元素位置")
                for _ in indices
            ]

        target_center = {
            "x": target_box["x"] + target_box["width"] / 2,
            "y": target_box["y"] + target_box["height"] / 2,
        }

        for index in indices:
            source = self.page.locator(selector).nth(index)
            source_box = await source.bounding_box()

            if not source_box:
                results.append(
                    DragResult(
                        success=False,
                        error=f"无法获取第 {index} 个源元素位置",
                    )
                )
                continue

            source_center = {
                "x": source_box["x"] + source_box["width"] / 2,
                "y": source_box["y"] + source_box["height"] / 2,
            }

            try:
                await self.page.mouse.move(source_center["x"], source_center["y"])
                await self.page.mouse.down()
                await self.page.mouse.move(target_center["x"], target_center["y"], steps=5)
                await self.page.mouse.up()

                results.append(
                    DragResult(
                        success=True,
                        source_position=source_center,
                        target_position=target_center,
                    )
                )
            except Exception as e:
                results.append(
                    DragResult(success=False, error=str(e))
                )

        logger.debug(f"多选拖拽完成: {len([r for r in results if r.success])}/{len(indices)}")
        return results

    async def drop_files(
        self,
        drop_zone_selector: str,
        file_paths: List[str],
    ) -> bool:
        """
        模拟文件拖放上传

        Args:
            drop_zone_selector: 放置区域选择器
            file_paths: 文件路径列表

        Returns:
            bool: 是否成功
        """
        drop_zone = self.page.locator(drop_zone_selector)

        try:
            # 使用 evaluate 在页面中创建并触发 drop 事件
            await drop_zone.evaluate(
                """(element, paths) => {
                    const dataTransfer = new DataTransfer();

                    const event = new DragEvent('drop', {
                        bubbles: true,
                        cancelable: true,
                        dataTransfer,
                    });

                    element.dispatchEvent(event);
                }""",
                file_paths,
            )

            logger.debug("文件拖放完成")
            return True
        except Exception as e:
            logger.debug(f"文件拖放失败: {e}")
            return False

    async def is_draggable(self, selector: str) -> bool:
        """检测元素是否可拖拽"""
        element = self.page.locator(selector)
        draggable = await element.get_attribute("draggable")
        return draggable == "true"

    async def get_element_position(
        self,
        selector: str
    ) -> Optional[Dict[str, float]]:
        """获取元素位置"""
        element = self.page.locator(selector)
        box = await element.bounding_box()

        if not box:
            return None

        return {
            "x": box["x"] + box["width"] / 2,
            "y": box["y"] + box["height"] / 2,
        }


def create_drag_drop_helper(page: Page) -> DragDropHelper:
    """创建拖拽操作助手实例"""
    return DragDropHelper(page)
