"""
Ch5 配套代码 1 / 3 —— 过期机制实测

演示：
  1. 惰性删除：写入 TTL=2s 的 Key，sleep 3s 后不主动触发，
                直到 GET 才发现过期被删，expired_keys 才 +1
  2. 定期删除：批量写入大量短 TTL Key，不去访问，观察后台
                activeExpireCycle 自动清理（DBSIZE 自然下降）
"""

import time
import redis

r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)


def section(title: str) -> None:
    print("\n" + "=" * 60)
    print(title)
    print("=" * 60)


def get_expired_count() -> int:
    return int(r.info("stats")["expired_keys"])


def demo_lazy_expiration() -> None:
    section("Demo 1: 惰性删除 —— 不访问就不触发")

    key = "demo:lazy:k1"
    r.delete(key)
    base = get_expired_count()

    r.set(key, "hello", ex=2)
    print(f"  写入 {key} TTL=2s，当前 expired_keys = {base}")

    print("  ⏳ sleep 3s，期间不访问 Key...")
    time.sleep(3)

    after_sleep = get_expired_count()
    print(f"  sleep 后 expired_keys = {after_sleep}（无变化 → 惰性删除未触发）")

    val = r.get(key)
    print(f"  GET {key} → {val!r}")

    after_get = get_expired_count()
    print(f"  GET 之后 expired_keys = {after_get}（{'+1' if after_get > after_sleep else '无变化'} → 惰性删除被触发）")


def demo_active_expiration(n: int = 2000) -> None:
    section(f"Demo 2: 定期删除 —— 后台自动清理 {n} 个短 TTL Key")

    base = get_expired_count()
    base_dbsize = r.dbsize()

    pipe = r.pipeline(transaction=False)
    for i in range(n):
        pipe.set(f"demo:active:k{i}", "v", ex=1)
    pipe.execute()
    print(f"  写入 {n} 个 TTL=1s 的 Key，当前 DBSIZE={r.dbsize()}, expired={get_expired_count() - base}")

    print("  ⏳ 不访问任何 Key，等定期任务慢慢清...\n")
    print(f"  {'秒':>4} | {'DBSIZE':>8} | {'本轮新增 expired':>18}")
    print("  " + "-" * 40)

    last_expired = get_expired_count()
    for sec in range(1, 21):
        time.sleep(1)
        cur_expired = get_expired_count()
        cur_size = r.dbsize()
        print(f"  {sec:>4} | {cur_size:>8} | {cur_expired - last_expired:>18}")
        last_expired = cur_expired
        if cur_size == base_dbsize:
            print(f"  ✅ 全部清理完毕（共 {cur_expired - base} 个被定期删除）")
            break

    for i in range(n):
        r.delete(f"demo:active:k{i}")


if __name__ == "__main__":
    try:
        demo_lazy_expiration()
        demo_active_expiration()
    except redis.ConnectionError as e:
        print(f"❌ Redis 连接失败: {e}")
