#!/usr/bin/env python3
"""Set a task's status in import/progress.json, atomically.

Sources of truth: import/progress.json. Several sessions edit it in one working tree, so
this takes an exclusive lock, rewrites in place, and stamps updated_at/updated_by. The
/plan page reads the file per request; no deploy is needed.

    python3 main/scripts/plan-set.py A1 doing "Stream A"
    python3 main/scripts/plan-set.py A1 done  "Stream A" --note "robots.py landed, 12 tests"

Registering a task (a planning session's job; a stream session never adds ids) goes through
the same lock, because a task list edited by hand once carried a merge conflict into the
running list and nothing noticed:

    python3 main/scripts/plan-set.py G1 todo "Away plan" --add --stream G \
        --stream-title "G · The complete airport page" --session /stream-g \
        --wave w3 --due 2026-09-14 --title "Opening hours, collected where robots allow"

`--add` creates the stream when it does not exist (then `--stream-title` is required) and
refuses an id that already exists, so a re-run cannot duplicate a row.
"""
import argparse, datetime, fcntl, json, pathlib, sys

STATUSES = ("done", "doing", "todo", "blocked", "deferred")
ROOT = pathlib.Path(__file__).resolve().parents[2]
FILE = ROOT / "import" / "progress.json"

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("task_id"); ap.add_argument("status", choices=STATUSES); ap.add_argument("who")
    ap.add_argument("--note", default=None, help="optional short note appended to the title in brackets")
    ap.add_argument("--add", action="store_true", help="register a new task id (see the docstring)")
    ap.add_argument("--stream", help="--add: the stream key the task joins (created if absent)")
    ap.add_argument("--stream-title", help="--add: the title of a stream being created")
    ap.add_argument("--session", help="--add: the /stream-x command of a stream being created")
    ap.add_argument("--title", help="--add: the task title")
    ap.add_argument("--wave", default="buf", help="--add: the wave key (w1..w4, buf)")
    ap.add_argument("--due", help="--add: ISO due date")
    ap.add_argument("--paid", action="store_true", help="--add: a quote line pays for it")
    ap.add_argument("--rian", action="store_true", help="--add: rian's own task")
    a = ap.parse_args()
    if a.add and not (a.stream and a.title):
        ap.error("--add needs --stream and --title")
    with FILE.open("r+") as fh:
        fcntl.flock(fh, fcntl.LOCK_EX)
        data = json.load(fh)
        hit = None
        if a.add:
            if any(t["id"] == a.task_id for s in data["streams"] for t in s["tasks"]):
                print(f"{a.task_id} already exists; use the status form to change it", file=sys.stderr); return 2
            stream = next((s for s in data["streams"] if s["key"] == a.stream), None)
            if stream is None:
                if not a.stream_title:
                    print(f"stream {a.stream} does not exist; pass --stream-title to create it", file=sys.stderr); return 2
                stream = {"key": a.stream, "title": a.stream_title, "session": a.session or "", "tasks": []}
                data["streams"].append(stream)
            task = {"id": a.task_id, "wave": a.wave, "title": a.title, "status": a.status, "paid": a.paid}
            if a.due:
                task["due"] = a.due
            if a.rian:
                task["rian"] = True
            stream["tasks"].append(task)
        for s in data["streams"]:
            for t in s["tasks"]:
                if t["id"] == a.task_id:
                    t["status"] = a.status
                    if a.note:
                        t["title"] = t["title"].split(" [")[0] + f" [{a.note}]"
                    hit = (s["key"], t["title"])
        if not hit:
            print(f"no task {a.task_id}", file=sys.stderr); return 2
        data["updated_at"] = datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="minutes")
        data["updated_by"] = a.who
        fh.seek(0); fh.truncate(); json.dump(data, fh, indent=1); fh.write("\n")
    print(f"{a.task_id} -> {a.status}  ({hit[0]}: {hit[1][:70]})")
    return 0

if __name__ == "__main__":
    sys.exit(main())
