#!/usr/bin/env python3
"""
Test parallel processing benefits with large documents
"""

import sys
import os
import time

# Add the package directory to the path
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))

try:
    from cutysoup import CutySoup
    CUTYSOUP_AVAILABLE = True
except ImportError as e:
    print(f"[ERROR] CutySoup not available: {e}")
    CUTYSOUP_AVAILABLE = False

if not CUTYSOUP_AVAILABLE:
    print("Please build CutySoup first: uv sync")
    sys.exit(1)


def create_large_document(element_count=1000):
    """Create a large document with many elements."""
    html_parts = ['<html><head><title>Large Document Test</title></head><body>']
    
    # Add a large navigation section
    html_parts.append('<nav class="navigation">')
    for i in range(element_count // 10):
        html_parts.append(f'<a href="/page{i}" class="nav-link" id="nav{i}">Page {i}</a>')
    html_parts.append('</nav>')
    
    # Add main content with many nested elements
    html_parts.append('<main class="content">')
    for i in range(element_count):
        html_parts.append(f'''
        <article class="post" id="post{i}" data-category="test{i % 5}">
            <header class="post-header">
                <h2 class="title">Post Title {i}</h2>
                <div class="meta">
                    <span class="author">Author {i % 10}</span>
                    <time class="date">2024-{(i % 12) + 1:02d}-01</time>
                </div>
            </header>
            <div class="content">
                <p class="intro">Introduction paragraph for post {i}.</p>
                <p class="body">Main content body for post {i} with more text.</p>
                <div class="tags">
                    <span class="tag primary">tag{i % 20}</span>
                    <span class="tag secondary">category{i % 8}</span>
                </div>
            </div>
            <footer class="post-footer">
                <a href="/post/{i}" class="read-more">Read More</a>
            </footer>
        </article>
        ''')
    html_parts.append('</main>')
    
    # Add sidebar with more elements
    html_parts.append('<aside class="sidebar">')
    for i in range(element_count // 20):
        html_parts.append(f'<div class="widget" id="widget{i}"><h3>Widget {i}</h3><p>Widget content</p></div>')
    html_parts.append('</aside>')
    
    html_parts.append('</body></html>')
    
    return ''.join(html_parts)


def benchmark_operation(name, operation, iterations=3):
    """Benchmark an operation."""
    times = []
    results = []
    
    for i in range(iterations):
        start_time = time.perf_counter()
        result = operation()
        end_time = time.perf_counter()
        
        times.append((end_time - start_time) * 1000)  # Convert to milliseconds
        results.append(len(result) if hasattr(result, '__len__') else (1 if result else 0))
    
    avg_time = sum(times) / len(times)
    avg_results = sum(results) / len(results)
    
    return {
        'name': name,
        'avg_time_ms': avg_time,
        'avg_results': avg_results,
        'times': times
    }


def test_large_document_performance():
    """Test performance with progressively larger documents."""
    print("Testing Large Document Performance")
    print("=" * 60)
    
    document_sizes = [500, 1000, 2000, 5000]
    
    for size in document_sizes:
        print(f"\nTesting with {size} elements...")
        
        # Create large document
        html = create_large_document(size)
        soup = CutySoup(html)
        root_node = soup._tree.get_root()
        
        print(f"Document size: {len(html):,} characters")
        
        # Test different operations
        test_operations = [
            ('Find all articles', lambda: root_node.find_all('article'), lambda: root_node.find_all_parallel('article')),
            ('Find all .title class', lambda: root_node.find_all('', {'class': 'title'}), lambda: root_node.find_all_parallel('', {'class': 'title'})),
            ('Find all paragraphs', lambda: root_node.find_all('p'), lambda: root_node.find_all_parallel('p')),
            ('Find by ID', lambda: root_node.find('', {'id': 'post100'}), lambda: root_node.find_parallel('', {'id': 'post100'})),
        ]
        
        for op_name, sequential_op, parallel_op in test_operations:
            try:
                # Sequential benchmark
                seq_result = benchmark_operation(f"{op_name} (Sequential)", sequential_op)
                
                # Parallel benchmark
                par_result = benchmark_operation(f"{op_name} (Parallel)", parallel_op)
                
                # Compare results
                seq_time = seq_result['avg_time_ms']
                par_time = par_result['avg_time_ms']
                seq_count = seq_result['avg_results']
                par_count = par_result['avg_results']
                
                print(f"  {op_name}:")
                print(f"    Sequential: {seq_time:.2f}ms ({seq_count:.0f} results)")
                print(f"    Parallel:   {par_time:.2f}ms ({par_count:.0f} results)")
                
                if abs(seq_count - par_count) < 0.1:  # Results should match
                    if par_time < seq_time:
                        speedup = seq_time / par_time
                        print(f"    [SPEEDUP] {speedup:.2f}x faster with parallel")
                    else:
                        slowdown = par_time / seq_time
                        print(f"    [INFO] {slowdown:.2f}x slower (overhead)")
                else:
                    print(f"    [ERROR] Result mismatch: seq={seq_count}, par={par_count}")
                    
            except Exception as e:
                print(f"  {op_name}: [ERROR] {e}")
    
    print(f"\n[INFO] For very large documents (>2000 elements), parallel processing")
    print(f"       should start showing performance benefits over sequential.")


def test_parallel_threshold():
    """Test to find the threshold where parallel becomes beneficial."""
    print("\nTesting Parallel Processing Threshold")
    print("=" * 60)
    
    thresholds = [100, 200, 500, 1000, 2000]
    
    print("Element Count | Sequential (ms) | Parallel (ms) | Speedup")
    print("-" * 60)
    
    for count in thresholds:
        html = create_large_document(count)
        soup = CutySoup(html)
        root_node = soup._tree.get_root()
        
        # Test article search (common operation)
        seq_result = benchmark_operation("Sequential", lambda: root_node.find_all('article'), iterations=3)
        par_result = benchmark_operation("Parallel", lambda: root_node.find_all_parallel('article'), iterations=3)
        
        seq_time = seq_result['avg_time_ms']
        par_time = par_result['avg_time_ms']
        speedup = seq_time / par_time
        
        print(f"{count:11d} | {seq_time:13.2f} | {par_time:11.2f} | {speedup:6.2f}x")
    
    print("\n[INFO] The threshold where parallel becomes beneficial depends on:")
    print("       - CPU cores available")
    print("       - Document complexity")
    print("       - Search operation type")


def main():
    """Main test function."""
    print("CutySoup Large Document Parallel Test")
    print("=" * 60)
    
    try:
        test_large_document_performance()
        test_parallel_threshold()
        
        print("\n[COMPLETE] Large document testing complete!")
        print("Check the results above to see where parallel processing becomes beneficial.")
        
    except Exception as e:
        print(f"\n[ERROR] Test failed: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    main()