"""Aha demo: the same crash, three buffering policies, and the discovery
that `kill -9` cannot tell two of them apart.

A child process writes RECORDS puts and then SIGKILLs itself at a fixed
point -- a real SIGKILL (no handlers, no atexit, no flush on the way out),
but at a byte-reproducible one, so this output is identical every run.

For each policy we then ask the log two questions:
  1. process crash  -- reopen the log exactly as the child left it
  2. power loss     -- first discard everything fsync() never wrote down
"""

import os
import signal
import subprocess
import sys

from wal_kv import WalKV, simulate_power_loss

LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "demo.log")
RECORDS = 5

POLICIES = [
    (False, False, "flush=off fsync=off"),
    (True, False, "flush=on  fsync=off"),
    (True, True, "flush=on  fsync=on "),
]


def crash_writer(path, flush, fsync, n):
    """Runs in a child process. Never returns -- dies by SIGKILL."""
    kv = WalKV(path, flush=flush, fsync=fsync)
    for i in range(n):
        kv.put(b"key%02d" % i, b"value%02d" % i)
    os.kill(os.getpid(), signal.SIGKILL)


def run_writer(flush, fsync):
    if os.path.exists(LOG):
        os.remove(LOG)
    proc = subprocess.run(
        [sys.executable, os.path.abspath(__file__), "--crash-writer",
         LOG, str(int(flush)), str(int(fsync)), str(RECORDS)],
        capture_output=True,
    )
    return proc.returncode


def recovered_count(path):
    kv = WalKV(path)
    n = len(kv.data)
    kv.close()
    return n


def main():
    print(f"{RECORDS} puts, then the writer SIGKILLs itself mid-run.\n")
    print(f"{'policy':<21} {'signal':>7} {'bytes':>7} "
          f"{'after SIGKILL':>15} {'after power loss':>18}")

    for flush, fsync, label in POLICIES:
        rc = run_writer(flush, fsync)
        size = os.path.getsize(LOG)

        # A process crash leaves the file exactly as the child left it.
        after_kill = recovered_count(LOG)

        # A power cut additionally empties the page cache. With fsync off,
        # not one byte was ever pushed to the platter.
        simulate_power_loss(LOG, size if fsync else 0)
        after_power = recovered_count(LOG)

        print(f"{label:<21} {-rc:>7} {size:>7} "
              f"{after_kill:>10} / {RECORDS} {after_power:>13} / {RECORDS}")

    os.remove(LOG)
    print("\nsignal 9 = SIGKILL. Note rows 2 and 3: same bytes, same "
          "survival, and\nfsync made no observable difference whatsoever "
          "to the SIGKILL column.")


if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "--crash-writer":
        crash_writer(sys.argv[2], sys.argv[3] == "1", sys.argv[4] == "1",
                     int(sys.argv[5]))
    else:
        main()
