#!/usr/bin/env python3
"""
Test script to validate parallel performance improvements in CutySoup

This script tests the new ParallelTreeWalker implementation and compares
performance between sequential and parallel search operations.
"""

import time
import sys
import os
from typing import List, Dict, Any

# 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"❌ CutySoup not available: {e}")
    CUTYSOUP_AVAILABLE = False

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


def create_large_test_document(article_count: int = 500) -> str:
    """Create a large test document for performance testing."""
    content_parts = ["""
    <!DOCTYPE html>
    <html>
    <head><title>Parallel Performance Test Document</title></head>
    <body>
        <div class="container">
            <header class="site-header">
                <h1 id="main-title">CutySoup Parallel Performance Test</h1>
                <nav class="navigation">
                    <ul class="nav-list">
                        <li><a href="/home" class="nav-link">Home</a></li>
                        <li><a href="/about" class="nav-link">About</a></li>
                        <li><a href="/contact" class="nav-link">Contact</a></li>
                    </ul>
                </nav>
            </header>
            <main class="content">
    """]
    
    # Add many articles for performance testing
    for i in range(article_count):
        content_parts.append(f"""
            <article class="post" id="post-{i}" data-category="tech">
                <header class="post-header">
                    <h2 class="post-title">Technology Post {i}</h2>
                    <div class="post-meta">
                        <span class="author" data-id="{i}">Author {i}</span>
                        <time class="date">2024-01-{(i % 31) + 1:02d}</time>
                        <span class="category">Technology</span>
                    </div>
                </header>
                <div class="post-content">
                    <p class="excerpt">This is an excerpt for technology post {i}.</p>
                    <div class="post-body">
                        <p class="paragraph">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
                        <p class="paragraph">Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                        <ul class="feature-list">
                            <li class="feature">Feature {i}-1</li>
                            <li class="feature">Feature {i}-2</li>
                            <li class="feature">Feature {i}-3</li>
                        </ul>
                    </div>
                </div>
                <footer class="post-footer">
                    <div class="tags">
                        <span class="tag">tag{i % 10}</span>
                        <span class="tag">category{i % 5}</span>
                    </div>
                    <div class="actions">
                        <a href="/post/{i}" class="read-more">Read More</a>
                        <a href="/post/{i}/edit" class="edit-link">Edit</a>
                    </div>
                </footer>
            </article>
        """)
    
    content_parts.append("""
            </main>
            <aside class="sidebar">
                <div class="widget recent-posts">
                    <h3>Recent Posts</h3>
                    <ul class="post-list">
                        <li><a href="/recent1">Recent Post 1</a></li>
                        <li><a href="/recent2">Recent Post 2</a></li>
                        <li><a href="/recent3">Recent Post 3</a></li>
                    </ul>
                </div>
            </aside>
            <footer class="site-footer">
                <div class="footer-content">
                    <p class="copyright">© 2024 CutySoup Performance Test</p>
                </div>
            </footer>
        </div>
    </body>
    </html>
    """)
    
    return "".join(content_parts)


def benchmark_operation(name: str, operation, iterations: int = 5) -> Dict[str, Any]:
    """Benchmark an operation and return performance statistics."""
    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)
    min_time = min(times)
    max_time = max(times)
    avg_results = sum(results) / len(results)
    
    return {
        'name': name,
        'avg_time_ms': avg_time,
        'min_time_ms': min_time,
        'max_time_ms': max_time,
        'avg_results': avg_results,
        'all_times': times
    }


def test_parallel_vs_sequential():
    """Test parallel vs sequential performance."""
    print("Testing Parallel vs Sequential Performance")
    print("=" * 60)
    
    # Create test document
    print("Creating test document with 500 articles...")
    html = create_large_test_document(500)
    soup = CutySoup(html)
    
    print(f"[OK] Document created ({len(html):,} characters)")
    print()
    
    # Get the root node for C++ operations
    root_node = soup._tree.get_root()
    
    # Test operations
    test_operations = [
        {
            'name': 'Find all <p> tags',
            'sequential': lambda: root_node.find_all('p'),
            'parallel': lambda: root_node.find_all_parallel('p'),
        },
        {
            'name': 'Find all .post-title class',
            'sequential': lambda: root_node.find_all('', {'class': 'post-title'}),
            'parallel': lambda: root_node.find_all_parallel('', {'class': 'post-title'}),
        },
        {
            'name': 'Find all articles',
            'sequential': lambda: root_node.find_all('article'),
            'parallel': lambda: root_node.find_all_parallel('article'),
        },
        {
            'name': 'Find by ID (single result)',
            'sequential': lambda: root_node.find('', {'id': 'main-title'}),
            'parallel': lambda: root_node.find_parallel('', {'id': 'main-title'}),
        },
        {
            'name': 'Find all .feature class',
            'sequential': lambda: root_node.find_all('', {'class': 'feature'}),
            'parallel': lambda: root_node.find_all_parallel('', {'class': 'feature'}),
        }
    ]
    
    results = []
    
    for test_op in test_operations:
        print(f"Testing: {test_op['name']}")
        
        # Test sequential operation
        try:
            seq_result = benchmark_operation(f"{test_op['name']} (Sequential)", test_op['sequential'])
            print(f"   Sequential: {seq_result['avg_time_ms']:.2f}ms (avg), {seq_result['avg_results']:.0f} results")
        except Exception as e:
            print(f"   Sequential: [ERROR] {e}")
            seq_result = None
        
        # Test parallel operation
        try:
            par_result = benchmark_operation(f"{test_op['name']} (Parallel)", test_op['parallel'])
            print(f"   Parallel:   {par_result['avg_time_ms']:.2f}ms (avg), {par_result['avg_results']:.0f} results")
            
            # Calculate speedup
            if seq_result:
                speedup = seq_result['avg_time_ms'] / par_result['avg_time_ms']
                if speedup > 1.0:
                    print(f"   [SPEEDUP] {speedup:.2f}x faster")
                else:
                    print(f"   [SLOWDOWN] {1/speedup:.2f}x slower")
            
        except Exception as e:
            print(f"   Parallel:   [ERROR] {e}")
            par_result = None
        
        print()
        
        if seq_result and par_result:
            results.append({
                'operation': test_op['name'],
                'sequential': seq_result,
                'parallel': par_result,
                'speedup': seq_result['avg_time_ms'] / par_result['avg_time_ms']
            })
    
    # Summary
    print("Performance Summary")
    print("=" * 60)
    
    if results:
        total_seq_time = sum(r['sequential']['avg_time_ms'] for r in results)
        total_par_time = sum(r['parallel']['avg_time_ms'] for r in results)
        overall_speedup = total_seq_time / total_par_time
        
        print(f"Total Sequential Time: {total_seq_time:.2f}ms")
        print(f"Total Parallel Time:   {total_par_time:.2f}ms")
        print(f"Overall Speedup:       {overall_speedup:.2f}x")
        print()
        
        # Best and worst performing operations
        best_speedup = max(results, key=lambda x: x['speedup'])
        worst_speedup = min(results, key=lambda x: x['speedup'])
        
        print(f"Best Speedup: {best_speedup['operation']} ({best_speedup['speedup']:.2f}x)")
        print(f"Worst Speedup: {worst_speedup['operation']} ({worst_speedup['speedup']:.2f}x)")
        
        # Identify if parallel processing is beneficial
        improvements = [r for r in results if r['speedup'] > 1.0]
        print(f"Operations with improvement: {len(improvements)}/{len(results)}")
        
        if len(improvements) >= len(results) // 2:
            print("[SUCCESS] Parallel processing shows significant benefits!")
        else:
            print("[WARN] Parallel processing needs optimization for this workload")
    else:
        print("[ERROR] No successful test results")
    
    return results


def test_thread_scaling():
    """Test how performance scales with different thread counts."""
    print("\nTesting Thread Scaling Performance")
    print("=" * 60)
    
    # This would require exposing thread count configuration in the C++ API
    # For now, we'll print a placeholder
    print("[WARN] Thread scaling test requires thread count configuration API")
    print("   This can be implemented in future iterations")


def test_memory_usage():
    """Test memory usage of parallel vs sequential operations."""
    print("\nTesting Memory Usage")
    print("=" * 60)
    
    try:
        import psutil
        import gc
        
        # Create test document
        html = create_large_test_document(200)
        soup = CutySoup(html)
        root_node = soup._tree.get_root()
        
        # Memory usage before operations
        gc.collect()
        initial_memory = psutil.Process().memory_info().rss / 1024 / 1024  # MB
        
        # Sequential search
        gc.collect()
        seq_start_memory = psutil.Process().memory_info().rss / 1024 / 1024
        results_seq = root_node.find_all('p')
        seq_end_memory = psutil.Process().memory_info().rss / 1024 / 1024
        seq_memory_delta = seq_end_memory - seq_start_memory
        
        # Parallel search
        gc.collect()
        par_start_memory = psutil.Process().memory_info().rss / 1024 / 1024
        results_par = root_node.find_all_parallel('p')
        par_end_memory = psutil.Process().memory_info().rss / 1024 / 1024
        par_memory_delta = par_end_memory - par_start_memory
        
        print(f"Initial Memory: {initial_memory:.1f} MB")
        print(f"Sequential Memory Delta: {seq_memory_delta:.1f} MB")
        print(f"Parallel Memory Delta: {par_memory_delta:.1f} MB")
        print(f"Results Count: seq={len(results_seq)}, par={len(results_par)}")
        
        if par_memory_delta <= seq_memory_delta * 1.5:  # Allow 50% overhead
            print("[OK] Parallel memory usage is acceptable")
        else:
            print("[WARN] Parallel memory usage is high")
        
    except ImportError:
        print("[WARN] psutil not available, skipping memory test")


def main():
    """Main test function."""
    print("CutySoup Parallel Performance Validation")
    print("=" * 60)
    print(f"Python: {sys.version}")
    print()
    
    # Check if parallel operations are available
    try:
        html = "<html><body><p>test</p></body></html>"
        soup = CutySoup(html)
        root_node = soup._tree.get_root()
        
        # Test if parallel methods exist
        if hasattr(root_node, 'find_all_parallel'):
            print("[OK] Parallel operations available")
        else:
            print("[ERROR] Parallel operations not available - C++ extension needs rebuilding")
            print("Run: uv sync --reinstall")
            return
            
        # Test basic functionality
        result = root_node.find_all_parallel('p')
        if result and len(result) > 0:
            print("[OK] Basic parallel functionality works")
        else:
            print("[WARN] Parallel operations return no results")
            
    except Exception as e:
        print(f"[ERROR] Error testing parallel operations: {e}")
        return
    
    print()
    
    # Run performance tests
    try:
        results = test_parallel_vs_sequential()
        test_memory_usage()
        test_thread_scaling()
        
        print("\n[COMPLETE] Test Complete!")
        print("Check the results above to validate parallel performance improvements.")
        
    except KeyboardInterrupt:
        print("\n[STOP] Test interrupted by user")
    except Exception as e:
        print(f"\n[ERROR] Test failed with error: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    main()