"""Aha demo: ONE arrival trace and ONE breaker configuration, run against
two dependencies that start failing at the same tick for different reasons.

The breaker's half-open probe is a single call. Against a dependency whose
health does not depend on load, one call is a valid sample. Against a
dependency that fails *because* of load, one call is a measurement taken at a
load level that will not exist a tick later.
"""

from circuit_breaker import (CircuitBreaker, DeadDependency, NoBreaker,
                             OverloadedDependency, drained_at, simulate,
                             totals)

BASE = 5            # requests per tick in steady state
SPIKE = 40          # requests per tick during the surge
SURGE = (10, 20)    # ticks [10, 20) — also exactly when the dead one is down
CAPACITY = 20       # the overloaded dependency's ceiling, 4x the baseline
TICKS = 45
HORIZON = 400       # long run, to ask whether the incident ever ends

TRACE = [SPIKE if SURGE[0] <= t < SURGE[1] else BASE for t in range(TICKS)]
LONG_TRACE = [SPIKE if SURGE[0] <= t < SURGE[1] else BASE
              for t in range(HORIZON)]


def breaker():
    return CircuitBreaker(window=20, min_calls=10, error_rate=0.5,
                          cooldown=5, probes=1)


def dead():
    return DeadDependency(*SURGE)


def overloaded():
    return OverloadedDependency(CAPACITY)


def side_by_side(left, right):
    print(f"{'':11}{'DEAD (down 10-20)':^30}{'':2}"
          f"{'OVERLOADED (capacity 20/tick)':^30}")
    head = f"{'state':>10}{'call':>6}{'ok':>5}{'fail':>5}{'retry':>6}"
    print(f"{'t':>3}{'arr':>5}  {head}  {head}")
    for a, b in zip(left, right):
        mark = ("  <- probe" if "half_open" in (a.state, b.state) else "")
        print(f"{a.now:>3}{a.arrivals:>5}  "
              f"{a.state:>10}{a.admitted:>6}{a.ok:>5}{a.failed:>5}"
              f"{a.pending:>6}  "
              f"{b.state:>10}{b.admitted:>6}{b.ok:>5}{b.failed:>5}"
              f"{b.pending:>6}{mark}")


def run():
    print("Same trace, same breaker. 5 req/tick, surging to 40 for ticks "
          "10-19.\n")
    side_by_side(simulate(dead(), breaker(), TRACE),
                 simulate(overloaded(), breaker(), TRACE))

    print(f"\n--- after {TICKS} ticks -------------------------------------")
    rows = {
        ("dead", "no breaker"): simulate(dead(), NoBreaker(), TRACE),
        ("dead", "breaker"): simulate(dead(), breaker(), TRACE),
        ("overloaded", "no breaker"): simulate(overloaded(), NoBreaker(), TRACE),
        ("overloaded", "breaker"): simulate(overloaded(), breaker(), TRACE),
    }
    print(f"{'dependency':>12}  {'':<14}{'calls':>6}{'failed':>8}"
          f"{'completed':>11}{'backlog':>9}{'over at':>9}")
    for (dep, arm), r in rows.items():
        t = totals(r)
        over = drained_at(r, SURGE[1])
        print(f"{dep:>12}  {arm:<14}{t['calls']:>6}{t['failed_calls']:>8}"
              f"{t['completed']:>11}{t['backlog']:>9}"
              f"{str(over) if over is not None else 'never':>9}")

    print(f"\n--- does it ever end? ({HORIZON} ticks) --------------------")
    for dep, mk in (("dead", dead), ("overloaded", overloaded)):
        for arm, mb in (("no breaker", NoBreaker), ("breaker", breaker)):
            r = simulate(mk(), mb(), LONG_TRACE)
            t = totals(r)
            over = drained_at(r, SURGE[1])
            print(f"{dep:>12}  {arm:<14}completed={t['completed']:>5}  "
                  f"trips={t['trips']:>3}  backlog={t['backlog']:>5}  "
                  f"over at {str(over) if over is not None else 'never'}")


if __name__ == "__main__":
    run()
