"""
高级浏览器操作
更复杂的自动化场景
"""
import asyncio
from typing import Optional, List, Dict, Any, Callable
from playwright.async_api import Page, Locator
from loguru import logger
from dataclasses import dataclass
from datetime import datetime


@dataclass
class DownloadTask:
    """下载任务"""
    url: str
    save_path: str
    status: str = "pending"
    progress: float = 0
    error: Optional[str] = None


class BatchOperations:
    """
    批量操作
    支持批量截图、下载、填充等
    """
    
    def __init__(self, page: Page):
        self.page = page
    
    async def batch_screenshot(
        self,
        urls: List[str],
        output_dir: str = "./screenshots",
        naming: str = "url",
        wait_after_load: float = 1.0,
        progress_callback: Optional[Callable[[int, int], None]] = None,
    ) -> List[str]:
        """
        批量截图
        
        Args:
            urls: URL列表
            output_dir: 输出目录
            naming: 命名方式 (url, index, timestamp)
            wait_after_load: 加载后等待时间
            
        Returns:
            List[str]: 截图路径列表
        """
        from pathlib import Path
        from urllib.parse import urlparse
        
        Path(output_dir).mkdir(parents=True, exist_ok=True)
        paths = []
        
        for i, url in enumerate(urls):
            try:
                await self.page.goto(url)
                await asyncio.sleep(wait_after_load)
                
                # 生成文件名
                if naming == "url":
                    parsed = urlparse(url)
                    name = parsed.netloc + parsed.path.replace("/", "_")
                elif naming == "index":
                    name = f"screenshot_{i:04d}"
                else:
                    name = datetime.now().strftime("%Y%m%d_%H%M%S")
                
                path = str(Path(output_dir) / f"{name}.png")
                await self.page.screenshot(path=path)
                paths.append(path)
                
                logger.info(f"截图完成: {url}")
                
                if progress_callback:
                    progress_callback(i + 1, len(urls))
                    
            except Exception as e:
                logger.error(f"截图失败: {url}, error={e}")
                paths.append(None)
        
        return paths
    
    async def batch_fill(
        self,
        form_data: List[Dict[str, str]],
        form_selector: str = "form",
        submit_selector: Optional[str] = None,
        delay: float = 1.0,
    ) -> List[bool]:
        """
        批量填充表单
        
        Args:
            form_data: 表单数据列表
            form_selector: 表单选择器
            submit_selector: 提交按钮选择器
            delay: 填充间隔
            
        Returns:
            List[bool]: 成功状态列表
        """
        from .form import FormHandler
        
        form_handler = FormHandler(self.page)
        results = []
        
        for i, data in enumerate(form_data):
            try:
                await form_handler.fill(data, form_selector)
                
                if submit_selector:
                    await self.page.click(submit_selector)
                    await asyncio.sleep(delay)
                
                results.append(True)
                logger.info(f"表单填充成功: {i + 1}/{len(form_data)}")
                
            except Exception as e:
                logger.error(f"表单填充失败: {e}")
                results.append(False)
        
        return results
    
    async def batch_download(
        self,
        urls: List[str],
        output_dir: str = "./downloads",
        progress_callback: Optional[Callable[[int, int], None]] = None,
    ) -> List[DownloadTask]:
        """
        批量下载文件
        
        Args:
            urls: 下载URL列表
            output_dir: 输出目录
            progress_callback: 进度回调
            
        Returns:
            List[DownloadTask]: 下载任务列表
        """
        from pathlib import Path
        
        Path(output_dir).mkdir(parents=True, exist_ok=True)
        tasks = []
        
        for i, url in enumerate(urls):
            task = DownloadTask(url=url, save_path="")
            tasks.append(task)
            
            try:
                async with self.page.expect_download() as download_info:
                    # 尝试直接导航到下载链接
                    await self.page.evaluate(f"window.location.href = '{url}'")
                
                download = await download_info.value
                save_path = str(Path(output_dir) / download.suggested_filename)
                await download.save_as(save_path)
                
                task.save_path = save_path
                task.status = "completed"
                
                logger.info(f"下载完成: {url}")
                
            except Exception as e:
                task.status = "failed"
                task.error = str(e)
                logger.error(f"下载失败: {url}, error={e}")
            
            if progress_callback:
                progress_callback(i + 1, len(urls))
        
        return tasks


class AutomationFlow:
    """
    自动化流程
    定义和执行自动化工作流
    """
    
    def __init__(self, page: Page):
        self.page = page
        self.steps: List[Dict[str, Any]] = []
        self._current_step = 0
    
    def add_step(
        self,
        action: str,
        selector: Optional[str] = None,
        value: Optional[str] = None,
        wait: Optional[float] = None,
        condition: Optional[Callable] = None,
    ) -> "AutomationFlow":
        """
        添加步骤
        
        Args:
            action: 动作类型
            selector: 选择器
            value: 值
            wait: 等待时间
            condition: 条件函数
            
        Returns:
            AutomationFlow: self (链式调用)
        """
        self.steps.append({
            "action": action,
            "selector": selector,
            "value": value,
            "wait": wait,
            "condition": condition,
        })
        return self
    
    def navigate(self, url: str) -> "AutomationFlow":
        """导航步骤"""
        return self.add_step("navigate", value=url)
    
    def click(self, selector: str, wait: float = None) -> "AutomationFlow":
        """点击步骤"""
        return self.add_step("click", selector=selector, wait=wait)
    
    def fill(self, selector: str, value: str) -> "AutomationFlow":
        """填充步骤"""
        return self.add_step("fill", selector=selector, value=value)
    
    def wait(self, seconds: float) -> "AutomationFlow":
        """等待步骤"""
        return self.add_step("wait", value=str(seconds))
    
    def wait_for_selector(self, selector: str, timeout: float = 30) -> "AutomationFlow":
        """等待元素步骤"""
        return self.add_step("wait_for_selector", selector=selector, value=str(timeout))
    
    def scroll(self, distance: int = 500) -> "AutomationFlow":
        """滚动步骤"""
        return self.add_step("scroll", value=str(distance))
    
    def screenshot(self, path: str) -> "AutomationFlow":
        """截图步骤"""
        return self.add_step("screenshot", value=path)
    
    def condition(self, check_func: Callable) -> "AutomationFlow":
        """条件步骤"""
        return self.add_step("condition", condition=check_func)
    
    async def run(
        self,
        on_step_start: Optional[Callable[[int, Dict], None]] = None,
        on_step_end: Optional[Callable[[int, Dict, bool], None]] = None,
    ) -> bool:
        """
        执行流程
        
        Args:
            on_step_start: 步骤开始回调
            on_step_end: 步骤结束回调
            
        Returns:
            bool: 是否全部成功
        """
        logger.info(f"开始执行流程: {len(self.steps)} 步")
        
        for i, step in enumerate(self.steps):
            self._current_step = i
            
            if on_step_start:
                on_step_start(i, step)
            
            success = await self._execute_step(step)
            
            if on_step_end:
                on_step_end(i, step, success)
            
            if not success and step.get("required", True):
                logger.error(f"流程中断: 步骤 {i + 1} 失败")
                return False
        
        logger.info("流程执行完成")
        return True
    
    async def _execute_step(self, step: Dict) -> bool:
        """执行单个步骤"""
        try:
            action = step["action"]
            
            if action == "navigate":
                await self.page.goto(step["value"])
            
            elif action == "click":
                await self.page.click(step["selector"])
                if step.get("wait"):
                    await asyncio.sleep(float(step["wait"]))
            
            elif action == "fill":
                await self.page.fill(step["selector"], step["value"])
            
            elif action == "wait":
                await asyncio.sleep(float(step["value"]))
            
            elif action == "wait_for_selector":
                await self.page.wait_for_selector(
                    step["selector"],
                    timeout=float(step["value"]) * 1000
                )
            
            elif action == "scroll":
                await self.page.mouse.wheel(0, int(step["value"]))
            
            elif action == "screenshot":
                await self.page.screenshot(path=step["value"])
            
            elif action == "condition":
                if asyncio.iscoroutinefunction(step["condition"]):
                    result = await step["condition"]()
                else:
                    result = step["condition"]()
                
                if not result:
                    return False
            
            logger.debug(f"步骤完成: {action}")
            return True
            
        except Exception as e:
            logger.error(f"步骤执行失败: {e}")
            return False
    
    def save(self, filepath: str):
        """保存流程"""
        import json
        
        data = {
            "steps": self.steps,
            "saved_at": datetime.now().isoformat(),
        }
        
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        
        logger.info(f"流程已保存: {filepath}")
    
    @classmethod
    def load(cls, page: Page, filepath: str) -> "AutomationFlow":
        """加载流程"""
        import json
        
        with open(filepath, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        flow = cls(page)
        flow.steps = data["steps"]
        
        logger.info(f"流程已加载: {filepath}")
        return flow


class TabManager:
    """
    标签页管理器
    高级多标签页操作
    """
    
    def __init__(self, page: Page):
        self.page = page
        self.context = page.context
        self._managed_tabs: Dict[str, Page] = {}
    
    async def open_tab(self, name: str, url: str = "about:blank") -> Page:
        """打开命名标签页"""
        new_page = await self.context.new_page()
        await new_page.goto(url)
        self._managed_tabs[name] = new_page
        logger.info(f"打开标签页: {name}")
        return new_page
    
    async def switch_to(self, name: str) -> Page:
        """切换到标签页"""
        if name not in self._managed_tabs:
            raise ValueError(f"标签页不存在: {name}")
        return self._managed_tabs[name]
    
    async def close_tab(self, name: str):
        """关闭标签页"""
        if name in self._managed_tabs:
            await self._managed_tabs[name].close()
            del self._managed_tabs[name]
            logger.info(f"关闭标签页: {name}")
    
    async def broadcast(self, action: Callable[[Page], Any]):
        """在所有标签页执行操作"""
        results = {}
        for name, page in self._managed_tabs.items():
            try:
                if asyncio.iscoroutinefunction(action):
                    results[name] = await action(page)
                else:
                    results[name] = action(page)
            except Exception as e:
                results[name] = {"error": str(e)}
        return results
    
    async def screenshot_all(self, output_dir: str = "./tabs"):
        """截取所有标签页"""
        from pathlib import Path
        
        Path(output_dir).mkdir(parents=True, exist_ok=True)
        
        for name, page in self._managed_tabs.items():
            try:
                path = str(Path(output_dir) / f"{name}.png")
                await page.screenshot(path=path)
                logger.info(f"截图: {name} -> {path}")
            except Exception as e:
                logger.error(f"截图失败: {name}, error={e}")


class SessionRecorder:
    """
    会话录制器
    录制整个浏览会话
    """
    
    def __init__(self, page: Page):
        self.page = page
        self._events: List[Dict] = []
        self._is_recording = False
        self._start_time: Optional[datetime] = None
    
    def start(self):
        """开始录制"""
        self._is_recording = True
        self._start_time = datetime.now()
        self._events.clear()
        
        # 监听页面事件
        self.page.on("load", self._on_load)
        self.page.on("console", self._on_console)
        self.page.on("pageerror", self._on_error)
        
        logger.info("会话录制开始")
    
    def stop(self):
        """停止录制"""
        self._is_recording = False
        
        self.page.remove_listener("load", self._on_load)
        self.page.remove_listener("console", self._on_console)
        self.page.remove_listener("pageerror", self._on_error)
        
        logger.info(f"会话录制结束: {len(self._events)} 个事件")
    
    def _record_event(self, event_type: str, data: Any):
        """记录事件"""
        if not self._is_recording:
            return
        
        self._events.append({
            "type": event_type,
            "data": data,
            "timestamp": datetime.now().isoformat(),
            "url": self.page.url,
        })
    
    def _on_load(self):
        self._record_event("load", {"url": self.page.url})
    
    def _on_console(self, msg):
        self._record_event("console", {"type": msg.type, "text": msg.text})
    
    def _on_error(self, error):
        self._record_event("error", {"message": str(error)})
    
    def save(self, filepath: str):
        """保存录制"""
        import json
        
        data = {
            "events": self._events,
            "start_time": self._start_time.isoformat() if self._start_time else None,
            "end_time": datetime.now().isoformat(),
            "duration_seconds": (datetime.now() - self._start_time).total_seconds() if self._start_time else 0,
        }
        
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        
        logger.info(f"会话录制已保存: {filepath}")
    
    def get_summary(self) -> Dict:
        """获取摘要"""
        summary = {
            "total_events": len(self._events),
            "errors": sum(1 for e in self._events if e["type"] == "error"),
            "consoles": sum(1 for e in self._events if e["type"] == "console"),
            "pages_loaded": sum(1 for e in self._events if e["type"] == "load"),
        }
        return summary
