"""
测试框架集成 - Pytest插件
提供pytest fixture和测试辅助
"""
import asyncio
import pytest
from typing import Optional, Callable, Generator, AsyncGenerator
from pathlib import Path
from loguru import logger

from .browser import EasyBrowser
from .config import Config
from .debug import Debugger, ErrorHandler
from .visual import VisualTester


# ==================== Pytest Fixtures ====================

@pytest.fixture(scope="session")
def browser_config() -> Config:
    """浏览器配置fixture"""
    return Config(
        headless=True,
        highlight_enabled=True,
        log_level="INFO",
    )


@pytest.fixture(scope="session")
def event_loop():
    """事件循环fixture"""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()


@pytest.fixture(scope="session")
async def browser_session(browser_config: Config):
    """会话级浏览器fixture"""
    browser = EasyBrowser(browser_config)
    await browser.start()
    
    yield browser
    
    await browser.close()


@pytest.fixture(scope="function")
async def browser(browser_session: EasyBrowser):
    """函数级浏览器fixture"""
    # 每个测试开始时导航到空白页
    await browser_session.goto("about:blank")
    
    yield browser_session
    
    # 测试结束后截图（失败时）
    # 由pytest hook处理


@pytest.fixture
async def page(browser: EasyBrowser):
    """页面对象fixture"""
    yield browser.page


@pytest.fixture
def debugger(page):
    """调试器fixture"""
    return Debugger(page)


@pytest.fixture
def visual_tester(page):
    """视觉测试fixture"""
    return VisualTester(page)


@pytest.fixture
def error_handler(page):
    """错误处理fixture"""
    return ErrorHandler(page, screenshot_on_error=True)


# ==================== Pytest Hooks ====================

def pytest_configure(config):
    """pytest配置"""
    # 注册自定义标记
    config.addinivalue_line(
        "markers", "browser: marks tests that require browser"
    )
    config.addinivalue_line(
        "markers", "visual: marks visual regression tests"
    )
    config.addinivalue_line(
        "markers", "slow: marks slow tests"
    )
    config.addinivalue_line(
        "markers", "flaky: marks flaky tests that may need retry"
    )


def pytest_collection_modifyitems(config, items):
    """修改测试集合"""
    # 为需要浏览器的测试添加标记
    for item in items:
        if "browser" in item.fixturenames:
            item.add_marker(pytest.mark.browser)


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """测试报告钩子"""
    outcome = yield
    report = outcome.get_result()
    
    # 失败时截图
    if report.when == "call" and report.failed:
        if "browser" in item.fixturenames:
            try:
                # 获取浏览器实例并截图
                loop = asyncio.get_event_loop()
                browser = loop.run_until_complete(item.funcargs.get("browser"))
                if browser:
                    screenshot_dir = Path("./test_screenshots")
                    screenshot_dir.mkdir(exist_ok=True)
                    screenshot_path = screenshot_dir / f"{item.name}_failed.png"
                    loop.run_until_complete(browser.screenshot(str(screenshot_path)))
                    logger.info(f"失败截图已保存: {screenshot_path}")
            except Exception as e:
                logger.warning(f"保存失败截图时出错: {e}")


# ==================== 测试辅助类 ====================

class TestContext:
    """测试上下文"""
    
    def __init__(self, browser: EasyBrowser):
        self.browser = browser
        self.debugger = Debugger(browser.page)
        self.error_handler = ErrorHandler(browser.page)
        self.visual_tester = VisualTester(browser.page)
        self._screenshots: list = []
    
    async def screenshot(self, name: str = None) -> str:
        """截图"""
        name = name or f"screenshot_{len(self._screenshots)}"
        path = f"./test_screenshots/{name}.png"
        await self.browser.screenshot(path)
        self._screenshots.append(path)
        return path
    
    async def assert_element_visible(self, selector: str):
        """断言元素可见"""
        from .debug import AssertionHelper
        helper = AssertionHelper(self.browser.page)
        await helper.element_visible(selector)
    
    async def assert_text_contains(self, selector: str, text: str):
        """断言文本包含"""
        from .debug import AssertionHelper
        helper = AssertionHelper(self.browser.page)
        await helper.text_contains(selector, text)


# ==================== 参数化装饰器 ====================

def browser_test(
    url: str = None,
    devices: list = None,
    browsers: list = None,
):
    """
    浏览器测试装饰器
    
    Args:
        url: 起始URL
        devices: 设备列表
        browsers: 浏览器列表
        
    Example:
        @browser_test(url="https://example.com")
        async def test_homepage(browser):
            await browser.click("button")
    """
    def decorator(func):
        # 添加标记
        func._browser_test_url = url
        func._browser_test_devices = devices
        func._browser_test_browsers = browsers
        
        # 添加pytest标记
        func = pytest.mark.browser(func)
        
        return func
    
    return decorator


def visual_test(name: str = None):
    """
    视觉测试装饰器
    
    Example:
        @visual_test("homepage")
        async def test_homepage_visual(browser):
            visual = VisualTester(browser.page)
            result = await visual.compare("homepage")
            assert result.is_match
    """
    def decorator(func):
        func._visual_test_name = name or func.__name__
        func = pytest.mark.visual(func)
        return func
    
    return decorator


def retry_flaky(max_retries: int = 3):
    """
    重试不稳定测试装饰器
    
    Example:
        @retry_flaky(max_retries=3)
        async def test_unstable_api(browser):
            ...
    """
    def decorator(func):
        func._flaky_retries = max_retries
        func = pytest.mark.flaky(func)
        return func
    
    return decorator


# ==================== 测试报告生成 ====================

class TestReport:
    """测试报告"""
    
    def __init__(self, output_dir: str = "./test_reports"):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.results: list = []
    
    def add_result(self, test_name: str, passed: bool, duration: float, error: str = None):
        """添加测试结果"""
        self.results.append({
            "name": test_name,
            "passed": passed,
            "duration": duration,
            "error": error,
        })
    
    def generate_html(self) -> str:
        """生成HTML报告"""
        total = len(self.results)
        passed = sum(1 for r in self.results if r["passed"])
        failed = total - passed
        
        html = f"""
<!DOCTYPE html>
<html>
<head>
    <title>Test Report</title>
    <style>
        body {{ font-family: Arial, sans-serif; margin: 20px; }}
        .summary {{ background: #f5f5f5; padding: 20px; margin-bottom: 20px; }}
        .pass {{ color: #4CAF50; }}
        .fail {{ color: #f44336; }}
        table {{ width: 100%; border-collapse: collapse; }}
        th, td {{ border: 1px solid #ddd; padding: 10px; text-align: left; }}
        th {{ background: #4CAF50; color: white; }}
        tr:nth-child(even) {{ background: #f9f9f9; }}
    </style>
</head>
<body>
    <h1>Test Report</h1>
    <div class="summary">
        <p>Total: {total}</p>
        <p class="pass">Passed: {passed}</p>
        <p class="fail">Failed: {failed}</p>
    </div>
    <table>
        <tr>
            <th>Test Name</th>
            <th>Status</th>
            <th>Duration</th>
            <th>Error</th>
        </tr>
        {self._generate_rows()}
    </table>
</body>
</html>
        """
        
        report_path = self.output_dir / "report.html"
        report_path.write_text(html)
        
        return str(report_path)
    
    def _generate_rows(self) -> str:
        """生成表格行"""
        rows = ""
        for result in self.results:
            status_class = "pass" if result["passed"] else "fail"
            status = "PASS" if result["passed"] else "FAIL"
            error = result.get("error", "") or ""
            
            rows += f"""
        <tr>
            <td>{result['name']}</td>
            <td class="{status_class}">{status}</td>
            <td>{result['duration']:.3f}s</td>
            <td>{error}</td>
        </tr>
            """
        
        return rows


# ==================== pytest.ini 示例 ====================

PYTEST_INI = """
[pytest]
asyncio_mode = auto
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
    browser: marks tests that require browser
    visual: marks visual regression tests
    slow: marks slow tests
    flaky: marks flaky tests

# 失败时重试
flaky_max_retries = 3

# 并行测试
addopts = -n auto --dist loadscope
"""
