"""
性能预算检查
支持指标阈值、违规告警
"""
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any, Callable
from datetime import datetime
from playwright.async_api import Page
from loguru import logger


@dataclass
class BudgetRule:
    """预算规则"""
    name: str
    metric: str
    threshold: float
    operator: str = "<="  # <=, <, >=, >, ==
    severity: str = "error"  # error, warning, info


@dataclass
class BudgetViolation:
    """预算违规"""
    rule_name: str
    metric: str
    actual_value: float
    threshold: float
    operator: str
    severity: str
    message: str
    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())


@dataclass
class BudgetResult:
    """预算检查结果"""
    passed: bool
    total_rules: int
    passed_rules: int
    failed_rules: int
    violations: List[BudgetViolation] = field(default_factory=list)
    metrics: Dict[str, float] = field(default_factory=dict)
    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())

    @property
    def error_count(self) -> int:
        return sum(1 for v in self.violations if v.severity == "error")

    @property
    def warning_count(self) -> int:
        return sum(1 for v in self.violations if v.severity == "warning")


@dataclass
class PerformanceBudgetConfig:
    """性能预算配置"""
    rules: List[BudgetRule] = field(default_factory=list)
    on_violation: Optional[Callable[[BudgetViolation], None]] = None


# 预定义的性能预算规则
DEFAULT_RULES = [
    BudgetRule("first_contentful_paint", "firstContentfulPaint", 2000, "<=", "warning"),
    BudgetRule("largest_contentful_paint", "largestContentfulPaint", 4000, "<=", "error"),
    BudgetRule("first_input_delay", "firstInputDelay", 100, "<=", "warning"),
    BudgetRule("cumulative_layout_shift", "cumulativeLayoutShift", 0.1, "<=", "warning"),
    BudgetRule("time_to_interactive", "timeToInteractive", 5000, "<=", "error"),
    BudgetRule("total_blocking_time", "totalBlockingTime", 300, "<=", "warning"),
    BudgetRule("speed_index", "speedIndex", 4000, "<=", "warning"),
    BudgetRule("dom_size", "domSize", 1500, "<=", "info"),
    BudgetRule("resource_count", "resourceCount", 50, "<=", "info"),
    BudgetRule("js_size_kb", "jsSize", 500, "<=", "warning"),
    BudgetRule("css_size_kb", "cssSize", 100, "<=", "info"),
    BudgetRule("image_size_kb", "imageSize", 1000, "<=", "info"),
]


class PerformanceBudget:
    """
    性能预算检查器
    检查性能指标是否满足预算要求
    """

    def __init__(
        self,
        page: Page,
        config: Optional[PerformanceBudgetConfig] = None,
    ):
        self.page = page
        self.config = config or PerformanceBudgetConfig()
        if not self.config.rules:
            self.config.rules = DEFAULT_RULES.copy()
        self._last_result: Optional[BudgetResult] = None

    # ==================== 规则管理 ====================

    def add_rule(
        self,
        name: str,
        metric: str,
        threshold: float,
        operator: str = "<=",
        severity: str = "error",
    ):
        """
        添加预算规则

        Args:
            name: 规则名称
            metric: 指标名称
            threshold: 阈值
            operator: 比较操作符
            severity: 严重级别
        """
        rule = BudgetRule(name, metric, threshold, operator, severity)
        self.config.rules.append(rule)

    def remove_rule(self, name: str) -> bool:
        """移除规则"""
        for i, rule in enumerate(self.config.rules):
            if rule.name == name:
                self.config.rules.pop(i)
                return True
        return False

    def list_rules(self) -> List[BudgetRule]:
        """列出所有规则"""
        return self.config.rules.copy()

    # ==================== 检查执行 ====================

    async def check(self) -> BudgetResult:
        """
        执行性能预算检查

        Returns:
            BudgetResult: 检查结果
        """
        # 收集性能指标
        metrics = await self._collect_metrics()

        result = BudgetResult(
            total_rules=len(self.config.rules),
            passed_rules=0,
            failed_rules=0,
            metrics=metrics,
        )

        # 检查每个规则
        for rule in self.config.rules:
            actual_value = metrics.get(rule.metric)

            if actual_value is None:
                logger.warning(f"指标未收集: {rule.metric}")
                continue

            violation = self._check_rule(rule, actual_value)

            if violation:
                result.violations.append(violation)
                result.failed_rules += 1

                # 触发违规回调
                if self.config.on_violation:
                    self.config.on_violation(violation)

                logger.warning(
                    f"预算违规: {rule.name}, "
                    f"实际值={actual_value}, 阈值={rule.threshold}"
                )
            else:
                result.passed_rules += 1

        result.passed = len(result.violations) == 0
        self._last_result = result

        return result

    async def _collect_metrics(self) -> Dict[str, float]:
        """收集性能指标"""
        metrics = {}

        # Web Vitals
        web_vitals = await self.page.evaluate("""
            () => {
                return new Promise((resolve) => {
                    const metrics = {};

                    // 获取 Navigation Timing
                    const timing = performance.timing;
                    metrics['navigationStart'] = timing.navigationStart;
                    metrics['domContentLoaded'] = timing.domContentLoadedEventEnd - timing.navigationStart;
                    metrics['loadComplete'] = timing.loadEventEnd - timing.navigationStart;

                    // 获取 Paint Timing
                    const paintEntries = performance.getEntriesByType('paint');
                    paintEntries.forEach(entry => {
                        metrics[entry.name] = entry.startTime;
                    });

                    // 获取资源数量和大小
                    const resources = performance.getEntriesByType('resource');
                    metrics['resourceCount'] = resources.length;

                    let jsSize = 0, cssSize = 0, imageSize = 0;
                    resources.forEach(r => {
                        if (r.initiatorType === 'script') jsSize += r.transferSize || 0;
                        else if (r.initiatorType === 'link') cssSize += r.transferSize || 0;
                        else if (r.initiatorType === 'img') imageSize += r.transferSize || 0;
                    });
                    metrics['jsSize'] = jsSize / 1024;  // KB
                    metrics['cssSize'] = cssSize / 1024;
                    metrics['imageSize'] = imageSize / 1024;

                    // DOM 大小
                    metrics['domSize'] = document.querySelectorAll('*').length;

                    // 尝试获取 Web Vitals (如果可用)
                    if (window.performance && window.performance.getEntriesByType) {
                        const lcp = performance.getEntriesByType('largest-contentful-paint');
                        if (lcp.length) metrics['largestContentfulPaint'] = lcp[lcp.length - 1].startTime;

                        const fid = performance.getEntriesByType('first-input');
                        if (fid.length) metrics['firstInputDelay'] = fid[0].duration;
                    }

                    resolve(metrics);
                });
            }
        """)

        metrics.update(web_vitals)

        # 映射指标名称
        if 'first-paint' in metrics:
            metrics['firstPaint'] = metrics['first-paint']
        if 'first-contentful-paint' in metrics:
            metrics['firstContentfulPaint'] = metrics['first-contentful-paint']

        return metrics

    def _check_rule(
        self,
        rule: BudgetRule,
        actual_value: float,
    ) -> Optional[BudgetViolation]:
        """检查单个规则"""
        violated = False

        if rule.operator == "<=":
            violated = actual_value > rule.threshold
        elif rule.operator == "<":
            violated = actual_value >= rule.threshold
        elif rule.operator == ">=":
            violated = actual_value < rule.threshold
        elif rule.operator == ">":
            violated = actual_value <= rule.threshold
        elif rule.operator == "==":
            violated = actual_value != rule.threshold

        if violated:
            return BudgetViolation(
                rule_name=rule.name,
                metric=rule.metric,
                actual_value=actual_value,
                threshold=rule.threshold,
                operator=rule.operator,
                severity=rule.severity,
                message=f"{rule.metric}={actual_value:.2f} 预期 {rule.operator} {rule.threshold}",
            )

        return None

    # ==================== 预设规则集 ====================

    @classmethod
    def google_web_vitals(cls, page: Page) -> "PerformanceBudget":
        """Google Web Vitals 规则集"""
        rules = [
            BudgetRule("lcp", "largestContentfulPaint", 2500, "<=", "error"),
            BudgetRule("fid", "firstInputDelay", 100, "<=", "error"),
            BudgetRule("cls", "cumulativeLayoutShift", 0.1, "<=", "error"),
        ]
        config = PerformanceBudgetConfig(rules=rules)
        return cls(page, config)

    @classmethod
    def strict_budget(cls, page: Page) -> "PerformanceBudget":
        """严格预算规则集"""
        rules = [
            BudgetRule("fcp_strict", "firstContentfulPaint", 1000, "<=", "error"),
            BudgetRule("lcp_strict", "largestContentfulPaint", 2000, "<=", "error"),
            BudgetRule("tti_strict", "timeToInteractive", 3000, "<=", "error"),
            BudgetRule("dom_strict", "domSize", 1000, "<=", "warning"),
        ]
        config = PerformanceBudgetConfig(rules=rules)
        return cls(page, config)

    # ==================== 结果查询 ====================

    def get_last_result(self) -> Optional[BudgetResult]:
        """获取最后一次检查结果"""
        return self._last_result

    def get_metrics(self) -> Dict[str, float]:
        """获取收集的指标"""
        return self._last_result.metrics if self._last_result else {}


def create_performance_budget(
    page: Page,
    config: Optional[PerformanceBudgetConfig] = None,
) -> PerformanceBudget:
    """创建性能预算检查器实例"""
    return PerformanceBudget(page, config)
