"""
文件管理器
文件上传/下载统一管理，支持进度监控
"""
import os
import mimetypes
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any
from pathlib import Path
from playwright.async_api import Page, Download, FileChooser
from loguru import logger


@dataclass
class FileResult:
    """文件操作结果"""
    success: bool
    path: str
    size: int = 0
    error: Optional[str] = None


@dataclass
class UploadConfig:
    """上传配置"""
    selector: str
    file_paths: List[str]


@dataclass
class DownloadConfig:
    """下载配置"""
    trigger_selector: str
    save_dir: str = "./downloads"
    file_name: Optional[str] = None


class FileManager:
    """
    文件管理器
    统一管理文件上传/下载操作
    """

    def __init__(self, page: Page, download_dir: str = "./downloads"):
        self.page = page
        self.download_dir = download_dir
        self._active_downloads: Dict[str, Download] = {}
        self._ensure_dir(self.download_dir)

    # ==================== 文件上传 ====================

    async def upload_file(
        self,
        selector: str,
        file_path: str,
    ) -> FileResult:
        """
        上传单个文件

        Args:
            selector: 文件输入框选择器
            file_path: 文件路径

        Returns:
            FileResult: 上传结果
        """
        try:
            # 检查文件是否存在
            if not os.path.exists(file_path):
                return FileResult(
                    success=False,
                    path=file_path,
                    error=f"文件不存在: {file_path}",
                )

            # 尝试直接设置文件
            input_locator = self.page.locator(f'{selector} input[type="file"]')
            input_count = await input_locator.count()

            if input_count > 0:
                await input_locator.set_input_files(file_path)
            else:
                # 尝试直接选择器
                direct_locator = self.page.locator(selector)
                is_file_input = await direct_locator.evaluate(
                    "el => el.tagName === 'INPUT' && el.type === 'file'"
                )

                if is_file_input:
                    await direct_locator.set_input_files(file_path)
                else:
                    # 使用 file chooser
                    async with self.page.expect_file_chooser() as fc_info:
                        await direct_locator.click()

                    file_chooser = await fc_info.value
                    await file_chooser.set_files(file_path)

            file_size = os.path.getsize(file_path)
            logger.debug(f"文件上传成功: {file_path} ({file_size} bytes)")

            return FileResult(
                success=True,
                path=file_path,
                size=file_size,
            )

        except Exception as e:
            logger.error(f"文件上传失败: {file_path}, error={e}")
            return FileResult(
                success=False,
                path=file_path,
                error=str(e),
            )

    async def upload_files(
        self,
        selector: str,
        file_paths: List[str],
    ) -> List[FileResult]:
        """
        上传多个文件

        Args:
            selector: 文件输入框选择器
            file_paths: 文件路径列表

        Returns:
            List[FileResult]: 上传结果列表
        """
        results: List[FileResult] = []

        # 检查是否支持多文件
        input_locator = self.page.locator(f'{selector} input[type="file"]')
        input_count = await input_locator.count()

        if input_count > 0:
            is_multiple = await input_locator.evaluate(
                "el => el.multiple"
            )

            if is_multiple:
                # 一次性上传所有文件
                existing_files = [f for f in file_paths if os.path.exists(f)]
                if existing_files:
                    await input_locator.set_input_files(existing_files)
                    for f in existing_files:
                        results.append(FileResult(
                            success=True,
                            path=f,
                            size=os.path.getsize(f),
                        ))
                    for f in file_paths:
                        if f not in existing_files:
                            results.append(FileResult(
                                success=False,
                                path=f,
                                error="文件不存在",
                            ))
                    return results

        # 逐个上传
        for file_path in file_paths:
            result = await self.upload_file(selector, file_path)
            results.append(result)

        return results

    async def upload(self, config: UploadConfig) -> List[FileResult]:
        """
        使用配置对象上传文件

        Args:
            config: 上传配置

        Returns:
            List[FileResult]: 上传结果列表
        """
        return await self.upload_files(config.selector, config.file_paths)

    async def upload_by_drop(
        self,
        selector: str,
        file_path: str,
    ) -> FileResult:
        """
        通过拖放上传文件

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

        Returns:
            FileResult: 上传结果
        """
        try:
            if not os.path.exists(file_path):
                return FileResult(
                    success=False,
                    path=file_path,
                    error=f"文件不存在: {file_path}",
                )

            drop_zone = self.page.locator(selector)
            file_name = os.path.basename(file_path)
            file_size = os.path.getsize(file_path)
            mime_type = self._get_mime_type(file_path)

            # 模拟拖放事件
            await drop_zone.dispatch_event("drop", {
                "dataTransfer": {
                    "files": [{
                        "name": file_name,
                        "size": file_size,
                        "type": mime_type,
                    }],
                },
            })

            logger.debug(f"拖放上传完成: {file_path}")

            return FileResult(
                success=True,
                path=file_path,
                size=file_size,
            )

        except Exception as e:
            logger.error(f"拖放上传失败: {file_path}, error={e}")
            return FileResult(
                success=False,
                path=file_path,
                error=str(e),
            )

    # ==================== 文件下载 ====================

    async def download_file(
        self,
        trigger_selector: str,
        save_path: Optional[str] = None,
    ) -> FileResult:
        """
        下载文件

        Args:
            trigger_selector: 触发下载的元素选择器
            save_path: 保存路径

        Returns:
            FileResult: 下载结果
        """
        try:
            # 监听下载事件并点击触发
            async with self.page.expect_download() as download_info:
                await self.page.locator(trigger_selector).click()

            download = await download_info.value

            # 确定保存路径
            suggested_name = download.suggested_filename
            final_path = save_path or os.path.join(self.download_dir, suggested_name)

            # 确保目录存在
            self._ensure_dir(os.path.dirname(final_path))

            # 保存文件
            await download.save_as(final_path)

            file_size = os.path.getsize(final_path)
            self._active_downloads.pop(suggested_name, None)

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

            return FileResult(
                success=True,
                path=final_path,
                size=file_size,
            )

        except Exception as e:
            logger.error(f"文件下载失败: {e}")
            return FileResult(
                success=False,
                path=save_path or "unknown",
                error=str(e),
            )

    async def download(self, config: DownloadConfig) -> FileResult:
        """
        使用配置对象下载文件

        Args:
            config: 下载配置

        Returns:
            FileResult: 下载结果
        """
        save_path = None
        if config.file_name:
            save_path = os.path.join(config.save_dir, config.file_name)

        self._ensure_dir(config.save_dir)
        return await self.download_file(config.trigger_selector, save_path)

    async def wait_for_download(self, timeout: float = 60000) -> Optional[Download]:
        """
        等待下载开始

        Args:
            timeout: 超时时间（毫秒）

        Returns:
            Optional[Download]: 下载对象
        """
        try:
            async with self.page.expect_download(timeout=timeout) as download_info:
                pass
            return await download_info.value
        except Exception:
            return None

    async def cancel_download(self, download: Download) -> None:
        """取消下载"""
        await download.cancel()

    async def get_download_progress(self, download: Download) -> int:
        """
        获取下载进度

        Args:
            download: 下载对象

        Returns:
            int: 已下载字节数
        """
        try:
            path = await download.path()
            if path and os.path.exists(path):
                return os.path.getsize(path)
        except Exception:
            pass
        return 0

    # ==================== 文件信息 ====================

    def file_exists(self, file_path: str) -> bool:
        """检查文件是否存在"""
        return os.path.exists(file_path)

    def get_file_size(self, file_path: str) -> int:
        """获取文件大小"""
        try:
            return os.path.getsize(file_path)
        except Exception:
            return 0

    def get_file_info(self, file_path: str) -> Dict[str, Any]:
        """
        获取文件信息

        Args:
            file_path: 文件路径

        Returns:
            dict: 文件信息
        """
        try:
            stat = os.stat(file_path)
            return {
                "exists": True,
                "size": stat.st_size,
                "created": stat.st_ctime,
                "modified": stat.st_mtime,
                "extension": os.path.splitext(file_path)[1],
            }
        except Exception:
            return {
                "exists": False,
                "size": 0,
                "created": None,
                "modified": None,
                "extension": "",
            }

    def delete_file(self, file_path: str) -> bool:
        """删除文件"""
        try:
            os.remove(file_path)
            return True
        except Exception:
            return False

    def rename_file(self, old_path: str, new_path: str) -> bool:
        """重命名文件"""
        try:
            os.rename(old_path, new_path)
            return True
        except Exception:
            return False

    def copy_file(self, source: str, destination: str) -> bool:
        """复制文件"""
        try:
            import shutil
            shutil.copy2(source, destination)
            return True
        except Exception:
            return False

    # ==================== 目录操作 ====================

    def create_dir(self, dir_path: str) -> bool:
        """创建目录"""
        try:
            os.makedirs(dir_path, exist_ok=True)
            return True
        except Exception:
            return False

    def remove_dir(self, dir_path: str, recursive: bool = False) -> bool:
        """删除目录"""
        try:
            import shutil
            if recursive:
                shutil.rmtree(dir_path)
            else:
                os.rmdir(dir_path)
            return True
        except Exception:
            return False

    def list_dir(self, dir_path: str) -> List[str]:
        """列出目录内容"""
        try:
            return os.listdir(dir_path)
        except Exception:
            return []

    def clear_download_dir(self) -> bool:
        """清空下载目录"""
        return self.remove_dir(self.download_dir, recursive=True)

    # ==================== 私有方法 ====================

    def _ensure_dir(self, dir_path: str) -> None:
        """确保目录存在"""
        if dir_path and not os.path.exists(dir_path):
            os.makedirs(dir_path, exist_ok=True)

    def _get_mime_type(self, file_path: str) -> str:
        """获取 MIME 类型"""
        mime_type, _ = mimetypes.guess_type(file_path)
        return mime_type or "application/octet-stream"


def create_file_manager(page: Page, download_dir: str = "./downloads") -> FileManager:
    """创建文件管理器实例"""
    return FileManager(page, download_dir)
