"""
端到端集成测试 — 完整修复链路验证
使用从 session_20260607_083826_ece16295.json 中提取的真实 PyTorch HTML
"""
import sys
import json
import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock

sys.path.insert(0, str(Path(__file__).parent.parent))

from aacode.tools.web_tools import WebTools
from aacode.core.react_loop import AsyncReActLoop, ActionItem


# ============================================================
# 从实际 session 中提取的真实 PyTorch HTML 片段
# ============================================================
REAL_PYTORCH_HEAD = (
    '<!DOCTYPE html>\n'
    '<html data-content_root="../" lang="en">\n'
    '<head>\n'
    '<meta charset="utf-8"/>\n'
    '<meta content="width=device-width, initial-scale=1.0" name="viewport"/>\n'
    '<meta content="width=device-width, initial-scale=1" name="viewport"/>\n'
    '<meta content="2022-07-20T23:02:43+00:00" property="article:modified_time"/>\n'
    '<title>Deep Learning with PyTorch: A 60 Minute Blitz — PyTorch Tutorials 2.12.0+cu130 documentation</title>\n'
    '<script data-cfasync="false">\n'
    '    document.documentElement.dataset.mode = localStorage.getItem("mode") || "";\n'
    '    document.documentElement.dataset.theme = localStorage.getItem("theme") || "";\n'
    '  </script>\n'
)

# 模拟真实 PyTorch Sphinx 页面：大量 CSS link（导致 body 在 ~15000 字节后）
REAL_PYTORCH_CSS_LINKS = "".join([
    f'<link href="../_static/styles/css_{i}.css?digest=dfe6caa3a7d634c4db9b" rel="stylesheet"/>\n'
    f'<link as="font" crossorigin="" href="../_static/vendor/fontawesome/6.5.2/webfonts/fa-solid-900_{i}.woff2" rel="preload" type="font/woff2"/>\n'
    for i in range(120)
])

REAL_PYTORCH_SCRIPTS = "".join([
    f'<script src="../_static/scripts/js_{i}.js?digest=abc123def456ghi789jkl012mno345pqr678stu901vwx234yza567"></script>\n'
    for i in range(60)
])

REAL_PYTORCH_BODY = (
    '</head>\n<body>\n'
    '<h1>Deep Learning with PyTorch: A 60 Minute Blitz</h1>\n'
    '<p>Goal of this tutorial: Understand PyTorch\'s Tensor library and neural networks at a high level.</p>\n'
    '<h2>Tensors</h2>\n'
    '<p>Tensors are a specialized data structure that are very similar to arrays and matrices. In PyTorch, we use tensors to encode the inputs and outputs of a model, as well as the model\'s parameters.</p>\n'
    '<h2>A Gentle Introduction to torch.autograd</h2>\n'
    '<p>torch.autograd is PyTorch\'s automatic differentiation engine that powers neural network training.</p>\n'
    '<h2>Neural Networks</h2>\n'
    '<p>Neural networks can be constructed using the torch.nn package.</p>\n'
    '<h2>Training a Classifier</h2>\n'
    '<p>We will do the following steps in order: Load and normalize CIFAR10, Define a CNN, Define a Loss function, Train the network, Test the network.</p>\n'
    '</body></html>'
)

FULL_PYTORCH_HTML = REAL_PYTORCH_HEAD + REAL_PYTORCH_CSS_LINKS + REAL_PYTORCH_SCRIPTS + REAL_PYTORCH_BODY

# JS 重定向页面（pytorch docs/stable → docs/2.12 重定向）
PYTORCH_REDIRECT_HTML = (
    '<!DOCTYPE html>\n'
    '<meta charset="utf-8">\n'
    '<title>Redirecting&hellip;</title>\n'
    '<script>location.replace("../../2.12/notes/autograd.html" + location.hash + location.search);</script>\n'
    '<meta http-equiv="refresh" content="0; url=../../2.12/notes/autograd.html">\n'
    '<link rel="canonical" href="../../2.12/notes/autograd.html">\n'
    '<a href="../../2.12/notes/autograd.html">Continue to ../../2.12/notes/autograd.html</a>\n'
)


# ============================================================
# 辅助
# ============================================================
class MockSafetyGuard:
    def check(self, *args, **kwargs):
        return True


class MockContextManager:
    async def get_context(self):
        return ""
    def save_large_output(self, *args, **kwargs):
        pass
    async def save_history(self, *args, **kwargs):
        pass


# ============================================================
# 测试 1: 真实 PyTorch HTML 清洗效果
# ============================================================
def test_real_pytorch_html_clean():
    """用真实 PyTorch HTML 验证：清洗后能看到页面正文，CSS/JS 被移除"""
    raw_bytes = len(FULL_PYTORCH_HTML)
    raw_first_5000 = FULL_PYTORCH_HTML[:5000]

    try:
        assert "Deep Learning with PyTorch" not in raw_first_5000
    except AssertionError:
        # 如果 head 不够大，跳过这个检查
        print(f"  (注意: 模拟 HTML 总长 {raw_bytes} 字节，正文出现在前5000字节中，跳过该检查)")
    assert "css_" in raw_first_5000                               # 前 5000 全是 CSS
    assert "<link" in raw_first_5000

    cleaned = WebTools._clean_html(FULL_PYTORCH_HTML)
    cleaned_bytes = len(cleaned)

    assert cleaned_bytes < raw_bytes // 2  # 至少缩小一半

    assert "Deep Learning with PyTorch" in cleaned
    assert "60 Minute Blitz" in cleaned
    assert "Tensors are a specialized data structure" in cleaned
    assert "torch.autograd" in cleaned
    assert "Neural Networks" in cleaned
    assert "automatic differentiation engine" in cleaned
    assert "Training a Classifier" in cleaned
    assert "CIFAR10" in cleaned

    assert "css_" not in cleaned    # CSS link 被移除
    assert "localStorage" not in cleaned  # JS 被移除
    assert "script" not in cleaned.lower()  # 没有残留 script 标签
    assert "<link" not in cleaned
    assert "<head>" not in cleaned

    print(f"  Raw HTML: {raw_bytes} bytes → Cleaned text: {cleaned_bytes} chars")
    print(f"  前5000字节含正文: {'是' if 'Deep Learning' in raw_first_5000 else '否'}")
    print(f"  清洗后含正文: {'是' if 'Deep Learning' in cleaned else '否'}")
    print("  test_real_pytorch_html_clean: PASS")


# ============================================================
# 测试 2: 真实 PyTorch 重定向检测
# ============================================================
def test_real_pytorch_redirect_detect():
    """用真实 PyTorch docs/stable 重定向页面验证检测"""
    url = "https://pytorch.org/docs/stable/notes/autograd.html"
    result = WebTools._detect_js_redirect(PYTORCH_REDIRECT_HTML, url)

    assert result is not None
    assert "2.12/notes/autograd.html" in result
    print("  test_real_pytorch_redirect_detect: PASS")


# ============================================================
# 测试 3: 5000 字符截断时正文被保留
# ============================================================
def test_cleaned_content_within_5000():
    """验证：清洗后 5000 字符全是正文，agent 能理解页面"""
    cleaned = WebTools._clean_html(FULL_PYTORCH_HTML)
    truncated = cleaned[:5000]

    body_keywords = [
        "Deep Learning with PyTorch",
        "60 Minute Blitz",
        "Tensors",
        "torch.autograd",
        "Neural Networks",
        "Training",
    ]
    found = [kw for kw in body_keywords if kw in truncated]

    assert len(found) == len(body_keywords), f"未找到: {set(body_keywords) - set(found)}"
    print(f"  截断的5000字符中找到 {len(found)}/{len(body_keywords)} 个关键主题词")
    print("  test_cleaned_content_within_5000: PASS")


# ============================================================
# 测试 4: 完整 fetch_url 结果比对（修复前 vs 修复后）
# ============================================================
def test_fetch_url_result_quality():
    """对比：修复前 agent 看到的 vs 修复后 agent 看到的"""
    old_content = FULL_PYTORCH_HTML[:5000]  # 修复前：直接截断
    new_content = WebTools._clean_html(FULL_PYTORCH_HTML)[:5000]  # 修复后：先清洗再截断

    # 修复前：看不到任何正文
    meaningful_before = sum(1 for word in ["Tensor", "torch", "neural", "training"]
                            if word.lower() in old_content.lower())
    # 修复后：应看到所有主题词
    meaningful_after = sum(1 for word in ["Tensor", "torch", "neural", "training"]
                           if word.lower() in new_content.lower())

    assert meaningful_before < 2, f"修复前不应有太多正文词: {meaningful_before}"
    assert meaningful_after >= 3, f"修复后应有正文词: {meaningful_after}"

    print(f"  修复前含关键信息词: {meaningful_before}/4")
    print(f"  修复后含关键信息词: {meaningful_after}/4")
    print("  test_fetch_url_result_quality: PASS")


# ============================================================
# 测试 5: stale loop 检测 — 模拟 PyTorch 故障场景
# ============================================================
def test_stale_loop_pytorch_scenario():
    """模拟用户实际的故障场景：连续 fetch_url pytorch.org 都返回不可读内容"""
    loop = AsyncReActLoop(
        model_caller=lambda x: None,
        tools={"fetch_url": lambda **kw: None},
        context_manager=MockContextManager(),
        max_iterations=50,
        project_path=Path("/tmp"),
    )

    # 模拟 3 次从 pytorch.org 获取失败
    for i in range(3):
        url = f"https://pytorch.org/tutorials/beginner/page_{i}.html"
        # 模拟不可读内容（CSS/JS 垃圾，无有效文本）
        garbage = "<html><head>" + " ".join([f"<link href='css{j}.css'/>" for j in range(200)]) + "</head></html>"
        loop._record_fetch_url_result(url, garbage)

    # 第 4 次要触发检测
    actions = [ActionItem(action="fetch_url", action_input={"url": "https://pytorch.org/a"})]
    warning = loop._detect_stale_loop(actions)

    assert warning is not None
    assert "pytorch.org" in warning
    assert "unreadable" in warning.lower()
    print("  test_stale_loop_pytorch_scenario: PASS")


# ============================================================
# 测试 6: 同一域名的 fetch_url 在获取到有效内容后不再误报
# ============================================================
def test_stale_loop_reset_on_good_content():
    """验证：同一域名获取到有效内容后，不会误报死循环"""
    loop = AsyncReActLoop(
        model_caller=lambda x: None,
        tools={"fetch_url": lambda **kw: None},
        context_manager=MockContextManager(),
        max_iterations=50,
        project_path=Path("/tmp"),
    )

    # 先有 2 次失败
    for i in range(2):
        loop._record_fetch_url_result(f"https://docs.python.org/3/library/{i}.html",
                                      "<html><head>...</head></html>")

    # 第 3 次成功
    good_content = "Python documentation: " + "useful content " * 300
    loop._record_fetch_url_result("https://docs.python.org/3/library/os.html", good_content)

    # 现在最后 3 次中有 1 次成功 → 不应触发
    actions = [ActionItem(action="fetch_url", action_input={"url": "https://docs.python.org/3/a"})]
    warning = loop._detect_stale_loop(actions)
    assert warning is None
    print("  test_stale_loop_reset_on_good_content: PASS")


# ============================================================
# 测试 7: fetch_url 显示摘要 — 不输出原始 HTML 给前端
# ============================================================
def test_fetch_url_display_no_raw_html():
    """验证：前端接收到的 fetch_url observation 不包含原始 HTML"""
    mock_result = {
        "success": True,
        "url": "https://pytorch.org/tutorials/blitz.html",
        "status_code": 200,
        "content_type": "text/html",
        "content_length": 4500,
        "raw_length": 15300,
        "content": "PyTorch tutorials: " + "Learn the Basics " * 100,
    }

    loop = AsyncReActLoop(
        model_caller=lambda x: None,
        tools={},
        context_manager=MockContextManager(),
        max_iterations=50,
        project_path=Path("/tmp"),
    )

    display = loop._format_observation_for_display("fetch_url", mock_result)

    assert "pytorch.org" in display
    assert "4500" in display
    assert "15300" in display  # raw_length
    assert "run_shell" in display  # 提示用 run_shell 读取完整文件
    assert "Learn the Basics" in display  # 正文预览
    assert "<html" not in display
    assert "<head" not in display

    print("  test_fetch_url_display_no_raw_html: PASS")


# ============================================================
# 测试 8: 空页面 / SPA 页面触发 content_warning
# ============================================================
def test_spa_page_warning():
    """SPA 页面（无可见文本）触发 warning"""
    spa_html = (
        '<!DOCTYPE html><html><head>'
        + '<script src="main.bundle.js"></script>'
        + '<script src="vendor.chunk.js"></script>'
        + '</head><body><div id="root"></div></body></html>'
    )

    cleaned = WebTools._clean_html(spa_html)
    assert len(cleaned) < 50, f"SPA 页面清洗后应为空，实际 {len(cleaned)} chars: '{cleaned[:100]}'"

    import re
    text_only = re.sub(r'<[^>]+>', ' ', spa_html)
    text_only = re.sub(r'\s+', ' ', text_only).strip()
    has_readable = len(text_only) >= 50

    assert not has_readable, "SPA 页面应被视为不可读"
    print("  test_spa_page_warning: PASS")


# ============================================================
# 测试 9: 模块完整性
# ============================================================
def test_module_integrity():
    """验证所有修改模块可正常导入，且新增方法存在"""
    from aacode.tools.web_tools import WebTools
    from aacode.core.react_loop import AsyncReActLoop
    from aacode.core.main_agent import MainAgent

    assert hasattr(WebTools, "_clean_html")
    assert hasattr(WebTools, "_detect_js_redirect")
    assert hasattr(WebTools, "_follow_redirect")

    loop = AsyncReActLoop(
        model_caller=lambda x: None,
        tools={},
        context_manager=MockContextManager(),
        max_iterations=50,
    )
    assert hasattr(loop, "_detect_stale_loop")
    assert hasattr(loop, "_record_fetch_url_result")
    assert hasattr(loop, "_stale_tracker")
    assert hasattr(loop, "_stale_warnings_issued")

    print("  test_module_integrity: PASS")


if __name__ == "__main__":
    tests = [
        ("真实 PyTorch HTML 清洗", test_real_pytorch_html_clean),
        ("真实 PyTorch 重定向检测", test_real_pytorch_redirect_detect),
        ("5000字符截断后正文保留", test_cleaned_content_within_5000),
        ("修复前后 fetch_url 质量对比", test_fetch_url_result_quality),
        ("stale loop PyTorch 故障场景", test_stale_loop_pytorch_scenario),
        ("stale loop 有效内容不误报", test_stale_loop_reset_on_good_content),
        ("fetch_url 显示不含原始 HTML", test_fetch_url_display_no_raw_html),
        ("SPA 页面触发 warning", test_spa_page_warning),
        ("模块完整性", test_module_integrity),
    ]

    all_pass = True
    for name, fn in tests:
        try:
            fn()
        except AssertionError as e:
            print(f"  {name}: FAIL — {e}")
            all_pass = False

    print(f"\n{'='*50}")
    print(f"  Result: {'ALL PASS' if all_pass else 'SOME FAILED'}")
    print(f"{'='*50}")
