"""
测试报告生成器
支持 HTML/JSON 报告、截图、日志收集
"""
import json
import base64
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any
from pathlib import Path
from datetime import datetime
from enum import Enum
from loguru import logger


class TestStatus(Enum):
    """测试状态"""
    PENDING = "pending"
    RUNNING = "running"
    PASSED = "passed"
    FAILED = "failed"
    SKIPPED = "skipped"


@dataclass
class TestStep:
    """测试步骤"""
    name: str
    status: str = "pending"
    duration_ms: float = 0.0
    error: Optional[str] = None
    screenshot: Optional[str] = None
    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())


@dataclass
class TestCase:
    """测试用例"""
    name: str
    status: TestStatus = TestStatus.PENDING
    duration_ms: float = 0.0
    steps: List[TestStep] = field(default_factory=list)
    error: Optional[str] = None
    error_stack: Optional[str] = None
    screenshots: List[str] = field(default_factory=list)
    logs: List[str] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)
    started_at: Optional[str] = None
    finished_at: Optional[str] = None


@dataclass
class ReportConfig:
    """报告配置"""
    output_dir: str = "./test_reports"
    report_name: str = "test_report"
    include_screenshots: bool = True
    include_logs: bool = True
    auto_screenshot_on_failure: bool = True
    embed_screenshots: bool = True  # 嵌入到 HTML 还是单独文件
    json_report: bool = True
    html_report: bool = True


@dataclass
class ReportSummary:
    """报告摘要"""
    total_tests: int = 0
    passed: int = 0
    failed: int = 0
    skipped: int = 0
    total_duration_ms: float = 0.0
    start_time: Optional[str] = None
    end_time: Optional[str] = None

    @property
    def pass_rate(self) -> float:
        if self.total_tests == 0:
            return 0.0
        return self.passed / self.total_tests * 100


class TestReporter:
    """
    测试报告生成器
    收集测试数据并生成报告
    """

    def __init__(
        self,
        page=None,
        config: Optional[ReportConfig] = None,
    ):
        self.page = page
        self.config = config or ReportConfig()
        self._test_cases: Dict[str, TestCase] = {}
        self._current_test: Optional[str] = None
        self._summary = ReportSummary()
        self._screenshots: Dict[str, bytes] = {}

    # ==================== 测试生命周期 ====================

    def start_report(self):
        """开始报告"""
        self._summary.start_time = datetime.now().isoformat()
        Path(self.config.output_dir).mkdir(parents=True, exist_ok=True)
        logger.info(f"测试报告开始: {self._summary.start_time}")

    def end_report(self):
        """结束报告"""
        self._summary.end_time = datetime.now().isoformat()

    def start_test(
        self,
        test_name: str,
        metadata: Optional[Dict[str, Any]] = None,
    ):
        """
        开始测试

        Args:
            test_name: 测试名称
            metadata: 元数据
        """
        test_case = TestCase(
            name=test_name,
            status=TestStatus.RUNNING,
            started_at=datetime.now().isoformat(),
            metadata=metadata or {},
        )
        self._test_cases[test_name] = test_case
        self._current_test = test_name
        self._summary.total_tests += 1

    def end_test(
        self,
        test_name: str,
        status: TestStatus,
        error: Optional[str] = None,
        error_stack: Optional[str] = None,
    ):
        """
        结束测试

        Args:
            test_name: 测试名称
            status: 测试状态
            error: 错误信息
            error_stack: 错误堆栈
        """
        test_case = self._test_cases.get(test_name)
        if not test_case:
            return

        test_case.status = status
        test_case.error = error
        test_case.error_stack = error_stack
        test_case.finished_at = datetime.now().isoformat()

        # 计算时长
        if test_case.started_at and test_case.finished_at:
            start = datetime.fromisoformat(test_case.started_at)
            end = datetime.fromisoformat(test_case.finished_at)
            test_case.duration_ms = (end - start).total_seconds() * 1000
            self._summary.total_duration_ms += test_case.duration_ms

        # 更新统计
        if status == TestStatus.PASSED:
            self._summary.passed += 1
        elif status == TestStatus.FAILED:
            self._summary.failed += 1
        elif status == TestStatus.SKIPPED:
            self._summary.skipped += 1

        if self._current_test == test_name:
            self._current_test = None

    # ==================== 步骤记录 ====================

    def add_step(
        self,
        step_name: str,
        status: str = "passed",
        error: Optional[str] = None,
    ):
        """
        添加步骤

        Args:
            step_name: 步骤名称
            status: 步骤状态
            error: 错误信息
        """
        if not self._current_test:
            return

        test_case = self._test_cases.get(self._current_test)
        if not test_case:
            return

        step = TestStep(
            name=step_name,
            status=status,
            error=error,
        )
        test_case.steps.append(step)

    # ==================== 截图和日志 ====================

    async def capture_screenshot(
        self,
        name: Optional[str] = None,
        test_name: Optional[str] = None,
    ) -> Optional[str]:
        """
        捕获截图

        Args:
            name: 截图名称
            test_name: 测试名称

        Returns:
            Optional[str]: 截图路径
        """
        if not self.page:
            return None

        test_name = test_name or self._current_test
        if not test_name:
            return None

        test_case = self._test_cases.get(test_name)
        if not test_case:
            return None

        screenshot_name = name or f"screenshot_{len(test_case.screenshots) + 1}"
        screenshot_data = await self.page.screenshot()

        # 保存截图
        screenshot_path = f"{screenshot_name}.png"
        self._screenshots[f"{test_name}_{screenshot_name}"] = screenshot_data

        # 存储为 base64
        if self.config.embed_screenshots:
            screenshot_b64 = base64.b64encode(screenshot_data).decode('utf-8')
            test_case.screenshots.append(f"data:image/png;base64,{screenshot_b64}")
        else:
            # 保存到文件
            screenshots_dir = Path(self.config.output_dir) / "screenshots"
            screenshots_dir.mkdir(exist_ok=True)
            file_path = screenshots_dir / f"{test_name}_{screenshot_name}.png"
            file_path.write_bytes(screenshot_data)
            test_case.screenshots.append(str(file_path))

        return screenshot_name

    async def capture_on_failure(self, test_name: Optional[str] = None):
        """失败时自动截图"""
        if self.config.auto_screenshot_on_failure:
            await self.capture_screenshot("failure", test_name)

    def add_log(
        self,
        message: str,
        level: str = "info",
        test_name: Optional[str] = None,
    ):
        """
        添加日志

        Args:
            message: 日志消息
            level: 日志级别
            test_name: 测试名称
        """
        test_name = test_name or self._current_test
        if not test_name:
            return

        test_case = self._test_cases.get(test_name)
        if not test_case:
            return

        log_entry = f"[{level.upper()}] {datetime.now().isoformat()} - {message}"
        test_case.logs.append(log_entry)

    # ==================== 报告生成 ====================

    async def generate_report(self) -> str:
        """
        生成报告

        Returns:
            str: 报告路径
        """
        self.end_report()

        output_dir = Path(self.config.output_dir)
        output_dir.mkdir(parents=True, exist_ok=True)

        # 生成 JSON 报告
        if self.config.json_report:
            json_path = output_dir / f"{self.config.report_name}.json"
            self._generate_json_report(json_path)

        # 生成 HTML 报告
        if self.config.html_report:
            html_path = output_dir / f"{self.config.report_name}.html"
            self._generate_html_report(html_path)
            return str(html_path)

        return str(output_dir)

    def _generate_json_report(self, path: Path):
        """生成 JSON 报告"""
        report = {
            "summary": {
                "total_tests": self._summary.total_tests,
                "passed": self._summary.passed,
                "failed": self._summary.failed,
                "skipped": self._summary.skipped,
                "pass_rate": self._summary.pass_rate,
                "total_duration_ms": self._summary.total_duration_ms,
                "start_time": self._summary.start_time,
                "end_time": self._summary.end_time,
            },
            "tests": [],
        }

        for test_case in self._test_cases.values():
            test_data = {
                "name": test_case.name,
                "status": test_case.status.value,
                "duration_ms": test_case.duration_ms,
                "steps": [
                    {
                        "name": step.name,
                        "status": step.status,
                        "duration_ms": step.duration_ms,
                        "error": step.error,
                    }
                    for step in test_case.steps
                ],
                "error": test_case.error,
                "started_at": test_case.started_at,
                "finished_at": test_case.finished_at,
            }
            report["tests"].append(test_data)

        with open(path, 'w', encoding='utf-8') as f:
            json.dump(report, f, ensure_ascii=False, indent=2)

        logger.info(f"JSON 报告已生成: {path}")

    def _generate_html_report(self, path: Path):
        """生成 HTML 报告"""
        # 计算状态颜色
        status_colors = {
            "passed": "#4CAF50",
            "failed": "#F44336",
            "skipped": "#FF9800",
            "running": "#2196F3",
            "pending": "#9E9E9E",
        }

        # 构建测试用例 HTML
        tests_html = ""
        for test_case in self._test_cases.values():
            status_color = status_colors.get(test_case.status.value, "#9E9E9E")

            steps_html = ""
            for step in test_case.steps:
                step_color = status_colors.get(step.status, "#9E9E9E")
                steps_html += f"""
                <div class="step" style="border-left: 3px solid {step_color}; padding-left: 10px; margin: 5px 0;">
                    <span class="step-name">{step.name}</span>
                    <span class="step-status" style="color: {step_color};">({step.status})</span>
                    {f'<span class="step-error" style="color: #F44336;"> - {step.error}</span>' if step.error else ''}
                </div>
                """

            # 截图
            screenshots_html = ""
            for i, screenshot in enumerate(test_case.screenshots, 1):
                if screenshot.startswith("data:"):
                    screenshots_html += f'<img src="{screenshot}" style="max-width: 300px; margin: 5px;">'

            tests_html += f"""
            <div class="test-case" style="border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 5px;">
                <div class="test-header" style="display: flex; justify-content: space-between; align-items: center;">
                    <h3 style="margin: 0;">{test_case.name}</h3>
                    <span style="background: {status_color}; color: white; padding: 3px 10px; border-radius: 3px;">
                        {test_case.status.value.upper()}
                    </span>
                </div>
                <div class="test-info" style="color: #666; font-size: 0.9em; margin-top: 5px;">
                    Duration: {test_case.duration_ms:.0f}ms
                </div>
                {f'<div class="test-error" style="color: #F44336; margin-top: 10px;"><strong>Error:</strong> {test_case.error}</div>' if test_case.error else ''}
                <div class="test-steps" style="margin-top: 10px; padding-left: 20px;">
                    {steps_html}
                </div>
                <div class="test-screenshots" style="margin-top: 10px;">
                    {screenshots_html}
                </div>
            </div>
            """

        # 完整 HTML
        html_content = f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Report - {self.config.report_name}</title>
    <style>
        body {{
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background: #f5f5f5;
        }}
        .header {{
            background: white;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }}
        .summary {{
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            gap: 15px;
            margin-top: 20px;
        }}
        .summary-card {{
            background: white;
            padding: 15px;
            border-radius: 8px;
            text-align: center;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }}
        .summary-card h4 {{
            margin: 0;
            color: #666;
            font-size: 0.9em;
        }}
        .summary-card .value {{
            font-size: 2em;
            font-weight: bold;
            margin-top: 5px;
        }}
        .tests-container {{
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }}
    </style>
</head>
<body>
    <div class="header">
        <h1>Test Report</h1>
        <p>Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
        <div class="summary">
            <div class="summary-card">
                <h4>Total</h4>
                <div class="value">{self._summary.total_tests}</div>
            </div>
            <div class="summary-card" style="border-top: 3px solid #4CAF50;">
                <h4>Passed</h4>
                <div class="value" style="color: #4CAF50;">{self._summary.passed}</div>
            </div>
            <div class="summary-card" style="border-top: 3px solid #F44336;">
                <h4>Failed</h4>
                <div class="value" style="color: #F44336;">{self._summary.failed}</div>
            </div>
            <div class="summary-card" style="border-top: 3px solid #FF9800;">
                <h4>Skipped</h4>
                <div class="value" style="color: #FF9800;">{self._summary.skipped}</div>
            </div>
            <div class="summary-card" style="border-top: 3px solid #2196F3;">
                <h4>Pass Rate</h4>
                <div class="value" style="color: #2196F3;">{self._summary.pass_rate:.1f}%</div>
            </div>
        </div>
    </div>
    <div class="tests-container">
        <h2>Test Cases</h2>
        {tests_html}
    </div>
</body>
</html>
        """

        with open(path, 'w', encoding='utf-8') as f:
            f.write(html_content)

        logger.info(f"HTML 报告已生成: {path}")

    # ==================== 便捷方法 ====================

    def get_summary(self) -> ReportSummary:
        """获取报告摘要"""
        return self._summary

    def get_test_case(self, test_name: str) -> Optional[TestCase]:
        """获取测试用例"""
        return self._test_cases.get(test_name)

    def list_tests(self) -> List[str]:
        """列出所有测试"""
        return list(self._test_cases.keys())


def create_test_reporter(
    page=None,
    config: Optional[ReportConfig] = None,
) -> TestReporter:
    """创建测试报告生成器实例"""
    return TestReporter(page, config)
