#!/usr/bin/env python3
"""Last-write-wins silently discards a write under clock skew, DDIA 2e Ch. 6.

In multi-leader and leaderless replication, when two nodes accept a write to the
same key concurrently, the conflict must be resolved. A popular rule is
Last-Write-Wins (LWW): attach a timestamp to every write and keep the one with
the greatest timestamp. It's simple, needs no coordination, and it silently
throws data away -- because the "timestamp" is a wall clock, and wall clocks lie.

If one node's clock runs fast, a write it made EARLIER in real time can carry a
LARGER timestamp than a write another node made LATER. LWW dutifully keeps the
larger timestamp, so the write that actually happened last is discarded -- no
error, no conflict, no trace. The name "last write wins" is misleading: the
surviving write was not necessarily last.

We simulate two leaders writing to the same key `title`, with explicit real
(wall-clock) times and explicit per-node clock offsets -- no real clocks, no
randomness, so the result is deterministic. First with a skewed clock (data is
lost), then with synced clocks (LWW is correct). Pure standard library.
"""


def timestamp(real_time, clock_offset):
    """The timestamp a node stamps on a write = its skewed reading of the clock."""
    return real_time + clock_offset


def lww_resolve(writes):
    """LWW conflict resolution: keep the write with the greatest timestamp.

    `writes` is a list of dicts with keys: node, value, real_time, offset, ts.
    Ties are broken deterministically by value (greater value wins) so that every
    replica independently picks the SAME winner -- LWW must be a total order.
    """
    return max(writes, key=lambda w: (w["ts"], w["value"]))


def run_scenario(title, writes):
    print(title)
    print("-" * len(title))

    # Real (wall-clock) order: what actually happened, and when.
    by_real = sorted(writes, key=lambda w: w["real_time"])
    print("  real (wall-clock) order of events -- what actually happened:")
    for w in by_real:
        print(
            f"    t={w['real_time']:>3}s real  node {w['node']} "
            f"(clock offset {w['offset']:+d}s)  writes title={w['value']!r}  "
            f"-> stamps ts={w['ts']}"
        )
    real_last = by_real[-1]
    print(f"  => the write that ACTUALLY happened last: title={real_last['value']!r} "
          f"(node {real_last['node']}, t={real_last['real_time']}s real)")

    # Timestamp order: what LWW sees.
    print("  timestamp order -- what LWW compares (greatest ts wins):")
    for w in sorted(writes, key=lambda w: w["ts"]):
        print(f"    ts={w['ts']}  title={w['value']!r}  (node {w['node']})")

    winner = lww_resolve(writes)
    print(f"  => LWW WINNER: title={winner['value']!r} (node {winner['node']}, ts={winner['ts']})")

    for w in writes:
        if w is winner:
            continue
        lost = w is not real_last  # is the discarded write the real-last one?
        tag = "LOST (silently discarded)"
        note = " <-- this was the REAL last write!" if w is real_last else ""
        print(f"     - title={w['value']!r} (node {w['node']}, ts={w['ts']}): {tag}{note}")

    correct = winner is real_last
    verdict = "CORRECT: LWW kept the real-last write." if correct else \
        "WRONG: LWW kept an OLDER write; the real-last write was lost with no error."
    print(f"  verdict: {verdict}\n")
    return correct


def main():
    print("LWW resolves conflicts by keeping the greatest TIMESTAMP.")
    print("A timestamp is a node's wall-clock reading -- and clocks can be skewed.\n")

    # --- Scenario 1: node A's clock is 30s fast ------------------------------
    # Real order: A writes "B" at t=100, THEN B writes "C" later at t=110.
    # So "C" is the write that actually happened last.
    # But A's clock is +30s, so A stamps ts=130; B's clock is accurate, ts=110.
    skewed = [
        {"node": "A", "value": "B", "real_time": 100, "offset": +30},
        {"node": "B", "value": "C", "real_time": 110, "offset": 0},
    ]
    for w in skewed:
        w["ts"] = timestamp(w["real_time"], w["offset"])
    skewed_correct = run_scenario(
        "Scenario 1 -- node A's clock is skewed +30s (fast)", skewed
    )

    # --- Scenario 2 (control): clocks synced, same two writes ----------------
    synced = [
        {"node": "A", "value": "B", "real_time": 100, "offset": 0},
        {"node": "B", "value": "C", "real_time": 110, "offset": 0},
    ]
    for w in synced:
        w["ts"] = timestamp(w["real_time"], w["offset"])
    synced_correct = run_scenario(
        "Scenario 2 (control) -- clocks synced (both offset 0)", synced
    )

    print("bottom line:")
    print(f"  skewed clock -> LWW correct? {skewed_correct}  "
          "(the real-last write 'C' was silently lost)")
    print(f"  synced clock -> LWW correct? {synced_correct}  "
          "(same two writes, but now 'C' survives)")
    print("  LWW is only as trustworthy as the clocks agree. \"Last write wins\" is")
    print("  a misnomer: the survivor is the greatest-timestamp write, not the last one.")


if __name__ == "__main__":
    main()
