"""
CV6 Validation A10 — real-server concurrency / stability probe.
Assumes uvicorn is running on 127.0.0.1:8000. Registers/logs in, then fires
concurrent mixed requests to surface: 500s, SQLite 'database is locked',
thread leaks, and memory growth. Reports PASS/FAIL.
"""
import concurrent.futures as cf
import time
import requests

BASE = "http://127.0.0.1:8000"
EMAIL = "loadtest@example.com"
PW = "CV6Load@2026!"


def login():
    requests.post(f"{BASE}/users/register", json={"name": "Load", "email": EMAIL, "password": PW}, timeout=10)
    r = requests.post(f"{BASE}/users/login", json={"email": EMAIL, "password": PW}, timeout=10)
    d = r.json()
    tok = d.get("access_token") or d.get("token") or ""
    return {"Authorization": f"Bearer {tok}"} if tok else {}


def server_proc():
    try:
        import psutil
        for p in psutil.process_iter(["name", "cmdline"]):
            cl = " ".join(p.info.get("cmdline") or [])
            if "uvicorn" in cl and "main:app" in cl:
                return p
    except Exception:
        return None
    return None


def main():
    H = login()
    if not H:
        print("[A10] FAIL — could not authenticate")
        return 1

    endpoints = [
        ("GET", "/", None), ("GET", "/broker/status", None),
        ("GET", "/dashboard/", H), ("GET", "/state/", H),
        ("GET", "/paper/account", H), ("GET", "/dashboard/pnl", H),
        ("GET", "/dashboard/positions", H), ("GET", "/state/pnl", H),
    ]

    proc = server_proc()
    mem0 = threads0 = None
    if proc:
        mem0 = proc.memory_info().rss / 1e6
        threads0 = proc.num_threads()

    N = 400
    results = {"ok": 0, "err5xx": 0, "locked": 0, "exc": 0}
    latencies = []

    def one(i):
        m, path, h = endpoints[i % len(endpoints)]
        t = time.time()
        try:
            r = requests.request(m, f"{BASE}{path}", headers=h, timeout=15)
            latencies.append(time.time() - t)
            if r.status_code >= 500:
                results["err5xx"] += 1
                if "database is locked" in r.text.lower():
                    results["locked"] += 1
            else:
                results["ok"] += 1
        except Exception as e:
            results["exc"] += 1
            if "locked" in str(e).lower():
                results["locked"] += 1

    start = time.time()
    with cf.ThreadPoolExecutor(max_workers=24) as ex:
        list(ex.map(one, range(N)))
    dur = time.time() - start

    mem1 = threads1 = None
    if proc:
        try:
            mem1 = proc.memory_info().rss / 1e6
            threads1 = proc.num_threads()
        except Exception:
            pass

    avg_ms = round(sum(latencies) / len(latencies) * 1000, 1) if latencies else 0
    p95_ms = round(sorted(latencies)[int(len(latencies) * 0.95)] * 1000, 1) if latencies else 0

    print("=" * 60)
    print(f"[A10] {N} concurrent requests in {dur:.1f}s  (24 workers)")
    print(f"      ok={results['ok']}  5xx={results['err5xx']}  "
          f"db_locked={results['locked']}  exceptions={results['exc']}")
    print(f"      latency avg={avg_ms}ms  p95={p95_ms}ms")
    if mem0 is not None:
        print(f"      server RSS {mem0:.0f}MB -> {mem1:.0f}MB  (delta {mem1 - mem0:+.0f}MB)")
        print(f"      server threads {threads0} -> {threads1}")

    ok = (results["err5xx"] == 0 and results["locked"] == 0 and results["exc"] == 0)
    mem_ok = (mem0 is None) or (mem1 - mem0 < 100)   # <100MB growth for 400 reqs
    print(f"[A10] {'PASS' if ok and mem_ok else 'FAIL'} — "
          f"{'no 5xx/locks/leaks' if ok and mem_ok else 'see counts above'}")
    print("=" * 60)
    return 0 if (ok and mem_ok) else 1


if __name__ == "__main__":
    import sys
    sys.exit(main())
