"""
Ch11 配套代码 4 / 4 —— 自实现简化版 Redlock

完整算法步骤（见 11.4.3）：
  Step 1: 记录 t1
  Step 2: 依次向 N 个实例 SET ... NX PX，单实例超时极小
  Step 3: 记录 t2，计算 Δt = t2 - t1
  Step 4: 多数派成功 (≥N/2+1) 且 Δt < TTL 才算加锁成功
  Step 5: 实际有效时间 = TTL - Δt
  Step 6: 失败时也要去所有实例释放（防止网络抖动导致部分成功）

环境优雅降级：
  如果你只有一个 Redis 实例，本脚本会自动用同一实例的 5 个不同 DB
  来模拟「5 个独立实例」（仅用于演示算法流程，不具备 Redlock 的隔离性）。
"""

import time
import uuid
from typing import List, Tuple

try:
    import redis
except ImportError:
    print("请先 pip install redis"); raise SystemExit(1)


RELEASE_LUA = """
if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('DEL', KEYS[1])
else return 0 end
"""

CLOCK_DRIFT_FACTOR = 0.01  # antirez 论文给的经验值（1%）
SINGLE_REQUEST_TIMEOUT = 0.05  # 50ms，单实例阻塞上限


class RedLock:
    def __init__(self, instances: List["redis.Redis"], key: str, ttl_ms: int = 5000):
        self.instances = instances
        self.key = key
        self.ttl_ms = ttl_ms
        self.token = uuid.uuid4().hex
        self.quorum = len(instances) // 2 + 1
        self.valid_ms = 0  # 加锁成功后的实际有效时间

    def _try_acquire_one(self, client) -> bool:
        try:
            return client.set(self.key, self.token, nx=True, px=self.ttl_ms) is True
        except Exception:
            return False

    def _release_one(self, client) -> None:
        try:
            client.eval(RELEASE_LUA, 1, self.key, self.token)
        except Exception:
            pass

    def acquire(self) -> Tuple[bool, dict]:
        """ 尝试加锁。返回 (是否成功, 详细信息)。"""
        t1 = time.time()

        votes = []  # [(idx, ok)]
        for idx, c in enumerate(self.instances):
            ok = self._try_acquire_one(c)
            votes.append((idx, ok))

        elapsed_ms = int((time.time() - t1) * 1000)
        drift_ms = int(self.ttl_ms * CLOCK_DRIFT_FACTOR) + 2
        valid_ms = self.ttl_ms - elapsed_ms - drift_ms
        success_count = sum(1 for _, ok in votes if ok)

        info = {
            "votes": votes,
            "success_count": success_count,
            "quorum": self.quorum,
            "elapsed_ms": elapsed_ms,
            "valid_ms": valid_ms,
        }

        if success_count >= self.quorum and valid_ms > 0:
            self.valid_ms = valid_ms
            return True, info

        # 失败：去所有实例释放（包括没成功的，防止半成功）
        for c in self.instances:
            self._release_one(c)
        return False, info

    def release(self) -> None:
        for c in self.instances:
            self._release_one(c)


def build_instances() -> List["redis.Redis"]:
    """ 优先尝试 5 个端口；若不可用则降级为 1 个端口 + 5 个 DB。"""
    candidates = [
        ("127.0.0.1", 6379), ("127.0.0.1", 6380), ("127.0.0.1", 6381),
        ("127.0.0.1", 6382), ("127.0.0.1", 6383),
    ]
    inst = []
    for host, port in candidates:
        try:
            c = redis.Redis(host=host, port=port, socket_timeout=SINGLE_REQUEST_TIMEOUT,
                            socket_connect_timeout=0.2, decode_responses=True)
            c.ping()
            inst.append(c)
        except Exception:
            pass

    if len(inst) >= 3:
        print(f"✅ 检测到 {len(inst)} 个独立 Redis 实例，启用真 Redlock")
        return inst

    print("⚠️  未检测到多实例，降级使用单实例 × 5 DB（仅演示算法流程）")
    return [redis.Redis(host="127.0.0.1", port=6379, db=i, decode_responses=True,
                        socket_timeout=SINGLE_REQUEST_TIMEOUT) for i in range(5)]


# -------------------------------------------------------------
# 演示 1: 正常 5 实例加锁成功
# -------------------------------------------------------------
def demo_normal_acquire() -> None:
    print("\n" + "=" * 60)
    print("演示 ①：正常加锁（期望多数派 + 总耗时 < TTL）")
    print("=" * 60)
    instances = build_instances()
    lock = RedLock(instances, key="redlock:order:1001", ttl_ms=5000)
    ok, info = lock.acquire()
    print(f"  实例投票: {info['votes']}")
    print(f"  成功数: {info['success_count']} / 多数派需 {info['quorum']}")
    print(f"  申请耗时: {info['elapsed_ms']} ms")
    print(f"  实际有效时间: {info['valid_ms']} ms")
    print(f"  结果: {'✅ 加锁成功' if ok else '❌ 加锁失败'}")
    if ok:
        lock.release()


# -------------------------------------------------------------
# 演示 2: 模拟 2 个实例已被占 → 仍能多数派成功
# -------------------------------------------------------------
def demo_partial_failure() -> None:
    print("\n" + "=" * 60)
    print("演示 ②：模拟 2 个实例被预先占用（5 个里仍能拿到 3 个，多数派成功）")
    print("=" * 60)
    instances = build_instances()
    key = "redlock:order:1002"
    # 预占 2 个
    for c in instances[:2]:
        c.set(key, "intruder", px=10_000)
    lock = RedLock(instances, key=key, ttl_ms=5000)
    ok, info = lock.acquire()
    print(f"  实例投票: {info['votes']}  （前 2 个被占用）")
    print(f"  成功数: {info['success_count']} / 多数派需 {info['quorum']}")
    print(f"  结果: {'✅ 加锁成功' if ok else '❌ 加锁失败'}")
    if ok:
        lock.release()
    # 清理
    for c in instances[:2]:
        c.delete(key)


# -------------------------------------------------------------
# 演示 3: 模拟 3 个实例被占 → 不足多数派，加锁失败
# -------------------------------------------------------------
def demo_quorum_fail() -> None:
    print("\n" + "=" * 60)
    print("演示 ③：3 个实例被占 → 仅 2 个可加锁 → 不足多数派 → 失败")
    print("=" * 60)
    instances = build_instances()
    key = "redlock:order:1003"
    for c in instances[:3]:
        c.set(key, "intruder", px=10_000)
    lock = RedLock(instances, key=key, ttl_ms=5000)
    ok, info = lock.acquire()
    print(f"  实例投票: {info['votes']}  （前 3 个被占用）")
    print(f"  成功数: {info['success_count']} / 多数派需 {info['quorum']}")
    print(f"  结果: {'✅ 加锁成功' if ok else '❌ 加锁失败（符合预期）'}")
    for c in instances[:3]:
        c.delete(key)


if __name__ == "__main__":
    try:
        demo_normal_acquire()
        demo_partial_failure()
        demo_quorum_fail()
    except redis.ConnectionError as e:
        print(f"❌ Redis 连接失败：{e}")
        raise SystemExit(1)

    print("\n说明：本实现是 Redlock 算法的简化教学版。")
    print("生产环境推荐 Java 用 Redisson 的 RedissonRedLock，")
    print("Python 可参考 `redlock-py` 库（pip install redlock-py）。")
