#!/usr/bin/env python3
"""
UCall Load & Concurrency Evaluation Suite (L-01 → L-07)
───────────────────────────────────────────────────────
Executes:
  L-01  Baseline (Cold/Idle state, latency distribution, infrastructure metrics)
  L-02  100 Concurrent WebSockets (holding capacity & connection latency)
  L-03  500 Concurrent WebSockets (holding capacity & connection latency)
  L-04  1,000 Concurrent WebSockets (holding capacity & connection latency)
  L-05  Concurrent Presence Heartbeats (100 clients × 10 heartbeats = 1,000 ops)
  L-06  Concurrent Call Signaling (50 concurrent call pairs invite→incoming→accept→connected)
  L-07  Concurrent Universal ID Allocation (200 concurrent registrations, Feistel/Verhoeff/DB check)
"""

import asyncio
import json
import os
import random
import socket
import statistics
import subprocess
import sys
import time
import urllib.request
import uuid
from typing import Dict, List, Optional, Tuple

try:
    import psutil
except ImportError:
    subprocess.check_call([sys.executable, "-m", "pip", "install", "psutil", "-q"])
    import psutil

try:
    import websockets
except ImportError:
    subprocess.check_call([sys.executable, "-m", "pip", "install", "websockets", "-q"])
    import websockets

BASE = "http://127.0.0.1:8080"
WS_BASE = "ws://127.0.0.1:8080/api/v1/ws"
DB_URL = "postgres://malleravindra:@127.0.0.1:5432/ucall_dev"


# ─── Statistics & Telemetry Helpers ──────────────────────────────────────────

def percentile(data: List[float], p: float) -> float:
    if not data:
        return 0.0
    s = sorted(data)
    k = (len(s) - 1) * (p / 100.0)
    f = int(k)
    c = min(f + 1, len(s) - 1)
    d0 = s[f] * (c - k)
    d1 = s[c] * (k - f)
    return d0 + d1

def compute_stats(data: List[float]) -> Dict[str, float]:
    if not data:
        return {"count": 0, "min": 0, "p50": 0, "p95": 0, "p99": 0, "max": 0, "avg": 0}
    return {
        "count": len(data),
        "min": min(data),
        "p50": percentile(data, 50),
        "p95": percentile(data, 95),
        "p99": percentile(data, 99),
        "max": max(data),
        "avg": statistics.mean(data),
    }

def get_server_process() -> Optional[psutil.Process]:
    for p in psutil.process_iter(["pid", "name", "cmdline"]):
        try:
            cmd = " ".join(p.info["cmdline"] or [])
            if "ucall-server" in cmd and "grep" not in cmd and p.is_running():
                return p
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue
    return None

def sample_telemetry() -> Dict[str, any]:
    telemetry = {
        "cpu_pct": 0.0,
        "rss_mb": 0.0,
        "fds": 0,
        "threads": 0,
        "pg_active": 0,
        "pg_idle": 0,
        "pg_total": 0,
        "redis_rtt_ms": 0.0,
    }
    p = get_server_process()
    if p:
        try:
            telemetry["cpu_pct"] = p.cpu_percent(interval=0.05)
            telemetry["rss_mb"] = p.memory_info().rss / (1024 * 1024)
            telemetry["threads"] = p.num_threads()
            out = subprocess.check_output(f"lsof -p {p.pid} 2>/dev/null | wc -l", shell=True).decode().strip()
            telemetry["fds"] = int(out) if out.isdigit() else 0
        except Exception:
            pass

    # PostgreSQL pool check
    try:
        pg_cmd = f"psql \"{DB_URL}\" -t -A -c \"SELECT COALESCE(state, 'unknown'), count(*) FROM pg_stat_activity WHERE datname = 'ucall_dev' GROUP BY state;\""
        out = subprocess.check_output(pg_cmd, shell=True).decode().strip().splitlines()
        for line in out:
            parts = line.split("|")
            if len(parts) == 2:
                state, cnt = parts[0].strip(), int(parts[1].strip())
                if state == "active":
                    telemetry["pg_active"] = cnt
                elif state == "idle":
                    telemetry["pg_idle"] = cnt
                telemetry["pg_total"] += cnt
    except Exception:
        pass

    # Redis socket ping RTT
    try:
        s = socket.create_connection(("127.0.0.1", 6379), timeout=1.0)
        t0 = time.perf_counter()
        s.sendall(b"PING\r\n")
        resp = s.recv(1024)
        telemetry["redis_rtt_ms"] = (time.perf_counter() - t0) * 1000
        s.close()
    except Exception:
        telemetry["redis_rtt_ms"] = -1.0

    return telemetry

def rand_phone() -> str:
    return f"+1{random.randint(2000000000, 9999999999)}"

def register_user_sync(phone: str, name: str = "User") -> Optional[dict]:
    body = json.dumps({"phone": phone, "display_name": name}).encode()
    req = urllib.request.Request(
        f"{BASE}/api/v1/auth/register",
        data=body,
        headers={"Content-Type": "application/json"}
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as res:
            data = json.loads(res.read().decode())
            return data.get("data")
    except Exception:
        return None

def ws_url(token: str, endpoint_id: Optional[uuid.UUID] = None) -> str:
    ep = endpoint_id or uuid.uuid4()
    return f"{WS_BASE}?token={token}&endpoint_id={ep}"


# ─── Verhoeff Check Digit Implementation for UID Validation ─────────────────

VERHOEFF_D = [
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
    [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
    [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
    [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
    [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
    [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
    [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
    [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
    [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
]

VERHOEFF_P = [
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
    [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
    [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
    [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
    [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
    [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
    [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
    [7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
]

def validate_verhoeff(num_str: str) -> bool:
    c = 0
    reversed_digits = [int(ch) for ch in reversed(num_str)]
    for i, digit in enumerate(reversed_digits):
        c = VERHOEFF_D[c][VERHOEFF_P[i % 8][digit]]
    return c == 0


# ─── L-01: Baseline Evaluation ───────────────────────────────────────────────

async def run_l01_baseline() -> Dict[str, any]:
    print("\n" + "=" * 65)
    print("  L-01: Cold / Baseline System Evaluation")
    print("=" * 65)

    telem = sample_telemetry()
    p = get_server_process()

    # 1. Health endpoint latency distribution (30 probes)
    health_lats = []
    for _ in range(30):
        t0 = time.perf_counter()
        req = urllib.request.Request(f"{BASE}/health")
        try:
            with urllib.request.urlopen(req, timeout=5) as res:
                if res.status == 200:
                    health_lats.append((time.perf_counter() - t0) * 1000)
        except Exception:
            pass

    # 2. Redis RTT distribution (30 probes)
    redis_lats = []
    for _ in range(30):
        try:
            s = socket.create_connection(("127.0.0.1", 6379), timeout=1.0)
            t0 = time.perf_counter()
            s.sendall(b"PING\r\n")
            s.recv(1024)
            redis_lats.append((time.perf_counter() - t0) * 1000)
            s.close()
        except Exception:
            pass

    # 3. WebSocket probe connect latency (10 probes)
    test_user = register_user_sync(rand_phone())
    token = test_user["access_token"] if test_user else ""
    ws_probe_lats = []
    if token:
        for _ in range(10):
            t0 = time.perf_counter()
            try:
                async with websockets.connect(ws_url(token), open_timeout=5) as ws:
                    ws_probe_lats.append((time.perf_counter() - t0) * 1000)
                    await ws.close()
            except Exception:
                pass

    # 4. Metrics scrape
    metrics_raw = ""
    try:
        with urllib.request.urlopen(f"{BASE}/metrics", timeout=5) as res:
            metrics_raw = res.read().decode()
    except Exception:
        pass

    h_stats = compute_stats(health_lats)
    r_stats = compute_stats(redis_lats)
    ws_stats = compute_stats(ws_probe_lats)

    print(f"  Process PID:          {p.pid if p else 'N/A'}")
    print(f"  CPU Utilization:      {telem['cpu_pct']:.1f}%")
    print(f"  Memory (RSS):         {telem['rss_mb']:.2f} MB")
    print(f"  Open File Descriptors:{telem['fds']}")
    print(f"  Thread Count:         {telem['threads']}")
    print(f"  PostgreSQL Pool:      {telem['pg_total']} connections ({telem['pg_active']} active, {telem['pg_idle']} idle)")
    print(f"  Redis PING RTT:       p50={r_stats['p50']:.2f}ms  p95={r_stats['p95']:.2f}ms  min={r_stats['min']:.2f}ms  max={r_stats['max']:.2f}ms")
    print(f"  HTTP /health Latency: p50={h_stats['p50']:.2f}ms  p95={h_stats['p95']:.2f}ms  p99={h_stats['p99']:.2f}ms  max={h_stats['max']:.2f}ms")
    print(f"  WebSocket Handshake:  p50={ws_stats['p50']:.2f}ms  p95={ws_stats['p95']:.2f}ms  p99={ws_stats['p99']:.2f}ms  max={ws_stats['max']:.2f}ms")

    return {
        "telemetry": telem,
        "health_stats": h_stats,
        "redis_stats": r_stats,
        "ws_stats": ws_stats,
        "metrics_raw": metrics_raw[:300],
    }


# ─── L-02 → L-04: Concurrent WebSockets Evaluation ───────────────────────────

async def run_ws_holding_tier(tier_name: str, target_connections: int, hold_seconds: float = 3.0) -> Dict[str, any]:
    print("\n" + "=" * 65)
    print(f"  {tier_name}: {target_connections} Concurrent WebSockets")
    print("=" * 65)

    user = register_user_sync(rand_phone(), f"WSTier-{target_connections}")
    if not user:
        print("  ❌ Failed to register user for WS tier")
        return {"error": "registration_failed"}

    token = user["access_token"]
    connect_lats = []
    active_sockets = []
    errors = 0
    disconnects = 0

    batch_semaphore = asyncio.Semaphore(50)  # ramp rate: max 50 concurrent handshakes

    async def connect_client():
        nonlocal errors, disconnects
        url = ws_url(token)
        t0 = time.perf_counter()
        async with batch_semaphore:
            try:
                ws = await asyncio.wait_for(websockets.connect(url), timeout=10.0)
                connect_lats.append((time.perf_counter() - t0) * 1000)
                active_sockets.append(ws)
            except Exception:
                errors += 1

    print(f"  Connecting {target_connections} WebSockets (concurrency throttled at 50 handshakes/batch)...")
    t_start = time.perf_counter()
    tasks = [connect_client() for _ in range(target_connections)]
    await asyncio.gather(*tasks)
    ramp_duration = time.perf_counter() - t_start
    throughput = len(active_sockets) / ramp_duration if ramp_duration > 0 else 0

    connected_count = len(active_sockets)
    print(f"  Connected: {connected_count}/{target_connections} in {ramp_duration:.2f}s ({throughput:.1f} connects/sec)")

    # Sample while all sockets are held alive
    mid_telem = sample_telemetry()
    print(f"  Holding all {connected_count} connections for {hold_seconds:.1f}s...")
    await asyncio.sleep(hold_seconds)

    # Check stability / unexpected drops
    for ws in list(active_sockets):
        is_closed = (getattr(ws, "state", None) is not None and ws.state.name == "CLOSED") or \
                    getattr(ws, "closed", False) or (getattr(ws, "close_code", None) is not None)
        if is_closed:
            disconnects += 1

    # Clean teardown
    print("  Closing all connections cleanly...")
    close_tasks = [ws.close() for ws in active_sockets]
    await asyncio.gather(*close_tasks, return_exceptions=True)
    await asyncio.sleep(0.5)

    post_telem = sample_telemetry()
    stats = compute_stats(connect_lats)

    err_rate = (errors / target_connections) * 100
    disc_rate = (disconnects / connected_count) * 100 if connected_count else 0

    print(f"  Connect Latency:      p50={stats['p50']:.1f}ms  p95={stats['p95']:.1f}ms  p99={stats['p99']:.1f}ms  max={stats['max']:.1f}ms")
    print(f"  Success Rate:         {connected_count}/{target_connections} ({(100 - err_rate):.1f}%)")
    print(f"  Mid-Hold Footprint:   CPU={mid_telem['cpu_pct']:.1f}%  RAM={mid_telem['rss_mb']:.2f} MB  FDs={mid_telem['fds']}")
    print(f"  Post-Close Footprint: CPU={post_telem['cpu_pct']:.1f}%  RAM={post_telem['rss_mb']:.2f} MB  FDs={post_telem['fds']}")
    print(f"  Abnormal Drops:       {disconnects} ({disc_rate:.2f}%)")

    return {
        "target": target_connections,
        "connected": connected_count,
        "errors": errors,
        "disconnects": disconnects,
        "error_rate": err_rate,
        "disconnect_rate": disc_rate,
        "throughput": throughput,
        "latency_stats": stats,
        "mid_telemetry": mid_telem,
        "post_telemetry": post_telem,
    }


# ─── L-05: Concurrent Presence Heartbeats ────────────────────────────────────

async def run_l05_presence_heartbeats(num_clients: int = 100, heartbeats_each: int = 10) -> Dict[str, any]:
    print("\n" + "=" * 65)
    print(f"  L-05: Concurrent Presence Heartbeats ({num_clients} clients × {heartbeats_each} heartbeats)")
    print("=" * 65)

    user = register_user_sync(rand_phone(), "HeartbeatUser")
    token = user["access_token"]
    total_ops = num_clients * heartbeats_each
    heartbeat_lats = []
    errors = 0

    semaphore = asyncio.Semaphore(40)

    async def client_worker():
        nonlocal errors
        async with semaphore:
            try:
                async with websockets.connect(ws_url(token), open_timeout=10.0) as ws:
                    for _ in range(heartbeats_each):
                        t0 = time.perf_counter()
                        payload = json.dumps({"method": "presence.heartbeat", "params": {}})
                        await ws.send(payload)
                        heartbeat_lats.append((time.perf_counter() - t0) * 1000)
                        await asyncio.sleep(0.02)
            except Exception:
                errors += 1

    t_start = time.perf_counter()
    tasks = [client_worker() for _ in range(num_clients)]
    await asyncio.gather(*tasks)
    duration = time.perf_counter() - t_start

    stats = compute_stats(heartbeat_lats)
    telem = sample_telemetry()
    ops_sec = len(heartbeat_lats) / duration if duration > 0 else 0

    print(f"  Total Heartbeats Sent:{len(heartbeat_lats)}/{total_ops} in {duration:.2f}s ({ops_sec:.1f} heartbeats/sec)")
    print(f"  Heartbeat Latency:    p50={stats['p50']:.2f}ms  p95={stats['p95']:.2f}ms  p99={stats['p99']:.2f}ms  max={stats['max']:.2f}ms")
    print(f"  Client Connection Err:{errors}/{num_clients}")
    print(f"  Redis PING RTT Post:  {telem['redis_rtt_ms']:.2f} ms")
    print(f"  System Resources:     CPU={telem['cpu_pct']:.1f}%  RAM={telem['rss_mb']:.2f} MB")

    return {
        "total_ops": total_ops,
        "completed": len(heartbeat_lats),
        "errors": errors,
        "throughput": ops_sec,
        "latency_stats": stats,
        "telemetry": telem,
    }


# ─── L-06: Concurrent Call Signaling ─────────────────────────────────────────

async def run_l06_call_signaling(pairs: int = 50) -> Dict[str, any]:
    print("\n" + "=" * 65)
    print(f"  L-06: Concurrent Call Signaling ({pairs} concurrent call flows)")
    print("=" * 65)

    rtts = []
    errors = 0
    semaphore = asyncio.Semaphore(25)

    async def execute_call_pair(i: int):
        nonlocal errors
        async with semaphore:
            loop = asyncio.get_event_loop()
            user_a = await loop.run_in_executor(None, register_user_sync, rand_phone(), f"CallA-{i}")
            user_b = await loop.run_in_executor(None, register_user_sync, rand_phone(), f"CallB-{i}")
            if not user_a or not user_b:
                errors += 1
                return

            tok_a = user_a["access_token"]
            tok_b = user_b["access_token"]
            uid_b = user_b["universal_id"]

            try:
                async with websockets.connect(ws_url(tok_a), open_timeout=8.0) as ws_a, \
                           websockets.connect(ws_url(tok_b), open_timeout=8.0) as ws_b:

                    t0 = time.perf_counter()
                    # A invites B
                    await ws_a.send(json.dumps({
                        "method": "call.invite",
                        "params": {
                            "target_universal_id": uid_b,
                            "call_type": "audio",
                            "sdp_offer": "v=0\r\no=alice\r\ns=call\r\n"
                        }
                    }))

                    # B receives call.incoming
                    call_id = None
                    deadline = time.perf_counter() + 6.0
                    while time.perf_counter() < deadline:
                        try:
                            msg_str = await asyncio.wait_for(ws_b.recv(), timeout=1.0)
                            msg = json.loads(msg_str)
                            if msg.get("method") == "call.incoming":
                                call_id = msg.get("params", {}).get("call_id")
                                break
                        except asyncio.TimeoutError:
                            break

                    if not call_id:
                        errors += 1
                        return

                    # B sends call.accept
                    await ws_b.send(json.dumps({
                        "method": "call.accept",
                        "params": {"call_id": call_id, "sdp_answer": "v=0\r\no=bob\r\ns=call\r\n"}
                    }))

                    # A receives call.connected
                    connected = False
                    deadline2 = time.perf_counter() + 6.0
                    while time.perf_counter() < deadline2:
                        try:
                            msg_str = await asyncio.wait_for(ws_a.recv(), timeout=1.0)
                            msg = json.loads(msg_str)
                            if msg.get("method") == "call.connected":
                                rtts.append((time.perf_counter() - t0) * 1000)
                                connected = True
                                break
                        except asyncio.TimeoutError:
                            break

                    if not connected:
                        errors += 1
                        return

                    # A hangs up cleanly
                    await ws_a.send(json.dumps({
                        "method": "call.hangup",
                        "params": {"call_id": call_id}
                    }))

            except Exception:
                errors += 1

    t_start = time.perf_counter()
    tasks = [execute_call_pair(i) for i in range(pairs)]
    await asyncio.gather(*tasks)
    duration = time.perf_counter() - t_start

    stats = compute_stats(rtts)
    telem = sample_telemetry()
    throughput = len(rtts) / duration if duration > 0 else 0

    print(f"  Completed Call Flows: {len(rtts)}/{pairs} in {duration:.2f}s ({throughput:.1f} calls/sec)")
    print(f"  Signaling RTT:        p50={stats['p50']:.1f}ms  p95={stats['p95']:.1f}ms  p99={stats['p99']:.1f}ms  max={stats['max']:.1f}ms")
    print(f"  Failed Call Pairs:    {errors}/{pairs} ({(errors / pairs * 100):.1f}%)")
    print(f"  PostgreSQL Pool:      {telem['pg_total']} connections ({telem['pg_active']} active)")
    print(f"  System Resources:     CPU={telem['cpu_pct']:.1f}%  RAM={telem['rss_mb']:.2f} MB")

    return {
        "pairs": pairs,
        "completed": len(rtts),
        "errors": errors,
        "throughput": throughput,
        "latency_stats": stats,
        "telemetry": telem,
    }


# ─── L-07: Concurrent Universal ID Allocation ────────────────────────────────

async def run_l07_uid_allocation(count: int = 200) -> Dict[str, any]:
    print("\n" + "=" * 65)
    print(f"  L-07: Concurrent Universal ID Allocation ({count} concurrent requests)")
    print("=" * 65)

    latencies = []
    errors = 0
    allocated_uids = []
    user_ids = []

    loop = asyncio.get_event_loop()

    def do_register():
        phone = rand_phone()
        t0 = time.perf_counter()
        res = register_user_sync(phone, "ConcurrentUID")
        lat = (time.perf_counter() - t0) * 1000
        return lat, res

    t_start = time.perf_counter()
    tasks = [loop.run_in_executor(None, do_register) for _ in range(count)]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    duration = time.perf_counter() - t_start

    for r in results:
        if isinstance(r, Exception) or not r or not r[1]:
            errors += 1
            continue
        lat, data = r
        latencies.append(lat)
        uid = data.get("universal_id", "")
        uid_user = data.get("user_id", "")
        allocated_uids.append(uid)
        user_ids.append(uid_user)

    unique_uids = set(allocated_uids)
    collisions = len(allocated_uids) - len(unique_uids)

    # Validate Verhoeff and format for every UID
    format_errors = 0
    verhoeff_errors = 0
    for uid in allocated_uids:
        if len(uid) != 12 or not uid.isdigit():
            format_errors += 1
        elif not validate_verhoeff(uid):
            verhoeff_errors += 1

    # Database consistency verification
    db_cmd = f"psql \"{DB_URL}\" -t -A -c \"" \
             f"SELECT (SELECT count(*) FROM users), " \
             f"       (SELECT count(*) FROM universal_ids), " \
             f"       (SELECT count(*) FROM user_credentials);\""
    db_out = subprocess.check_output(db_cmd, shell=True).decode().strip()
    u_cnt, uid_cnt, cred_cnt = db_out.split("|")

    stats = compute_stats(latencies)
    telem = sample_telemetry()
    throughput = len(allocated_uids) / duration if duration > 0 else 0

    print(f"  Allocated UIDs:       {len(allocated_uids)}/{count} in {duration:.2f}s ({throughput:.1f} allocations/sec)")
    print(f"  Allocation Latency:   p50={stats['p50']:.1f}ms  p95={stats['p95']:.1f}ms  p99={stats['p99']:.1f}ms  max={stats['max']:.1f}ms")
    print(f"  Unique UIDs:          {len(unique_uids)} ({collisions} collisions)")
    print(f"  Format Valid (12d):   {len(allocated_uids) - format_errors}/{len(allocated_uids)}")
    print(f"  Verhoeff Valid:       {len(allocated_uids) - verhoeff_errors}/{len(allocated_uids)}")
    print(f"  Database Totals:      users={u_cnt}, universal_ids={uid_cnt}, credentials={cred_cnt}")
    print(f"  System Resources:     CPU={telem['cpu_pct']:.1f}%  RAM={telem['rss_mb']:.2f} MB")

    return {
        "count": count,
        "allocated": len(allocated_uids),
        "unique": len(unique_uids),
        "collisions": collisions,
        "format_errors": format_errors,
        "verhoeff_errors": verhoeff_errors,
        "errors": errors,
        "throughput": throughput,
        "latency_stats": stats,
        "db_counts": {"users": u_cnt, "universal_ids": uid_cnt, "credentials": cred_cnt},
        "telemetry": telem,
    }


# ─── Master Runner ───────────────────────────────────────────────────────────

async def main():
    print("╔══════════════════════════════════════════════════════════════╗")
    print("║   UCall Control Plane Concurrency & Load Suite (L-01→L-07)   ║")
    print("╚══════════════════════════════════════════════════════════════╝")
    print(f"  Target: {BASE}")
    print(f"  Time:   {time.strftime('%Y-%m-%d %H:%M:%S')}")

    results = {}

    # L-01: Baseline
    results["L-01"] = await run_l01_baseline()

    # L-02: 100 WebSockets
    results["L-02"] = await run_ws_holding_tier("L-02", 100, hold_seconds=3.0)

    # L-03: 500 WebSockets
    results["L-03"] = await run_ws_holding_tier("L-03", 500, hold_seconds=3.0)

    # L-04: 1,000 WebSockets
    results["L-04"] = await run_ws_holding_tier("L-04", 1000, hold_seconds=3.0)

    # L-05: Presence Heartbeats
    results["L-05"] = await run_l05_presence_heartbeats(num_clients=100, heartbeats_each=10)

    # L-06: Call Signaling
    results["L-06"] = await run_l06_call_signaling(pairs=50)

    # L-07: UID Allocation
    results["L-07"] = await run_l07_uid_allocation(count=200)

    # Output JSON summary for machine parsing
    with open("tests/concurrency_results.json", "w") as f:
        json.dump(results, f, indent=2)

    print("\n" + "=" * 65)
    print("  Load & Concurrency Suite (L-01 → L-07) Execution Complete")
    print("  Detailed results saved to tests/concurrency_results.json")
    print("=" * 65)


if __name__ == "__main__":
    asyncio.run(main())
