#!/usr/bin/env python3
"""Benchmark: compare Flask sync vs Quart async catalog search.

Prerequisites:
    - Flask POS server running on http://localhost:5001
    - Quart async server running on http://localhost:5002
      (start with: cd pos && hypercorn async_catalog:app --bind 0.0.0.0:5002)

Usage:
    python3 benchmark_async_catalog.py --workers 20 --requests 200
"""

import argparse
import asyncio
import json
import statistics
import sys
import time
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed


def sync_request(url):
    req = urllib.request.Request(url)
    start = time.perf_counter()
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            _ = resp.read()
            return (time.perf_counter() - start) * 1000, resp.status, None
    except Exception as e:
        return (time.perf_counter() - start) * 1000, 0, str(e)


async def async_request(session, url):
    import aiohttp
    start = time.perf_counter()
    try:
        async with session.get(url) as resp:
            _ = await resp.read()
            return (time.perf_counter() - start) * 1000, resp.status, None
    except Exception as e:
        return (time.perf_counter() - start) * 1000, 0, str(e)


def benchmark_sync(url, workers, requests_total):
    latencies = []
    errors = []
    with ThreadPoolExecutor(max_workers=workers) as ex:
        futures = [ex.submit(sync_request, url) for _ in range(requests_total)]
        for f in as_completed(futures):
            latency, status, err = f.result()
            if err:
                errors.append((status, err))
            else:
                latencies.append(latency)
    return latencies, errors


async def benchmark_async(url, workers, requests_total):
    import aiohttp
    latencies = []
    errors = []
    connector = aiohttp.TCPConnector(limit=workers * 2)
    async with aiohttp.ClientSession(connector=connector) as session:
        sem = asyncio.Semaphore(workers)

        async def task():
            async with sem:
                return await async_request(session, url)

        results = await asyncio.gather(*[task() for _ in range(requests_total)])
        for latency, status, err in results:
            if err:
                errors.append((status, err))
            else:
                latencies.append(latency)
    return latencies, errors


def report(label, latencies, errors, duration):
    if not latencies:
        print(f"{label}: NO successful requests")
        return
    lat = sorted(latencies)
    p50 = lat[int(len(lat) * 0.5)]
    p95 = lat[int(len(lat) * 0.95)]
    p99 = lat[int(len(lat) * 0.99)]
    mean = statistics.mean(lat)
    rps = len(lat) / duration if duration > 0 else 0
    print(f"{label:20s} | mean={mean:7.1f}ms | p50={p50:7.1f}ms | p95={p95:7.1f}ms | p99={p99:7.1f}ms | OK={len(lat)} | Err={len(errors)} | RPS={rps:.1f}")


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--flask-url', default='http://localhost:5001/pos/api/catalog/search?q=filtro%20aire&limit=20')
    parser.add_argument('--quart-url', default='http://localhost:5002/pos/api/catalog/async-search?q=filtro%20aire&limit=20')
    parser.add_argument('--workers', '-w', type=int, default=20)
    parser.add_argument('--requests', '-n', type=int, default=200)
    args = parser.parse_args()

    print("=" * 100)
    print(f"Benchmark: {args.requests} requests, {args.workers} concurrent workers")
    print("=" * 100)

    # Sync (Flask)
    print("\n[1/2] Warming up Flask...")
    sync_request(args.flask_url)
    print("[1/2] Benchmarking Flask (sync)...")
    start = time.time()
    lat_sync, err_sync = benchmark_sync(args.flask_url, args.workers, args.requests)
    dur_sync = time.time() - start
    report("Flask sync", lat_sync, err_sync, dur_sync)

    # Async (Quart)
    print("\n[2/2] Warming up Quart...")
    asyncio.run(benchmark_async(args.quart_url, 5, 1))
    print("[2/2] Benchmarking Quart (async)...")
    start = time.time()
    lat_async, err_async = asyncio.run(benchmark_async(args.quart_url, args.workers, args.requests))
    dur_async = time.time() - start
    report("Quart async", lat_async, err_async, dur_async)

    print("\n" + "=" * 100)
    if lat_sync and lat_async:
        improvement = (statistics.mean(lat_sync) - statistics.mean(lat_async)) / statistics.mean(lat_sync) * 100
        print(f"Mean latency improvement: {improvement:+.1f}%")
    print("=" * 100)


if __name__ == '__main__':
    try:
        import aiohttp
    except ImportError:
        print("ERROR: aiohttp is required for async benchmark.")
        print("Install: pip install aiohttp --break-system-packages")
        sys.exit(1)
    main()
