#!/usr/bin/env python3
"""Print what the machine is doing, live, from the event stream.

    python3 kb_watch.py --host kilobaser.lab.example.org --user apibot --insecure

Read-only: it never queues, starts, answers or cancels anything, so it is safe to
point at a machine that is in the middle of a real run. Use it while you develop --
run it in one terminal, make API calls in another, and watch what the device
actually broadcasts.

Two things it is built to demonstrate:

**A quiet machine sends nothing.** There is no heartbeat. If you start this against
an idle instrument you will see the initial snapshot and then silence, possibly for
hours. That is normal. It also means a proxy that closes idle connections will
disconnect you without either end noticing, which is why the client reconnects on a
backoff and re-reads the full state each time it does -- the server does not replay
what you missed.

**The two collection events are not alike.** `processRunQueue` carries the entire
queue and replaces it; `processRuns` carries a single run and is merged in by id.
The counters below come from a mirror maintained that way (see `Kilobaser.watch`).
"""

import argparse
import getpass
from datetime import datetime

from kb_client import Kilobaser, checkpoint


def summarise(state):
    status = state["status"] or {}
    current = status.get("currentState") or {}
    hardware = status.get("currentHWDState") or {}
    cartridge = current.get("cartridge") or {}

    bits = [f"mode={current.get('mode')}"]
    if cartridge.get("name"):
        remaining = cartridge.get("totalBasesCount")
        bits.append(f"cartridge={cartridge['name']}"
                    + (f" ({remaining} bases left)" if remaining else ""))
    if hardware.get("chipKind"):
        bits.append(f"chip={hardware['chipKind']}")
    bits.append(f"queue={len(state['queue'])}")
    if status.get("currentProcessRunId"):
        bits.append(f"running={status['currentProcessRunId']}")
    if status.get("currentRemainingTime"):
        bits.append(f"remaining={status['currentRemainingTime']}")
    return "  ".join(bits)


def main():
    parser = argparse.ArgumentParser(description=__doc__,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("--host", required=True)
    parser.add_argument("--user", required=True)
    parser.add_argument("--password")
    parser.add_argument("--insecure", action="store_true")
    parser.add_argument("--queue", action="store_true",
                        help="also print the whole queue whenever it changes")
    args = parser.parse_args()

    password = args.password or getpass.getpass(f"password for {args.user}: ")
    kb = Kilobaser(args.host, args.user, password, verify=not args.insecure)
    kb.login()
    print(f"connected to {args.host} as {kb.user['id']} ({kb.user['role']})")
    print("waiting for events -- silence means the machine is idle, not disconnected\n")

    for name, state in kb.watch():
        stamp = datetime.now().strftime("%H:%M:%S")
        print(f"{stamp}  {name:<16} {summarise(state)}")

        pending = checkpoint(state)
        if pending:
            print(f"          waiting for someone to answer: {', '.join(pending)}")

        for error in state["errors"]:
            print(f"          error {error.get('code')}: {error.get('message')}")

        if args.queue and name in ("init", "processRunQueue"):
            for entry in state["queue"]:
                steps = " -> ".join(entry.get("processTypes") or [])
                print(f"          {entry['priority']:>2}. {entry['title']:<28} {steps}")


if __name__ == "__main__":
    main()
