"""
调试辅助模块
调试工具、错误处理增强
"""
import traceback
from typing import Optional, Dict, Any, List, Callable
from playwright.async_api import Page, Locator
from loguru import logger
from dataclasses import dataclass
from datetime import datetime
from functools import wraps
import asyncio
import inspect


@dataclass
class DebugInfo:
    """调试信息"""
    url: str
    title: str
    screenshot_path: Optional[str] = None
    html_path: Optional[str] = None
    console_logs: List[str] = None
    errors: List[str] = None
    timestamp: datetime = None
    
    def __post_init__(self):
        if self.console_logs is None:
            self.console_logs = []
        if self.errors is None:
            self.errors = []
        if self.timestamp is None:
            self.timestamp = datetime.now()


class Debugger:
    """
    调试器
    提供调试辅助功能
    """
    
    def __init__(self, page: Page, debug_dir: str = "./debug"):
        self.page = page
        self.debug_dir = debug_dir
        self._console_messages: List[str] = []
        self._page_errors: List[str] = []
        self._is_monitoring = False
    
    def start_monitoring(self):
        """开始监控控制台和错误"""
        if self._is_monitoring:
            return
        
        self._is_monitoring = True
        
        self.page.on("console", lambda msg: self._console_messages.append(
            f"[{msg.type}] {msg.text}"
        ))
        self.page.on("pageerror", lambda err: self._page_errors.append(str(err)))
        
        logger.info("调试监控已启动")
    
    def stop_monitoring(self):
        """停止监控"""
        self._is_monitoring = False
        self.page.remove_listener("console", lambda msg: None)
        self.page.remove_listener("pageerror", lambda err: None)
    
    def get_console_logs(self) -> List[str]:
        """获取控制台日志"""
        return self._console_messages.copy()
    
    def get_errors(self) -> List[str]:
        """获取页面错误"""
        return self._page_errors.copy()
    
    def clear_logs(self):
        """清除日志"""
        self._console_messages.clear()
        self._page_errors.clear()
    
    async def capture_debug_info(
        self,
        name: str = "debug",
        include_screenshot: bool = True,
        include_html: bool = True,
    ) -> DebugInfo:
        """
        捕获调试信息
        
        Args:
            name: 名称
            include_screenshot: 是否包含截图
            include_html: 是否包含HTML
            
        Returns:
            DebugInfo: 调试信息
        """
        from pathlib import Path
        
        debug_dir = Path(self.debug_dir)
        debug_dir.mkdir(parents=True, exist_ok=True)
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        screenshot_path = None
        html_path = None
        
        if include_screenshot:
            screenshot_path = str(debug_dir / f"{name}_{timestamp}.png")
            await self.page.screenshot(path=screenshot_path)
        
        if include_html:
            html_path = str(debug_dir / f"{name}_{timestamp}.html")
            html = await self.page.content()
            Path(html_path).write_text(html, encoding='utf-8')
        
        info = DebugInfo(
            url=self.page.url,
            title=await self.page.title(),
            screenshot_path=screenshot_path,
            html_path=html_path,
            console_logs=self._console_messages.copy(),
            errors=self._page_errors.copy(),
        )
        
        logger.info(f"调试信息已捕获: {name}")
        return info
    
    async def highlight_all_clickable(self):
        """高亮所有可点击元素"""
        await self.page.evaluate("""
            () => {
                const clickables = document.querySelectorAll('a, button, input[type="button"], input[type="submit"], [onclick], [role="button"]');
                clickables.forEach(el => {
                    el.style.outline = '2px solid red';
                });
            }
        """)
        logger.debug("已高亮所有可点击元素")
    
    async def highlight_all_inputs(self):
        """高亮所有输入框"""
        await self.page.evaluate("""
            () => {
                const inputs = document.querySelectorAll('input, textarea, select');
                inputs.forEach(el => {
                    el.style.outline = '2px solid blue';
                });
            }
        """)
        logger.debug("已高亮所有输入框")
    
    async def print_element_info(self, selector: str):
        """打印元素信息"""
        element = self.page.locator(selector)
        
        try:
            info = await element.evaluate("""
                el => ({
                    tagName: el.tagName,
                    id: el.id,
                    className: el.className,
                    text: el.textContent?.substring(0, 100),
                    value: el.value,
                    href: el.href,
                    src: el.src,
                    display: getComputedStyle(el).display,
                    visibility: getComputedStyle(el).visibility,
                    position: getBoundingClientRect(el) ? {
                        x: getBoundingClientRect(el).x,
                        y: getBoundingClientRect(el).y,
                        width: getBoundingClientRect(el).width,
                        height: getBoundingClientRect(el).height,
                    } : null
                })
            """)
            
            print(f"\n=== 元素信息: {selector} ===")
            for key, value in info.items():
                print(f"  {key}: {value}")
            
        except Exception as e:
            print(f"无法获取元素信息: {e}")
    
    async def print_page_info(self):
        """打印页面信息"""
        info = await self.page.evaluate("""
            () => ({
                url: location.href,
                title: document.title,
                readyState: document.readyState,
                viewport: {
                    width: window.innerWidth,
                    height: window.innerHeight,
                },
                scroll: {
                    x: window.scrollX,
                    y: window.scrollY,
                },
            })
        """)
        
        print(f"\n=== 页面信息 ===")
        print(f"  URL: {info['url']}")
        print(f"  Title: {info['title']}")
        print(f"  ReadyState: {info['readyState']}")
        print(f"  Viewport: {info['viewport']['width']}x{info['viewport']['height']}")
        print(f"  Scroll: x={info['scroll']['x']}, y={info['scroll']['y']}")


def retry_on_error(
    max_retries: int = 3,
    delay: float = 1.0,
    exceptions: tuple = (Exception,),
    on_retry: Optional[Callable] = None,
):
    """
    错误重试装饰器
    
    Args:
        max_retries: 最大重试次数
        delay: 重试延迟
        exceptions: 要捕获的异常类型
        on_retry: 重试时的回调
        
    Example:
        @retry_on_error(max_retries=3, delay=1.0)
        async def unstable_operation():
            ...
    """
    def decorator(func):
        @wraps(func)
        async def async_wrapper(*args, **kwargs):
            last_error = None
            
            for attempt in range(max_retries):
                try:
                    return await func(*args, **kwargs)
                except exceptions as e:
                    last_error = e
                    logger.warning(
                        f"操作失败: {func.__name__}, "
                        f"尝试 {attempt + 1}/{max_retries}, error={e}"
                    )
                    
                    if on_retry:
                        on_retry(attempt, e)
                    
                    if attempt < max_retries - 1:
                        await asyncio.sleep(delay)
            
            raise last_error
        
        @wraps(func)
        def sync_wrapper(*args, **kwargs):
            import time
            last_error = None
            
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    last_error = e
                    logger.warning(
                        f"操作失败: {func.__name__}, "
                        f"尝试 {attempt + 1}/{max_retries}, error={e}"
                    )
                    
                    if on_retry:
                        on_retry(attempt, e)
                    
                    if attempt < max_retries - 1:
                        time.sleep(delay)
            
            raise last_error
        
        if asyncio.iscoroutinefunction(func):
            return async_wrapper
        return sync_wrapper
    
    return decorator


class ErrorHandler:
    """
    错误处理器
    统一的错误处理和日志记录
    """
    
    def __init__(self, page: Page, screenshot_on_error: bool = True):
        self.page = page
        self.screenshot_on_error = screenshot_on_error
        self._errors: List[Dict[str, Any]] = []
    
    async def handle_error(
        self,
        error: Exception,
        context: Optional[str] = None,
        screenshot_name: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        处理错误
        
        Args:
            error: 异常对象
            context: 上下文信息
            screenshot_name: 截图名称
            
        Returns:
            Dict: 错误信息
        """
        timestamp = datetime.now()
        
        error_info = {
            "timestamp": timestamp.isoformat(),
            "error_type": type(error).__name__,
            "error_message": str(error),
            "context": context,
            "url": self.page.url,
            "traceback": traceback.format_exc(),
        }
        
        # 截图
        if self.screenshot_on_error:
            screenshot_path = f"./debug/error_{timestamp.strftime('%Y%m%d_%H%M%S')}.png"
            try:
                await self.page.screenshot(path=screenshot_path)
                error_info["screenshot"] = screenshot_path
            except:
                pass
        
        self._errors.append(error_info)
        
        logger.error(
            f"错误处理: {error_info['error_type']}: {error_info['error_message']}"
        )
        
        return error_info
    
    def get_errors(self) -> List[Dict[str, Any]]:
        """获取所有错误"""
        return self._errors.copy()
    
    def clear_errors(self):
        """清除错误记录"""
        self._errors.clear()
    
    def has_errors(self) -> bool:
        """是否有错误"""
        return len(self._errors) > 0
    
    def get_error_summary(self) -> Dict[str, int]:
        """获取错误统计"""
        summary = {}
        for error in self._errors:
            error_type = error["error_type"]
            summary[error_type] = summary.get(error_type, 0) + 1
        return summary


class AssertionHelper:
    """
    断言辅助工具
    提供更友好的断言方法
    """
    
    def __init__(self, page: Page):
        self.page = page
    
    async def element_exists(self, selector: str, message: str = "") -> bool:
        """断言元素存在"""
        count = await self.page.locator(selector).count()
        if count == 0:
            raise AssertionError(
                f"元素不存在: {selector}. {message}"
            )
        return True
    
    async def element_not_exists(self, selector: str, message: str = "") -> bool:
        """断言元素不存在"""
        count = await self.page.locator(selector).count()
        if count > 0:
            raise AssertionError(
                f"元素存在（预期不存在）: {selector}. {message}"
            )
        return True
    
    async def element_visible(self, selector: str, message: str = "") -> bool:
        """断言元素可见"""
        locator = self.page.locator(selector)
        if not await locator.is_visible():
            raise AssertionError(
                f"元素不可见: {selector}. {message}"
            )
        return True
    
    async def element_hidden(self, selector: str, message: str = "") -> bool:
        """断言元素隐藏"""
        locator = self.page.locator(selector)
        if await locator.is_visible():
            raise AssertionError(
                f"元素可见（预期隐藏）: {selector}. {message}"
            )
        return True
    
    async def element_enabled(self, selector: str, message: str = "") -> bool:
        """断言元素可用"""
        locator = self.page.locator(selector)
        if not await locator.is_enabled():
            raise AssertionError(
                f"元素禁用: {selector}. {message}"
            )
        return True
    
    async def element_disabled(self, selector: str, message: str = "") -> bool:
        """断言元素禁用"""
        locator = self.page.locator(selector)
        if await locator.is_enabled():
            raise AssertionError(
                f"元素可用（预期禁用）: {selector}. {message}"
            )
        return True
    
    async def text_equals(
        self, selector: str, expected: str, message: str = ""
    ) -> bool:
        """断言文本相等"""
        actual = await self.page.locator(selector).text_content()
        actual = (actual or "").strip()
        
        if actual != expected:
            raise AssertionError(
                f"文本不匹配: 期望 '{expected}', 实际 '{actual}'. {message}"
            )
        return True
    
    async def text_contains(
        self, selector: str, expected: str, message: str = ""
    ) -> bool:
        """断言文本包含"""
        actual = await self.page.locator(selector).text_content()
        actual = actual or ""
        
        if expected not in actual:
            raise AssertionError(
                f"文本不包含: 期望包含 '{expected}', 实际 '{actual[:100]}'. {message}"
            )
        return True
    
    async def value_equals(
        self, selector: str, expected: str, message: str = ""
    ) -> bool:
        """断言值相等"""
        actual = await self.page.locator(selector).input_value()
        
        if actual != expected:
            raise AssertionError(
                f"值不匹配: 期望 '{expected}', 实际 '{actual}'. {message}"
            )
        return True
    
    async def url_equals(self, expected: str, message: str = "") -> bool:
        """断言URL相等"""
        actual = self.page.url
        
        if actual != expected:
            raise AssertionError(
                f"URL不匹配: 期望 '{expected}', 实际 '{actual}'. {message}"
            )
        return True
    
    async def url_contains(self, expected: str, message: str = "") -> bool:
        """断言URL包含"""
        actual = self.page.url
        
        if expected not in actual:
            raise AssertionError(
                f"URL不包含: 期望包含 '{expected}', 实际 '{actual}'. {message}"
            )
        return True
    
    async def title_equals(self, expected: str, message: str = "") -> bool:
        """断言标题相等"""
        actual = await self.page.title()
        
        if actual != expected:
            raise AssertionError(
                f"标题不匹配: 期望 '{expected}', 实际 '{actual}'. {message}"
            )
        return True
    
    async def title_contains(self, expected: str, message: str = "") -> bool:
        """断言标题包含"""
        actual = await self.page.title()
        
        if expected not in actual:
            raise AssertionError(
                f"标题不包含: 期望包含 '{expected}', 实际 '{actual}'. {message}"
            )
        return True
    
    async def count_equals(
        self, selector: str, expected: int, message: str = ""
    ) -> bool:
        """断言元素数量相等"""
        actual = await self.page.locator(selector).count()
        
        if actual != expected:
            raise AssertionError(
                f"元素数量不匹配: 期望 {expected}, 实际 {actual}. {message}"
            )
        return True
