#!/usr/bin/env python3
"""
ai-learning-platform (yuzhiran.com) 浏览器自动化测试
测试 SEO 优化、页面加载、功能完整性
"""

import subprocess
import json
import re
import sys
import time
from urllib.request import urlopen, Request
from urllib.error import HTTPError, URLError
from html.parser import HTMLParser

BASE_URL = "https://yuzhiran.com"
results = []

def run_test(name, test_func):
    """运行单个测试"""
    try:
        test_func()
        results.append({"name": name, "status": "PASS", "error": None})
        print(f"  ✅ {name}")
    except AssertionError as e:
        results.append({"name": name, "status": "FAIL", "error": str(e)})
        print(f"  ❌ {name}: {e}")
    except Exception as e:
        results.append({"name": name, "status": "ERROR", "error": str(e)})
        print(f"  ⚠️  {name}: {e}")

def fetch_page(path, timeout=15):
    """获取页面内容"""
    url = f"{BASE_URL}{path}"
    req = Request(url, headers={
        "User-Agent": "Mozilla/5.0 (compatible; Hermes-Test/1.0)",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    })
    resp = urlopen(req, timeout=timeout)
    return resp.read().decode('utf-8', errors='replace'), resp

def check_meta_tags(html, path="/"):
    """检查 meta 标签"""
    # Title
    title_match = re.search(r'<title[^>]*>(.*?)</title>', html, re.DOTALL | re.IGNORECASE)
    assert title_match, f"Missing <title> tag on {path}"
    title = title_match.group(1).strip()
    assert len(title) > 5, f"Title too short: '{title}' on {path}"
    
    # Description
    desc_match = re.search(r'<meta[^>]+name=["\']description["\'][^>]+content=["\'](.*?)["\'][^>]*/?>', html, re.DOTALL | re.IGNORECASE)
    assert desc_match, f"Missing description meta tag on {path}"
    desc = desc_match.group(1).strip()
    assert len(desc) > 20, f"Description too short: '{desc[:50]}' on {path}"
    
    # Keywords
    kw_match = re.search(r'<meta[^>]+name=["\']keywords["\'][^>]+content=["\'](.*?)["\'][^>]*/?>', html, re.DOTALL | re.IGNORECASE)
    assert kw_match, f"Missing keywords meta tag on {path}"
    
    # Viewport
    vp_match = re.search(r'<meta[^>]+name=["\']viewport["\'][^>]+content=["\'](.*?)["\'][^>]*/?>', html, re.DOTALL | re.IGNORECASE)
    assert vp_match, f"Missing viewport meta tag on {path}"
    vp = vp_match.group(1).strip()
    assert "width=device-width" in vp.lower(), f"Viewport missing width=device-width on {path}"
    
    # Canonical
    canon_match = re.search(r'<link[^>]+rel=["\']canonical["\'][^>]+href=["\'](.*?)["\'][^>]*/?>', html, re.DOTALL | re.IGNORECASE)
    assert canon_match, f"Missing canonical link tag on {path}"
    
    # Open Graph
    og_match = re.search(r'<meta[^>]+property=["\']og:title["\'][^>]+content=["\'](.*?)["\'][^>]*/?>', html, re.DOTALL | re.IGNORECASE)
    assert og_match, f"Missing og:title meta tag on {path}"
    
    # Theme color
    tc_match = re.search(r'<meta[^>]+name=["\']theme-color["\'][^>]+content=["\'](.*?)["\'][^>]*/?>', html, re.DOTALL | re.IGNORECASE)
    assert tc_match, f"Missing theme-color meta tag on {path}"

def check_structured_data(html, path="/"):
    """检查结构化数据 (JSON-LD)"""
    ld_match = re.search(r'<script[^>]+type=["\']application/ld\+json["\'][^>]*>(.*?)</script>', html, re.DOTALL | re.IGNORECASE)
    assert ld_match, f"Missing JSON-LD structured data on {path}"
    ld_content = ld_match.group(1).strip()
    try:
        data = json.loads(ld_content)
        assert '@context' in data, f"JSON-LD missing @context on {path}"
        assert '@type' in data, f"JSON-LD missing @type on {path}"
    except json.JSONDecodeError:
        assert False, f"Invalid JSON-LD on {path}"

def check_performance(html, path="/"):
    """检查性能相关优化"""
    # Check for inline scripts (should be minimal)
    inline_scripts = re.findall(r'<script[^>]*>(?!.*?</script>)([^<]{100,})', html, re.DOTALL | re.IGNORECASE)
    # Large inline scripts are bad for performance
    large_scripts = [s for s in inline_scripts if len(s) > 5000]
    assert len(large_scripts) <= 2, f"Too many large inline scripts ({len(large_scripts)}) on {path}"
    
    # Check for preconnect hints
    preconnect = re.findall(r'<link[^>]+rel=["\']preconnect["\']', html, re.IGNORECASE)
    # Not required but good to have
    print(f"    Preconnect hints: {len(preconnect)}")

def check_semantic_html(html, path="/"):
    """检查语义化 HTML"""
    # Should have proper heading structure
    h1_count = len(re.findall(r'<h1[^>]*>', html, re.IGNORECASE))
    assert h1_count >= 1, f"No <h1> tag on {path}"
    
    # Should have main element or equivalent
    main_match = re.search(r'<main[^>]*>', html, re.IGNORECASE)
    nav_match = re.search(r'<nav[^>]*>', html, re.IGNORECASE)
    assert main_match or nav_match, f"No <main> or <nav> element on {path}"

def check_image_optimization(html, path="/"):
    """检查图片优化"""
    # Check for alt attributes on images
    images = re.findall(r'<img[^>]*>', html, re.IGNORECASE)
    for img in images[:10]:  # Check first 10 images
        alt_match = re.search(r'alt=["\'](.*?)["\']', img, re.IGNORECASE)
        if not alt_match:
            assert False, f"Image missing alt attribute on {path}"

def check_link_validity(html, path="/"):
    """检查链接有效性"""
    links = re.findall(r'<a[^>]+href=["\'](.*?)["\'][^>]*>', html, re.IGNORECASE)
    # Check for broken internal links
    broken = []
    for link in links[:20]:  # Check first 20 links
        if link.startswith('#') or link.startswith('mailto:') or link.startswith('tel:'):
            continue
        if link.startswith('http'):
            continue
        try:
            req = Request(f"{BASE_URL}{link}", headers={"User-Agent": "Hermes-Test/1.0"})
            resp = urlopen(req, timeout=5)
            if resp.status >= 400:
                broken.append(link)
        except:
            pass
    assert len(broken) == 0, f"Broken links found: {broken}"

# ==================== TESTS ====================

print("=" * 60)
print("🧪 ai-learning-platform (yuzhiran.com) 浏览器自动化测试")
print("=" * 60)

# 1. 首页 SEO
print("\n📄 首页 SEO 测试")
run_test("首页 SEO 优化", lambda: check_meta_tags(*fetch_page("/")))
run_test("首页结构化数据", lambda: check_structured_data(*fetch_page("/")))
run_test("首页性能检查", lambda: check_performance(*fetch_page("/")))
run_test("首页语义化 HTML", lambda: check_semantic_html(*fetch_page("/")))
run_test("首页图片优化", lambda: check_image_optimization(*fetch_page("/")))
run_test("首页链接有效性", lambda: check_link_validity(*fetch_page("/")))

# 2. 重要页面 SEO
print("\n📄 重要页面 SEO 测试")
important_pages = ["/tools", "/courses", "/skills", "/prompts", "/practices", "/sandbox", "/about"]
for page in important_pages:
    run_test(f"{page} SEO 优化", lambda p=page: check_meta_tags(*fetch_page(p)))
    run_test(f"{page} 结构化数据", lambda p=page: check_structured_data(*fetch_page(p)))

# 3. robots.txt
print("\n🤖 robots.txt 测试")
def test_robots():
    html, resp = fetch_page("/robots.txt")
    assert "User-agent" in html, "robots.txt missing User-agent"
    assert "Disallow" in html, "robots.txt missing Disallow rules"
    assert "Sitemap" in html, "robots.txt missing Sitemap"
    assert "/admin/" in html, "robots.txt should disallow /admin/"
    assert "/my/" in html, "robots.txt should disallow /my/"
test_robots()

# 4. sitemap.xml
print("\n🗺️  sitemap.xml 测试")
def test_sitemap():
    html, resp = fetch_page("/sitemap.xml")
    assert "<urlset" in html, "Invalid sitemap format"
    assert "<loc>" in html, "Sitemap missing <loc> entries"
    url_count = len(re.findall(r'<loc>', html))
    assert url_count > 10, f"Sitemap has only {url_count} URLs"
test_sitemap()

# 5. PWA manifest
print("\n📱 PWA manifest 测试")
def test_manifest():
    html, resp = fetch_page("/site.webmanifest")
    data = json.loads(html)
    assert "name" in data, "Manifest missing name"
    assert "start_url" in data, "Manifest missing start_url"
    assert "icons" in data, "Manifest missing icons"
    assert "theme_color" in data, "Manifest missing theme_color"
    assert "shortcuts" in data, "Manifest missing shortcuts"
test_manifest()

# 6. 页面加载性能
print("\n⚡ 页面加载性能测试")
def test_homepage_load():
    start = time.time()
    html, resp = fetch_page("/")
    load_time = time.time() - start
    assert load_time < 5, f"Homepage took {load_time:.2f}s to load"
    print(f"    Load time: {load_time:.2f}s")
test_homepage_load()

def test_tools_load():
    start = time.time()
    html, resp = fetch_page("/tools")
    load_time = time.time() - start
    assert load_time < 5, f"/tools took {load_time:.2f}s to load"
    print(f"    Load time: {load_time:.2f}s")
test_tools_load()

# 7. 移动端适配
print("\n📱 移动端适配测试")
def test_mobile_viewport():
    html, resp = fetch_page("/")
    vp_match = re.search(r'<meta[^>]+name=["\']viewport["\'][^>]+content=["\'](.*?)["\'][^>]*/?>', html, re.DOTALL | re.IGNORECASE)
    assert vp_match, "Missing viewport meta tag"
    vp = vp_match.group(1).strip()
    assert "initial-scale=1" in vp.lower(), "Viewport missing initial-scale"
test_mobile_viewport()

# 8. 暗黑模式支持
print("\n🌙 暗黑模式测试")
def test_dark_mode():
    html, resp = fetch_page("/")
    # Check for dark mode CSS variables or class
    dark_match = re.search(r'dark:', html)
    assert dark_match, "No dark mode CSS found"
test_dark_mode()

# 9. 国际化支持
print("\n🌐 国际化测试")
def test_i18n():
    html, resp = fetch_page("/")
    # Check for lang attribute
    lang_match = re.search(r'<html[^>]+lang=["\']zh-CN["\']', html, re.IGNORECASE)
    assert lang_match, "Missing lang='zh-CN' attribute"
test_i18n()

# 10. 安全头检查
print("\n🔒 安全头检查")
def test_security_headers():
    html, resp = fetch_page("/")
    # Check if HTTPS
    assert BASE_URL.startswith("https"), "Not using HTTPS"
    # Check for CSP (optional but good)
    # Check for HSTS (optional)
    print("    HTTPS: ✅")
test_security_headers()

# ==================== SUMMARY ====================
print("\n" + "=" * 60)
print("📊 测试总结")
print("=" * 60)

passed = sum(1 for r in results if r["status"] == "PASS")
failed = sum(1 for r in results if r["status"] == "FAIL")
errors = sum(1 for r in results if r["status"] == "ERROR")
total = len(results)

print(f"\n总计: {total} 项测试")
print(f"  ✅ 通过: {passed}")
print(f"  ❌ 失败: {failed}")
print(f"  ⚠️  错误: {errors}")
print(f"  📈 通过率: {passed/total*100:.1f}%")

if failed > 0:
    print("\n❌ 失败的测试:")
    for r in results:
        if r["status"] == "FAIL":
            print(f"  - {r['name']}: {r['error']}")

if errors > 0:
    print("\n⚠️  出错的测试:")
    for r in results:
        if r["status"] == "ERROR":
            print(f"  - {r['name']}: {r['error']}")

sys.exit(0 if failed == 0 and errors == 0 else 1)