"""
控制台监控器
捕获/过滤控制台日志，自动检测 JS 错误、警告
"""
import asyncio
import re
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, List, Dict, Callable, Literal
from playwright.async_api import Page, ConsoleMessage
from loguru import logger


LogLevel = Literal["log", "warn", "error", "info", "debug"]


@dataclass
class ConsoleLogEntry:
    """控制台日志条目"""
    level: str
    message: str
    timestamp: datetime = field(default_factory=datetime.now)
    url: Optional[str] = None
    line_number: Optional[int] = None
    column_number: Optional[int] = None
    stack_trace: Optional[str] = None


@dataclass
class ConsoleFilterConfig:
    """日志过滤配置"""
    include: Optional[List[re.Pattern]] = None
    exclude: Optional[List[re.Pattern]] = None


@dataclass
class ConsoleMonitorConfig:
    """控制台监控配置"""
    levels: List[str] = field(default_factory=lambda: ["log", "warn", "error", "info", "debug"])
    capture_stack_trace: bool = True
    filters: Optional[ConsoleFilterConfig] = None


@dataclass
class ConsoleMonitorResult:
    """控制台监控结果"""
    total_count: int = 0
    by_level: Dict[str, int] = field(default_factory=dict)
    errors: List[ConsoleLogEntry] = field(default_factory=list)
    warnings: List[ConsoleLogEntry] = field(default_factory=list)
    logs: List[ConsoleLogEntry] = field(default_factory=list)


class ConsoleMonitor:
    """
    控制台监控器
    捕获浏览器控制台日志
    """

    def __init__(
        self,
        page: Page,
        config: Optional[ConsoleMonitorConfig] = None
    ):
        self.page = page
        self.config = config or ConsoleMonitorConfig()
        self._logs: List[ConsoleLogEntry] = []
        self._is_monitoring = False
        self._handlers: List[tuple] = []

    def start(self) -> None:
        """开始监控"""
        if self._is_monitoring:
            return

        self._logs = []
        self._is_monitoring = True

        # 监听控制台消息
        async def console_handler(msg: ConsoleMessage):
            await self._handle_console_message(msg)

        self.page.on("console", lambda msg: asyncio.create_task(console_handler(msg)))
        self._handlers.append(("console", console_handler))

        # 监听页面错误
        async def error_handler(error: Exception):
            self._add_log(ConsoleLogEntry(
                level="error",
                message=str(error),
                stack_trace=getattr(error, '__traceback__', None),
            ))

        self.page.on("pageerror", lambda e: asyncio.create_task(error_handler(e)))
        self._handlers.append(("pageerror", error_handler))

        logger.debug("控制台监控已启动")

    def stop(self) -> None:
        """停止监控"""
        if not self._is_monitoring:
            return

        self._is_monitoring = False
        self._handlers.clear()
        logger.debug("控制台监控已停止")

    async def _handle_console_message(self, msg: ConsoleMessage) -> None:
        """处理控制台消息"""
        level = msg.type

        # 过滤级别
        if level not in self.config.levels:
            return

        message = msg.text

        # 应用过滤规则
        if self.config.filters:
            include = self.config.filters.include
            exclude = self.config.filters.exclude

            if exclude and any(p.search(message) for p in exclude):
                return
            if include and not any(p.search(message) for p in include):
                return

        entry = ConsoleLogEntry(
            level=level,
            message=message,
        )

        # 获取位置信息
        try:
            location = msg.location
            if location:
                entry.url = location.get("url")
                entry.line_number = location.get("lineNumber")
                entry.column_number = location.get("columnNumber")
        except Exception:
            pass

        self._add_log(entry)

    def _add_log(self, entry: ConsoleLogEntry) -> None:
        """添加日志条目"""
        self._logs.append(entry)

    # ==================== 获取日志 ====================

    def get_logs(self) -> List[ConsoleLogEntry]:
        """获取所有日志"""
        return list(self._logs)

    def get_errors(self) -> List[ConsoleLogEntry]:
        """获取错误日志"""
        return [log for log in self._logs if log.level == "error"]

    def get_warnings(self) -> List[ConsoleLogEntry]:
        """获取警告日志"""
        return [log for log in self._logs if log.level == "warn"]

    def get_logs_by_level(self, level: str) -> List[ConsoleLogEntry]:
        """获取指定级别的日志"""
        return [log for log in self._logs if log.level == level]

    def get_result(self) -> ConsoleMonitorResult:
        """获取结果统计"""
        by_level: Dict[str, int] = {
            "log": 0,
            "warn": 0,
            "error": 0,
            "info": 0,
            "debug": 0,
        }

        for log in self._logs:
            if log.level in by_level:
                by_level[log.level] += 1

        return ConsoleMonitorResult(
            total_count=len(self._logs),
            by_level=by_level,
            errors=self.get_errors(),
            warnings=self.get_warnings(),
            logs=self.get_logs(),
        )

    def has_errors(self) -> bool:
        """检查是否有错误"""
        return any(log.level == "error" for log in self._logs)

    def has_warnings(self) -> bool:
        """检查是否有警告"""
        return any(log.level == "warn" for log in self._logs)

    def get_error_count(self) -> int:
        """获取错误数量"""
        return len(self.get_errors())

    def get_warning_count(self) -> int:
        """获取警告数量"""
        return len(self.get_warnings())

    # ==================== 日志操作 ====================

    def clear(self) -> None:
        """清空日志"""
        self._logs = []

    def export_json(self) -> str:
        """导出日志为 JSON"""
        import json
        return json.dumps([
            {
                "level": log.level,
                "message": log.message,
                "timestamp": log.timestamp.isoformat(),
                "url": log.url,
                "line": log.line_number,
                "column": log.column_number,
            }
            for log in self._logs
        ], ensure_ascii=False, indent=2)

    def export_text(self) -> str:
        """导出日志为文本"""
        lines = []
        for log in self._logs:
            timestamp = log.timestamp.isoformat()
            location = ""
            if log.url:
                location = f" ({log.url}:{log.line_number}:{log.column_number})"
            lines.append(f"[{timestamp}] [{log.level.upper()}]{location} {log.message}")
        return "\n".join(lines)

    def print_summary(self) -> None:
        """打印日志摘要"""
        result = self.get_result()

        print("\n=== 控制台监控摘要 ===")
        print(f"总日志数: {result.total_count}")
        print(f"  - 错误: {result.by_level.get('error', 0)}")
        print(f"  - 警告: {result.by_level.get('warn', 0)}")
        print(f"  - 信息: {result.by_level.get('info', 0)}")
        print(f"  - 调试: {result.by_level.get('debug', 0)}")
        print(f"  - 日志: {result.by_level.get('log', 0)}")

        if result.errors:
            print("\n错误详情:")
            for i, err in enumerate(result.errors, 1):
                print(f"  {i}. {err.message}")
                if err.url:
                    print(f"     位置: {err.url}:{err.line_number}")

    # ==================== 等待方法 ====================

    async def wait_for_log(
        self,
        pattern: str,
        timeout: float = 30000,
        level: Optional[str] = None,
    ) -> Optional[ConsoleLogEntry]:
        """
        等待特定日志

        Args:
            pattern: 日志匹配模式
            timeout: 超时时间（毫秒）
            level: 日志级别

        Returns:
            Optional[ConsoleLogEntry]: 匹配的日志条目
        """
        regex = re.compile(pattern) if isinstance(pattern, str) else pattern
        start_time = asyncio.get_event_loop().time()

        while (asyncio.get_event_loop().time() - start_time) * 1000 < timeout:
            for log in self._logs:
                if level and log.level != level:
                    continue
                if regex.search(log.message):
                    return log

            await asyncio.sleep(0.1)

        return None

    async def wait_for_error(self, timeout: float = 30000) -> Optional[ConsoleLogEntry]:
        """等待错误出现"""
        return await self.wait_for_log(r".*", timeout=timeout, level="error")

    # ==================== 断言方法 ====================

    def assert_no_errors(self) -> None:
        """断言无错误"""
        errors = self.get_errors()
        if errors:
            messages = "\n  ".join(e.message for e in errors)
            raise AssertionError(f"控制台存在错误:\n  {messages}")

    def assert_no_warnings(self) -> None:
        """断言无警告"""
        warnings = self.get_warnings()
        if warnings:
            messages = "\n  ".join(w.message for w in warnings)
            raise AssertionError(f"控制台存在警告:\n  {messages}")

    def assert_contains(self, pattern: str, level: Optional[str] = None) -> None:
        """断言包含日志"""
        regex = re.compile(pattern)
        logs = self.get_logs_by_level(level) if level else self._logs

        if not any(regex.search(log.message) for log in logs):
            raise AssertionError(f"未找到匹配的日志: {pattern}")


def create_console_monitor(
    page: Page,
    config: Optional[ConsoleMonitorConfig] = None
) -> ConsoleMonitor:
    """创建控制台监控器实例"""
    return ConsoleMonitor(page, config)
