"""The demo: five acts over ONE fixed latency trace.

    python3 demo.py

Act 1 puts the reader's model in place (the tail is noisy, fair enough),
act 2 breaks it, act 3 rebuilds it around the quantile function, act 4 spends
the identical memory budget the other way, act 5 finds the boundary.

Nothing here reads a clock or a global RNG. Run it twice and diff it.
"""
import math
import random
import statistics

from aggregator import (DEF_BUCKETS, Histogram, Reservoir, beta_inv, beta_sd,
                        exact_pct, latency_trace, ms, uniform_subset)

N = 1_000_000
K = 100
SEEDS = 2000
TRACE_SEED = 20260803

rng = random.Random(TRACE_SEED)
srt = sorted(latency_trace(rng, N))
true_p50 = exact_pct(srt, 0.50)
true_p99 = exact_pct(srt, 0.99)

print(f"trace: n={N} seed={TRACE_SEED}   "
      f"true p50 {ms(true_p50)}   true p99 {ms(true_p99)}")

# --------------------------------------------------------------------- act 1
print()
print(f"--- 1. one trace, {SEEDS} reservoir seeds, k={K} slots ---")
reported = {"p50": [], "p99": []}
for s in range(SEEDS):
    samp = uniform_subset(random.Random(1000 + s), srt, K)
    reported["p50"].append(exact_pct(samp, 0.50))
    reported["p99"].append(exact_pct(samp, 0.99))
for lab, true in (("p50", true_p50), ("p99", true_p99)):
    v = sorted(reported[lab])
    reported[lab] = v
    print(f"  reported {lab}: {ms(v[0])} .. {ms(v[-1])}   "
          f"spread {v[-1] / v[0]:6.2f}x   median {ms(exact_pct(v, 0.5))} "
          f"({exact_pct(v, 0.5) / true:5.3f}x true)")
print(f"  -> the p99 swings {reported['p99'][-1] / reported['p99'][0]:.1f}x on "
      f"ONE trace; the p50 swings {reported['p50'][-1] / reported['p50'][0]:.2f}x.")

# The sweep above draws uniform k-subsets directly. Real Algorithm R, three
# seeds, one pass each over all 1,000,000 samples -- it must land inside.
alg_r = []
for s in (0, 1, 2):
    res = Reservoir(K, s)
    for x in srt:
        res.add(x)
    alg_r.append(res.pct(0.99))
print(f"  Algorithm R over the full stream, seeds 0/1/2: "
      f"{', '.join(ms(v).strip() for v in alg_r)}"
      f"  (inside the swept range: "
      f"{all(reported['p99'][0] <= v <= reported['p99'][-1] for v in alg_r)})")

# --------------------------------------------------------------------- act 2
print()
print("--- 2. and it is NOT because the tail slot is sampled sloppily ---")
for j, lab in ((50, "p50"), (99, "p99")):
    sd = beta_sd(j, K)
    print(f"  slot {j:3d} ({lab}) sits at quantile {j / (K + 1):.4f} +/- {sd:.4f}"
          f"  (sd in percentile-points: {sd * 100:.2f})")
print(f"  -> the p99 slot is located {beta_sd(50, K) / beta_sd(99, K):.2f}x "
      f"MORE tightly than the p50 slot.")

# --------------------------------------------------------------------- act 3
print()
print("--- 3. the damage is Q(u)'s slope, not the sampler ---")


def Q(u):
    """The trace's own quantile function."""
    return srt[max(1, min(N, math.ceil(u * N))) - 1]


for j, lab in ((50, "p50"), (99, "p99")):
    lo_u, hi_u = beta_inv(0.05, j, K), beta_inv(0.95, j, K)
    print(f"  {lab}: slot roams u={lo_u:.4f}..{hi_u:.4f} "
          f"({(hi_u - lo_u) * 100:5.2f} pct-points) -> value "
          f"{ms(Q(lo_u))}..{ms(Q(hi_u))} = {Q(hi_u) / Q(lo_u):5.2f}x")
print()
print("  push Beta(j, k+1-j) through Q(u) and the measurement falls out:")
print("  slot |     p05 pred/meas   |     p50 pred/meas   |     p95 pred/meas")
for j, lab in ((50, "p50"), (99, "p99")):
    cells = []
    for p in (0.05, 0.50, 0.95):
        pred, meas = Q(beta_inv(p, j, K)), exact_pct(reported[lab], p)
        cells.append(f"{ms(pred)}{ms(meas)} {pred / meas:5.3f}x")
    print(f"   {lab} | " + " | ".join(cells))

# --------------------------------------------------------------------- act 4
print()
print(f"--- 4. same {K} units of memory, spent on buckets instead ---")
log_bounds = [0.001 * 10 ** (i * 5 / (K - 1)) for i in range(K)]
for name, bounds in (("log-spaced 1ms..100s", log_bounds),
                     ("Prometheus DefBuckets", DEF_BUCKETS)):
    h = Histogram(bounds)
    for x in srt:
        h.add(x)
    print(f"  {name:22s} ({len(bounds) + 1:3d} counters) p99: "
          f"{ms(h.quantile(0.99))} ({h.quantile(0.99) / true_p99:5.3f}x true), "
          f"deterministic")
print(f"  {'reservoir':22s} ({K:3d} slots)    p99: {ms(reported['p99'][0])} .. "
      f"{ms(reported['p99'][-1])} "
      f"({reported['p99'][-1] / reported['p99'][0]:.2f}x spread)")

# --------------------------------------------------------------------- act 5
print()
print("--- 5. boundary: a trace with a flat tail ---")
r2 = random.Random(TRACE_SEED)
flat = sorted(r2.uniform(0.010, 0.030) for _ in range(200_000))
const = [0.020] * 200_000
for name, t in (("uniform 10-30ms", flat), ("constant 20ms", const)):
    v = [exact_pct(uniform_subset(random.Random(1000 + s), t, K), 0.99)
         for s in range(SEEDS)]
    print(f"  {name:16s}: true p99 {ms(exact_pct(t, 0.99))}  reported "
          f"{ms(min(v))} .. {ms(max(v))}  spread {max(v) / min(v):5.2f}x")
print(f"  mean of the {SEEDS} constant-trace readings: "
      f"{ms(statistics.mean(v))}  -- the estimator has nothing left to be "
      f"wrong about.")
