"""
Comprehensive benchmarking suite for Sprint 4 performance validation
"""
import time
import gc
import sys
import threading
from typing import Dict, List, Any, Optional, Callable
from dataclasses import dataclass
from pathlib import Path
import json
import statistics
from .performance import get_performance_monitor
from .performance_css import get_css_engine
from .lazy_results import get_lazy_monitor
from .memory_optimization import get_memory_optimization_stats


@dataclass
class BenchmarkResult:
    """Result of a single benchmark run."""
    name: str
    duration: float
    memory_before: int
    memory_after: int
    memory_peak: int
    operations_count: int
    throughput: float  # operations per second
    metadata: Dict[str, Any]


@dataclass
class ComparisonResult:
    """Result comparing CutySoup vs BeautifulSoup performance."""
    operation: str
    cutysoup_time: float
    beautifulsoup_time: float
    speedup: float
    cutysoup_memory: int
    beautifulsoup_memory: int
    memory_efficiency: float


class PerformanceBenchmarker:
    """Comprehensive performance benchmarking system."""
    
    def __init__(self):
        self.results: List[BenchmarkResult] = []
        self.comparisons: List[ComparisonResult] = []
        self._lock = threading.Lock()
    
    def benchmark_operation(self, name: str, operation: Callable, 
                          iterations: int = 100, warmup: int = 10) -> BenchmarkResult:
        """Benchmark a single operation with multiple iterations."""
        
        # Warmup runs
        for _ in range(warmup):
            try:
                operation()
            except Exception:
                pass
        
        # Force garbage collection before measurement
        gc.collect()
        
        # Measure memory before
        memory_before = self._get_memory_usage()
        
        # Run benchmark
        times = []
        memory_peak = memory_before
        
        for _ in range(iterations):
            start_time = time.perf_counter()
            
            try:
                operation()
            except Exception:
                # Record failed operations but continue
                times.append(float('inf'))
                continue
            
            end_time = time.perf_counter()
            times.append(end_time - start_time)
            
            # Track peak memory
            current_memory = self._get_memory_usage()
            memory_peak = max(memory_peak, current_memory)
        
        # Measure memory after
        gc.collect()
        memory_after = self._get_memory_usage()
        
        # Calculate statistics
        valid_times = [t for t in times if t != float('inf')]
        if not valid_times:
            avg_time = float('inf')
            throughput = 0.0
        else:
            avg_time = statistics.mean(valid_times)
            throughput = 1.0 / avg_time if avg_time > 0 else float('inf')
        
        result = BenchmarkResult(
            name=name,
            duration=avg_time,
            memory_before=memory_before,
            memory_after=memory_after,
            memory_peak=memory_peak,
            operations_count=len(valid_times),
            throughput=throughput,
            metadata={
                "iterations": iterations,
                "warmup": warmup,
                "success_rate": len(valid_times) / iterations,
                "min_time": min(valid_times) if valid_times else float('inf'),
                "max_time": max(valid_times) if valid_times else float('inf'),
                "std_dev": statistics.stdev(valid_times) if len(valid_times) > 1 else 0.0
            }
        )
        
        with self._lock:
            self.results.append(result)
        
        return result
    
    def compare_with_beautifulsoup(self, html_content: str, operations: Dict[str, Callable]) -> List[ComparisonResult]:
        """Compare CutySoup performance with BeautifulSoup."""
        try:
            from bs4 import BeautifulSoup
        except ImportError:
            print("BeautifulSoup4 not available for comparison")
            return []
        
        comparison_results = []
        
        for op_name, cutysoup_op in operations.items():
            # Benchmark CutySoup
            cs_result = self.benchmark_operation(f"cutysoup_{op_name}", cutysoup_op, iterations=50)
            
            # Create BeautifulSoup equivalent operation
            def bs_operation():
                BeautifulSoup(html_content, 'html.parser')
                # The operation function should be designed to work with both
                try:
                    return cutysoup_op.__code__.co_names  # Try to extract operation
                except Exception:
                    pass
            
            # Benchmark BeautifulSoup
            bs_result = self.benchmark_operation(f"beautifulsoup_{op_name}", bs_operation, iterations=50)
            
            # Calculate comparison
            speedup = bs_result.duration / cs_result.duration if cs_result.duration > 0 else float('inf')
            memory_efficiency = bs_result.memory_peak / max(cs_result.memory_peak, 1)
            
            comparison = ComparisonResult(
                operation=op_name,
                cutysoup_time=cs_result.duration,
                beautifulsoup_time=bs_result.duration,
                speedup=speedup,
                cutysoup_memory=cs_result.memory_peak,
                beautifulsoup_memory=bs_result.memory_peak,
                memory_efficiency=memory_efficiency
            )
            
            comparison_results.append(comparison)
        
        with self._lock:
            self.comparisons.extend(comparison_results)
        
        return comparison_results
    
    def _get_memory_usage(self) -> int:
        """Get current memory usage in bytes."""
        try:
            import psutil
            process = psutil.Process()
            return process.memory_info().rss
        except ImportError:
            # Fallback to basic memory tracking
            return sys.getsizeof({})  # Placeholder
    
    def generate_report(self) -> Dict[str, Any]:
        """Generate comprehensive performance report."""
        with self._lock:
            # Calculate aggregate statistics
            if self.results:
                total_operations = sum(r.operations_count for r in self.results)
                avg_throughput = statistics.mean([r.throughput for r in self.results if r.throughput != float('inf')])
                memory_savings = []
                
                for result in self.results:
                    if result.memory_before > 0:
                        savings = (result.memory_before - result.memory_after) / result.memory_before
                        memory_savings.append(savings)
                
                avg_memory_efficiency = statistics.mean(memory_savings) if memory_savings else 0.0
            else:
                total_operations = 0
                avg_throughput = 0.0
                avg_memory_efficiency = 0.0
            
            # Speedup statistics
            speedups = [c.speedup for c in self.comparisons if c.speedup != float('inf')]
            avg_speedup = statistics.mean(speedups) if speedups else 1.0
            max_speedup = max(speedups) if speedups else 1.0
            
            report = {
                "summary": {
                    "total_benchmarks": len(self.results),
                    "total_operations": total_operations,
                    "average_throughput": avg_throughput,
                    "average_memory_efficiency": avg_memory_efficiency,
                    "average_speedup": avg_speedup,
                    "maximum_speedup": max_speedup
                },
                "detailed_results": [
                    {
                        "name": r.name,
                        "duration_ms": r.duration * 1000,
                        "throughput_ops_sec": r.throughput,
                        "memory_delta_mb": (r.memory_after - r.memory_before) / (1024 * 1024),
                        "success_rate": r.metadata.get("success_rate", 1.0),
                        "std_dev_ms": r.metadata.get("std_dev", 0.0) * 1000
                    }
                    for r in self.results
                ],
                "comparisons": [
                    {
                        "operation": c.operation,
                        "speedup": c.speedup,
                        "cutysoup_time_ms": c.cutysoup_time * 1000,
                        "beautifulsoup_time_ms": c.beautifulsoup_time * 1000,
                        "memory_efficiency": c.memory_efficiency
                    }
                    for c in self.comparisons
                ],
                "system_stats": self._get_system_stats()
            }
            
            return report
    
    def _get_system_stats(self) -> Dict[str, Any]:
        """Get system performance statistics."""
        stats = {
            "python_version": sys.version,
            "platform": sys.platform,
        }
        
        # Add performance system stats
        try:
            stats["performance_monitor"] = get_performance_monitor().get_all_stats()
            stats["css_engine"] = get_css_engine().get_performance_stats()
            stats["lazy_operations"] = get_lazy_monitor().get_stats()
            stats["memory_optimization"] = get_memory_optimization_stats()
        except Exception:
            pass  # Stats not available
        
        return stats
    
    def save_report(self, filename: str):
        """Save benchmark report to file."""
        report = self.generate_report()
        
        filepath = Path(filename)
        filepath.parent.mkdir(parents=True, exist_ok=True)
        
        with open(filepath, 'w') as f:
            json.dump(report, f, indent=2, default=str)
    
    def clear_results(self):
        """Clear all benchmark results."""
        with self._lock:
            self.results.clear()
            self.comparisons.clear()


class RealWorldBenchmarks:
    """Real-world scenario benchmarks."""
    
    def __init__(self):
        self.benchmarker = PerformanceBenchmarker()
    
    def create_test_document(self, size: str = "medium") -> str:
        """Create test HTML document of specified size."""
        if size == "small":
            return self._create_small_document()
        elif size == "medium":
            return self._create_medium_document()
        elif size == "large":
            return self._create_large_document()
        else:
            return self._create_medium_document()
    
    def _create_small_document(self) -> str:
        """Create small test document (~1KB)."""
        return """
        <!DOCTYPE html>
        <html>
        <head><title>Test Document</title></head>
        <body>
            <div class="container">
                <h1 id="title">Welcome</h1>
                <p class="content">This is a test paragraph.</p>
                <ul class="list">
                    <li>Item 1</li>
                    <li>Item 2</li>
                    <li>Item 3</li>
                </ul>
            </div>
        </body>
        </html>
        """
    
    def _create_medium_document(self) -> str:
        """Create medium test document (~10KB)."""
        base = self._create_small_document()
        
        # Add repetitive content
        additional_content = []
        for i in range(50):
            additional_content.append(f"""
            <article class="post" id="post-{i}">
                <h2 class="post-title">Post Title {i}</h2>
                <div class="post-content">
                    <p class="text">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
                    <p class="text">Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                    <div class="metadata">
                        <span class="author">Author {i}</span>
                        <span class="date">2024-01-{i:02d}</span>
                        <a href="/post/{i}" class="link">Read more</a>
                    </div>
                </div>
            </article>
            """)
        
        return base.replace("</body>", "".join(additional_content) + "</body>")
    
    def _create_large_document(self) -> str:
        """Create large test document (~100KB)."""
        medium = self._create_medium_document()
        
        # Add even more content
        additional_content = []
        for i in range(200):
            additional_content.append(f"""
            <section class="section-{i % 10}">
                <div class="row">
                    <div class="col">
                        <span class="data" data-value="{i}">Value {i}</span>
                    </div>
                    <div class="col">
                        <img src="/image/{i}.jpg" alt="Image {i}" class="image">
                    </div>
                </div>
            </section>
            """)
        
        return medium.replace("</body>", "".join(additional_content) + "</body>")
    
    def benchmark_parsing(self) -> Dict[str, BenchmarkResult]:
        """Benchmark HTML parsing performance."""
        results = {}
        
        for size in ["small", "medium", "large"]:
            html = self.create_test_document(size)
            
            def parse_operation():
                import cutysoup
                soup = cutysoup.CutySoup(html)
                return soup
            
            result = self.benchmarker.benchmark_operation(
                f"parsing_{size}",
                parse_operation,
                iterations=100 if size == "small" else 50 if size == "medium" else 20
            )
            results[size] = result
        
        return results
    
    def benchmark_css_selectors(self) -> Dict[str, BenchmarkResult]:
        """Benchmark CSS selector performance."""
        html = self.create_test_document("medium")
        
        import cutysoup
        soup = cutysoup.CutySoup(html)
        
        selectors = {
            "simple_tag": "p",
            "simple_class": ".content",
            "simple_id": "#title",
            "descendant": "article p",
            "child": "div > p",
            "complex": "article.post .post-content p.text",
            "attribute": "[data-value]",
            "nth_child": "li:nth-child(2)",
            "multiple": "h1, h2, h3"
        }
        
        results = {}
        
        for name, selector in selectors.items():
            def select_operation():
                return soup.select(selector)
            
            result = self.benchmarker.benchmark_operation(
                f"css_{name}",
                select_operation,
                iterations=200
            )
            results[name] = result
        
        return results
    
    def benchmark_batch_operations(self) -> Dict[str, BenchmarkResult]:
        """Benchmark batch operations performance."""
        html = self.create_test_document("large")
        
        import cutysoup
        soup = cutysoup.CutySoup(html)
        results_set = soup.select("span.data")
        
        operations = {
            "batch_text": lambda: results_set.text() if hasattr(results_set, 'text') else [r.text for r in results_set],
            "batch_attrs": lambda: results_set.attrs('data-value') if hasattr(results_set, 'attrs') else [r.get('data-value') for r in results_set],
            "individual_text": lambda: [r.text for r in results_set],
            "individual_attrs": lambda: [r.get('data-value') for r in results_set]
        }
        
        results = {}
        
        for name, operation in operations.items():
            result = self.benchmarker.benchmark_operation(
                f"batch_{name}",
                operation,
                iterations=50
            )
            results[name] = result
        
        return results
    
    def benchmark_tree_navigation(self) -> Dict[str, BenchmarkResult]:
        """Benchmark tree navigation performance."""
        html = self.create_test_document("medium")
        
        import cutysoup
        soup = cutysoup.CutySoup(html)
        
        operations = {
            "find_all": lambda: soup.find_all("p"),
            "descendants": lambda: list(soup.descendants) if hasattr(soup, 'descendants') else [],
            "parent_access": lambda: [p.parent for p in soup.find_all("p")[:10]],
            "children_access": lambda: [div.children for div in soup.find_all("div")[:10]],
            "sibling_access": lambda: [p.next_sibling for p in soup.find_all("p")[:10]]
        }
        
        results = {}
        
        for name, operation in operations.items():
            result = self.benchmarker.benchmark_operation(
                f"nav_{name}",
                operation,
                iterations=100
            )
            results[name] = result
        
        return results

    def benchmark_bs4_compatibility(self, size: str = "medium", iterations: int = 100) -> Dict[str, Any]:
        """Benchmark bs4-compatible operations against BeautifulSoup."""
        try:
            from bs4 import BeautifulSoup
        except ImportError:
            return {
                "html_size": size,
                "iterations": iterations,
                "comparisons": [],
                "error": "BeautifulSoup4 not available",
            }

        from .core import CutySoup

        html = self.create_test_document(size)
        cs_soup = CutySoup(html)
        bs_soup = BeautifulSoup(html, 'html.parser')

        cs_article = cs_soup.find('article')
        bs_article = bs_soup.find('article')

        operations = {
            "parse": (
                lambda: CutySoup(html),
                lambda: BeautifulSoup(html, 'html.parser'),
            ),
            "parse_and_find_all_tag": (
                lambda: CutySoup(html).find_all("p"),
                lambda: BeautifulSoup(html, 'html.parser').find_all("p"),
            ),
            "parse_and_find_all_class": (
                lambda: CutySoup(html).find_all(class_="text"),
                lambda: BeautifulSoup(html, 'html.parser').find_all(class_="text"),
            ),
            "find_all_tag": (
                lambda: cs_soup.find_all("p"),
                lambda: bs_soup.find_all("p"),
            ),
            "find_all_class": (
                lambda: cs_soup.find_all(class_="text"),
                lambda: bs_soup.find_all(class_="text"),
            ),
            "find_one_id": (
                lambda: cs_soup.find(id="title"),
                lambda: bs_soup.find(id="title"),
            ),
            "select_descendant": (
                lambda: cs_soup.select("article .link"),
                lambda: bs_soup.select("article .link"),
            ),
            "get_text": (
                lambda: cs_article.get_text(strip=True) if cs_article else "",
                lambda: bs_article.get_text(strip=True) if bs_article else "",
            ),
            "href_batch": (
                lambda: cs_soup.get_all_hrefs(),
                lambda: [a.get("href") for a in bs_soup.find_all("a")],
            ),
            "lazy_href_batch": (
                lambda: cs_soup.find_all_lazy("a").attrs("href"),
                lambda: [a.get("href") for a in bs_soup.find_all("a")],
            ),
        }

        comparisons = []

        for name, (cs_operation, bs_operation) in operations.items():
            op_iterations = min(iterations, 25) if name.startswith("parse") else iterations
            cs_result = self.benchmarker.benchmark_operation(
                f"compat_cutysoup_{name}",
                cs_operation,
                iterations=op_iterations,
                warmup=0 if name.startswith("parse") else 5,
            )
            bs_result = self.benchmarker.benchmark_operation(
                f"compat_bs4_{name}",
                bs_operation,
                iterations=op_iterations,
                warmup=0 if name.startswith("parse") else 5,
            )

            speedup = (
                bs_result.duration / cs_result.duration
                if cs_result.duration not in (0.0, float('inf'))
                else float('inf')
            )

            comparisons.append({
                "operation": name,
                "cutysoup_ms": cs_result.duration * 1000,
                "beautifulsoup_ms": bs_result.duration * 1000,
                "speedup": speedup,
            })

        return {
            "html_size": size,
            "iterations": iterations,
            "comparisons": comparisons,
        }
    
    def run_comprehensive_benchmark(self) -> Dict[str, Any]:
        """Run comprehensive benchmark suite."""
        print("Running comprehensive CutySoup Sprint 4 benchmarks...")
        
        # Clear previous results
        self.benchmarker.clear_results()
        
        # Run all benchmark categories
        parsing_results = self.benchmark_parsing()
        print(f"✓ Parsing benchmarks: {len(parsing_results)} tests")
        
        css_results = self.benchmark_css_selectors()
        print(f"✓ CSS selector benchmarks: {len(css_results)} tests")
        
        batch_results = self.benchmark_batch_operations()
        print(f"✓ Batch operations benchmarks: {len(batch_results)} tests")
        
        nav_results = self.benchmark_tree_navigation()
        print(f"✓ Tree navigation benchmarks: {len(nav_results)} tests")
        
        # Generate comprehensive report
        report = self.benchmarker.generate_report()
        
        print("\n🎯 Benchmark Summary:")
        print(f"   Total tests: {report['summary']['total_benchmarks']}")
        print(f"   Average throughput: {report['summary']['average_throughput']:.2f} ops/sec")
        print(f"   Memory efficiency: {report['summary']['average_memory_efficiency']:.2%}")
        
        if report['summary']['average_speedup'] > 1:
            print(f"   Average speedup: {report['summary']['average_speedup']:.1f}x")
            print(f"   Maximum speedup: {report['summary']['maximum_speedup']:.1f}x")
        
        return {
            "parsing": parsing_results,
            "css_selectors": css_results,
            "batch_operations": batch_results,
            "tree_navigation": nav_results,
            "full_report": report
        }


# Performance regression testing
class RegressionTester:
    """Automated performance regression testing."""
    
    def __init__(self, baseline_file: Optional[str] = None):
        self.baseline_file = baseline_file
        self.baseline_data: Optional[Dict[str, Any]] = None
        self.load_baseline()
    
    def load_baseline(self):
        """Load baseline performance data."""
        if self.baseline_file and Path(self.baseline_file).exists():
            try:
                with open(self.baseline_file, 'r') as f:
                    self.baseline_data = json.load(f)
            except Exception as e:
                print(f"Warning: Could not load baseline data: {e}")
    
    def save_baseline(self, results: Dict[str, Any]):
        """Save current results as baseline."""
        if self.baseline_file:
            with open(self.baseline_file, 'w') as f:
                json.dump(results, f, indent=2, default=str)
            self.baseline_data = results
    
    def check_regression(self, current_results: Dict[str, Any], 
                        threshold: float = 0.1) -> Dict[str, Any]:
        """Check for performance regressions."""
        if not self.baseline_data:
            return {"status": "no_baseline", "regressions": []}
        
        regressions = []
        
        # Compare detailed results
        baseline_results = {r["name"]: r for r in self.baseline_data.get("detailed_results", [])}
        current_detailed = {r["name"]: r for r in current_results.get("detailed_results", [])}
        
        for name, current in current_detailed.items():
            if name in baseline_results:
                baseline = baseline_results[name]
                
                # Check throughput regression
                baseline_throughput = baseline.get("throughput_ops_sec", 0)
                current_throughput = current.get("throughput_ops_sec", 0)
                
                if baseline_throughput > 0:
                    throughput_change = (current_throughput - baseline_throughput) / baseline_throughput
                    
                    if throughput_change < -threshold:  # Negative change is regression
                        regressions.append({
                            "test": name,
                            "metric": "throughput",
                            "baseline": baseline_throughput,
                            "current": current_throughput,
                            "change_percent": throughput_change * 100,
                            "severity": "high" if throughput_change < -0.25 else "medium"
                        })
        
        return {
            "status": "checked",
            "regressions": regressions,
            "regression_count": len(regressions),
            "total_tests": len(current_detailed)
        }


# Global benchmark instance
_global_benchmarker: Optional[RealWorldBenchmarks] = None


def get_benchmarker() -> RealWorldBenchmarks:
    """Get global benchmark instance."""
    global _global_benchmarker
    if _global_benchmarker is None:
        _global_benchmarker = RealWorldBenchmarks()
    return _global_benchmarker


def run_sprint4_benchmarks() -> Dict[str, Any]:
    """Run comprehensive Sprint 4 benchmarks."""
    benchmarker = get_benchmarker()
    return benchmarker.run_comprehensive_benchmark()


def benchmark_bs4_compatibility(size: str = "medium", iterations: int = 100) -> Dict[str, Any]:
    """Run a focused BeautifulSoup compatibility benchmark."""
    benchmarker = get_benchmarker()
    return benchmarker.benchmark_bs4_compatibility(size=size, iterations=iterations)


def quick_performance_test() -> Dict[str, float]:
    """Quick performance test for validation."""
    import cutysoup
    
    html = """
    <html><body>
        <div class="container">
            <h1>Title</h1>
            <p class="text">Content</p>
            <ul><li>Item 1</li><li>Item 2</li></ul>
        </div>
    </body></html>
    """
    
    # Parsing test
    start = time.perf_counter()
    for _ in range(100):
        soup = cutysoup.CutySoup(html)
    parsing_time = (time.perf_counter() - start) / 100
    
    # CSS selector test
    soup = cutysoup.CutySoup(html)
    start = time.perf_counter()
    for _ in range(100):
        soup.select("p.text")
    css_time = (time.perf_counter() - start) / 100
    
    # Find test
    start = time.perf_counter()
    for _ in range(100):
        soup.find_all("li")
    find_time = (time.perf_counter() - start) / 100
    
    return {
        "parsing_ms": parsing_time * 1000,
        "css_selector_ms": css_time * 1000,
        "find_all_ms": find_time * 1000,
        "total_ms": (parsing_time + css_time + find_time) * 1000
    }
