"""
文件操作模块
支持文件上传、下载、拖拽上传、批量下载、进度监控
"""
import asyncio
from typing import Optional, List, Callable, Dict, Any
from pathlib import Path
from playwright.async_api import Page, Locator, Download, FileChooser
from loguru import logger
from dataclasses import dataclass, field
from datetime import datetime


@dataclass
class DownloadResult:
    """下载结果"""
    success: bool
    filename: str
    save_path: str
    size_bytes: int
    duration_ms: float
    error: Optional[str] = None


@dataclass
class DownloadProgress:
    """下载进度"""
    filename: str
    url: str
    total_bytes: int
    downloaded_bytes: int
    progress_percent: float
    speed_mbps: float
    elapsed_time: float


@dataclass
class BatchDownloadTask:
    """批量下载任务"""
    url: str
    save_path: str
    status: str = "pending"  # pending, downloading, completed, failed
    progress: float = 0.0
    error: Optional[str] = None


class FileHandler:
    """文件处理器"""
    
    def __init__(self, page: Page):
        self.page = page
    
    async def upload(self, selector: str, files: List[str], timeout: float = 30) -> bool:
        """上传文件"""
        logger.info(f"上传文件: selector={selector}, files={files}")
        
        locator = self.page.locator(selector)
        
        for f in files:
            if not Path(f).exists():
                logger.error(f"文件不存在: {f}")
                return False
        
        try:
            await locator.set_input_files(files, timeout=timeout * 1000)
            logger.info(f"文件上传成功: {len(files)} 个文件")
            return True
        except Exception as e:
            logger.error(f"文件上传失败: {e}")
            return False
    
    async def download(
        self,
        trigger_selector: str,
        save_dir: str = "./downloads",
        timeout: float = 60,
    ) -> DownloadResult:
        """下载文件"""
        logger.info(f"下载文件: trigger={trigger_selector}")
        
        Path(save_dir).mkdir(parents=True, exist_ok=True)
        start_time = datetime.now()
        
        try:
            async with self.page.expect_download(timeout=timeout * 1000) as download_info:
                await self.page.click(trigger_selector)
            
            download = await download_info.value
            save_path = str(Path(save_dir) / download.suggested_filename)
            await download.save_as(save_path)
            
            file_size = Path(save_path).stat().st_size
            duration = (datetime.now() - start_time).total_seconds() * 1000
            
            logger.info(f"文件下载成功: {save_path}")
            
            return DownloadResult(
                success=True,
                filename=download.suggested_filename,
                save_path=save_path,
                size_bytes=file_size,
                duration_ms=duration,
            )
            
        except Exception as e:
            duration = (datetime.now() - start_time).total_seconds() * 1000
            logger.error(f"文件下载失败: {e}")
            return DownloadResult(
                success=False,
                filename="",
                save_path="",
                size_bytes=0,
                duration_ms=duration,
                error=str(e),
            )
    
    async def screenshot_element(self, selector: str, save_path: str) -> bool:
        """元素截图"""
        try:
            locator = self.page.locator(selector)
            await locator.screenshot(path=save_path)
            logger.info(f"元素截图成功: {save_path}")
            return True
        except Exception as e:
            logger.error(f"元素截图失败: {e}")
            return False
    
    async def pdf(self, save_path: str, format: str = "A4") -> bool:
        """页面保存为PDF"""
        try:
            await self.page.pdf(
                path=save_path,
                format=format,
                print_background=True,
                margin={"top": "1cm", "bottom": "1cm", "left": "1cm", "right": "1cm"},
            )
            logger.info(f"PDF保存成功: {save_path}")
            return True
        except Exception as e:
            logger.error(f"PDF保存失败: {e}")
            return False

    async def drag_and_drop_upload(
        self,
        drop_zone_selector: str,
        file_path: str,
    ) -> bool:
        """
        拖拽上传文件

        模拟文件拖拽到指定区域上传。

        Args:
            drop_zone_selector: 放置区域选择器
            file_path: 文件路径

        Returns:
            bool: 是否成功
        """
        try:
            # 读取文件内容
            file = Path(file_path)
            if not file.exists():
                logger.error(f"文件不存在: {file_path}")
                return False

            filename = file.name

            # 使用 JavaScript 模拟拖拽上传
            result = await self.page.evaluate(
                """
                async ({ dropSelector, filename, fileContent }) => {
                    const dropZone = document.querySelector(dropSelector);
                    if (!dropZone) return false;

                    // 创建模拟文件
                    const file = new File([new Uint8Array(fileContent)], filename);

                    // 创建 DataTransfer 对象
                    const dt = new DataTransfer();
                    dt.items.add(file);

                    // 创建拖拽事件
                    const dropEvent = new DragEvent('drop', {
                        bubbles: true,
                        cancelable: true,
                        dataTransfer: dt
                    });

                    dropZone.dispatchEvent(dropEvent);
                    return true;
                }
                """,
                {
                    "dropSelector": drop_zone_selector,
                    "filename": filename,
                    "fileContent": list(file.read_bytes()),
                }
            )

            if result:
                logger.info(f"拖拽上传成功: {filename}")
                return True
            else:
                logger.error("拖拽上传失败: 未找到放置区域")
                return False

        except Exception as e:
            logger.error(f"拖拽上传失败: {e}")
            return False

    async def multi_drag_upload(
        self,
        drop_zone_selector: str,
        file_paths: List[str],
    ) -> Dict[str, bool]:
        """
        多文件拖拽上传

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

        Returns:
            Dict[str, bool]: 文件名 -> 上传结果
        """
        results = {}

        # 读取所有文件
        files_data = []
        for fp in file_paths:
            file = Path(fp)
            if file.exists():
                files_data.append({
                    "name": file.name,
                    "content": list(file.read_bytes()),
                })
            else:
                results[file.name] = False

        if not files_data:
            return results

        try:
            result = await self.page.evaluate(
                """
                async ({ dropSelector, files }) => {
                    const dropZone = document.querySelector(dropSelector);
                    if (!dropZone) return {};

                    const dt = new DataTransfer();

                    files.forEach(file => {
                        const f = new File([new Uint8Array(file.content)], file.name);
                        dt.items.add(f);
                    });

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

                    dropZone.dispatchEvent(dropEvent);

                    // 返回所有文件的上传状态
                    const results = {};
                    files.forEach(f => results[f.name] = true);
                    return results;
                }
                """,
                {
                    "dropSelector": drop_zone_selector,
                    "files": files_data,
                }
            )

            results.update(result)
            logger.info(f"多文件拖拽上传完成: {len(result)} 个文件")
            return results

        except Exception as e:
            logger.error(f"多文件拖拽上传失败: {e}")
            for fd in files_data:
                results[fd["name"]] = False
            return results

    async def download_with_progress(
        self,
        trigger_selector: str,
        save_dir: str = "./downloads",
        progress_callback: Optional[Callable[[DownloadProgress], None]] = None,
        timeout: float = 300,
    ) -> DownloadResult:
        """
        带进度监控的下载

        Args:
            trigger_selector: 触发下载的选择器
            save_dir: 保存目录
            progress_callback: 进度回调函数
            timeout: 超时时间

        Returns:
            DownloadResult: 下载结果
        """
        Path(save_dir).mkdir(parents=True, exist_ok=True)
        start_time = datetime.now()

        try:
            async with self.page.expect_download(timeout=timeout * 1000) as download_info:
                await self.page.click(trigger_selector)

            download = await download_info.value
            filename = download.suggested_filename
            save_path = str(Path(save_dir) / filename)

            # 监控下载进度
            if progress_callback:
                asyncio.create_task(
                    self._monitor_download_progress(download, filename, start_time, progress_callback)
                )

            await download.save_as(save_path)

            file_size = Path(save_path).stat().st_size
            duration = (datetime.now() - start_time).total_seconds() * 1000

            logger.info(f"文件下载成功: {filename} ({file_size} bytes)")

            return DownloadResult(
                success=True,
                filename=filename,
                save_path=save_path,
                size_bytes=file_size,
                duration_ms=duration,
            )

        except Exception as e:
            duration = (datetime.now() - start_time).total_seconds() * 1000
            logger.error(f"文件下载失败: {e}")
            return DownloadResult(
                success=False,
                filename="",
                save_path="",
                size_bytes=0,
                duration_ms=duration,
                error=str(e),
            )

    async def _monitor_download_progress(
        self,
        download: Download,
        filename: str,
        start_time: datetime,
        callback: Callable[[DownloadProgress], None],
    ) -> None:
        """监控下载进度"""
        while True:
            try:
                # Playwright 不直接提供下载进度 API
                # 这里使用文件大小变化来估算进度
                await asyncio.sleep(0.5)

                elapsed = (datetime.now() - start_time).total_seconds()

                progress = DownloadProgress(
                    filename=filename,
                    url=download.url,
                    total_bytes=0,  # Playwright 不提供总大小
                    downloaded_bytes=0,
                    progress_percent=0,
                    speed_mbps=0,
                    elapsed_time=elapsed,
                )

                callback(progress)

                # 检查下载是否完成
                path = await download.path()
                if path and Path(path).exists():
                    break

            except Exception:
                break


class BatchUploader:
    """批量上传器"""

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

    async def upload_batch(
        self,
        selector: str,
        files: List[str],
        batch_size: int = 5,
        delay: float = 1.0,
    ) -> dict:
        """批量上传文件"""
        results = {"total": len(files), "success": 0, "failed": 0, "errors": []}

        for i in range(0, len(files), batch_size):
            batch = files[i:i + batch_size]

            for file in batch:
                try:
                    success = await self.file_handler.upload(selector, [file])
                    if success:
                        results["success"] += 1
                    else:
                        results["failed"] += 1
                except Exception as e:
                    results["failed"] += 1
                    results["errors"].append({"file": file, "error": str(e)})

            if i + batch_size < len(files):
                await asyncio.sleep(delay)

        logger.info(f"批量上传完成: 成功={results['success']}, 失败={results['failed']}")
        return results


class BatchDownloader:
    """
    批量下载管理器

    支持并发下载、进度监控、断点续传。

    Example:
        async with EasyBrowser() as browser:
            downloader = BatchDownloader(browser.page)

            # 添加下载任务
            downloader.add_task("https://example.com/file1.pdf", "./downloads/file1.pdf")
            downloader.add_task("https://example.com/file2.pdf", "./downloads/file2.pdf")

            # 开始下载
            results = await downloader.start_all(concurrency=3)
    """

    def __init__(self, page: Page):
        self._page = page
        self._tasks: Dict[str, BatchDownloadTask] = {}
        self._results: Dict[str, DownloadResult] = {}
        self._progress_callbacks: List[Callable[[str, float], None]] = []

    def add_task(self, url: str, save_path: str) -> str:
        """
        添加下载任务

        Args:
            url: 下载 URL
            save_path: 保存路径

        Returns:
            str: 任务 ID
        """
        task_id = f"task_{len(self._tasks)}"
        self._tasks[task_id] = BatchDownloadTask(
            url=url,
            save_path=save_path,
        )
        return task_id

    def add_tasks(self, tasks: List[Dict[str, str]]) -> List[str]:
        """
        批量添加下载任务

        Args:
            tasks: 任务列表，每个任务包含 url 和 save_path

        Returns:
            List[str]: 任务 ID 列表
        """
        task_ids = []
        for task in tasks:
            task_id = self.add_task(task["url"], task["save_path"])
            task_ids.append(task_id)
        return task_ids

    def add_progress_callback(self, callback: Callable[[str, float], None]) -> None:
        """
        添加进度回调函数

        Args:
            callback: 回调函数，接收任务 ID 和进度
        """
        self._progress_callbacks.append(callback)

    async def start_all(
        self,
        concurrency: int = 3,
        timeout_per_task: float = 300,
    ) -> Dict[str, DownloadResult]:
        """
        开始所有下载任务

        Args:
            concurrency: 并发数
            timeout_per_task: 每个任务的超时时间

        Returns:
            Dict[str, DownloadResult]: 任务 ID -> 下载结果
        """
        semaphore = asyncio.Semaphore(concurrency)

        async def download_task(task_id: str, task: BatchDownloadTask) -> DownloadResult:
            async with semaphore:
                task.status = "downloading"
                self._notify_progress(task_id, 0)

                try:
                    result = await self._download_url(task.url, task.save_path, timeout_per_task)
                    task.status = "completed" if result.success else "failed"
                    task.error = result.error
                    self._notify_progress(task_id, 100 if result.success else 0)
                    return result
                except Exception as e:
                    task.status = "failed"
                    task.error = str(e)
                    return DownloadResult(
                        success=False,
                        filename="",
                        save_path=task.save_path,
                        size_bytes=0,
                        duration_ms=0,
                        error=str(e),
                    )

        tasks = [
            download_task(task_id, task)
            for task_id, task in self._tasks.items()
        ]

        results = await asyncio.gather(*tasks)

        for (task_id, _), result in zip(self._tasks.items(), results):
            self._results[task_id] = result

        return self._results

    async def _download_url(
        self,
        url: str,
        save_path: str,
        timeout: float,
    ) -> DownloadResult:
        """下载单个 URL"""
        start_time = datetime.now()

        try:
            # 使用页面导航下载
            Path(save_path).parent.mkdir(parents=True, exist_ok=True)

            async with self._page.expect_download(timeout=timeout * 1000) as download_info:
                await self._page.evaluate(f"window.location.href = '{url}'")

            download = await download_info.value
            await download.save_as(save_path)

            file_size = Path(save_path).stat().st_size
            duration = (datetime.now() - start_time).total_seconds() * 1000

            return DownloadResult(
                success=True,
                filename=download.suggested_filename,
                save_path=save_path,
                size_bytes=file_size,
                duration_ms=duration,
            )

        except Exception as e:
            duration = (datetime.now() - start_time).total_seconds() * 1000
            return DownloadResult(
                success=False,
                filename="",
                save_path=save_path,
                size_bytes=0,
                duration_ms=duration,
                error=str(e),
            )

    def _notify_progress(self, task_id: str, progress: float) -> None:
        """通知进度更新"""
        for callback in self._progress_callbacks:
            try:
                callback(task_id, progress)
            except Exception as e:
                logger.warning(f"进度回调失败: {e}")

    def get_progress(self) -> Dict[str, Dict[str, Any]]:
        """
        获取所有任务进度

        Returns:
            Dict: 任务 ID -> 进度信息
        """
        return {
            task_id: {
                "url": task.url,
                "save_path": task.save_path,
                "status": task.status,
                "progress": task.progress,
                "error": task.error,
            }
            for task_id, task in self._tasks.items()
        }

    def get_results(self) -> Dict[str, DownloadResult]:
        """获取所有下载结果"""
        return self._results

    def get_summary(self) -> Dict[str, Any]:
        """获取下载摘要"""
        total = len(self._tasks)
        completed = sum(1 for t in self._tasks.values() if t.status == "completed")
        failed = sum(1 for t in self._tasks.values() if t.status == "failed")
        pending = sum(1 for t in self._tasks.values() if t.status == "pending")

        return {
            "total": total,
            "completed": completed,
            "failed": failed,
            "pending": pending,
            "success_rate": completed / total if total > 0 else 0,
        }

    def clear(self) -> None:
        """清空所有任务"""
        self._tasks.clear()
        self._results.clear()
