#!/usr/bin/env python3
"""Hand a file to another agent without putting it in git history.

    share put <file> --note "what this is"   [--for decoder|port|referee|all]
    share ls [--for <who>] [--from <who>] [--all]
    share get <id> [<destination>]
    share drop <id>

Why this exists
---------------
Three kinds of thing were travelling down one channel, and they want opposite
treatment:

  * decoded knowledge and code   -- want history, review, permanence  -> git
  * evidence cited in a finding  -- wants permanence                  -> git
  * "look at this PNG I just made" -- wants NO history at all         -> here

Committing the third kind bloats the repository forever with files nobody will
read twice. Passing it by message is worse: the receiver gets bytes with no idea
which build produced them.

So this writes to a shared volume with a manifest entry recording who, when,
what, and -- the part that matters -- **the commit the sender was on**. A capture
with no provenance is not evidence, it is a picture.

Nothing here is durable. If a result is worth keeping, it belongs in the corpus
with its evidence, and this tool is not how you put it there.
"""

from __future__ import annotations

import argparse
import getpass
import hashlib
import json
import os
import shutil
import subprocess
import sys
import time
from pathlib import Path

ROOT = Path(os.environ.get("SYLPH_EXCHANGE", "/exchange"))
MANIFEST = ROOT / "manifest.jsonl"
WHO = os.environ.get("SYLPH_AGENT", getpass.getuser())

# A transient store that never forgets is just a slow repository.
TTL_DAYS = 14


def _git(*args: str) -> str:
    try:
        return subprocess.run(
            ["git", *args], capture_output=True, text=True, timeout=10
        ).stdout.strip()
    except Exception:
        return ""


def _provenance() -> dict:
    """The sender's commit, and whether the tree was dirty at the time.

    `dirty` is not a footnote: a capture taken from a modified tree cannot be
    reproduced from the sha alone, and the receiver deserves to know that before
    building an argument on it.
    """
    return {
        "commit": _git("rev-parse", "--short", "HEAD") or None,
        "branch": _git("rev-parse", "--abbrev-ref", "HEAD") or None,
        "dirty": bool(_git("status", "--porcelain")),
    }


def _entries() -> list[dict]:
    if not MANIFEST.exists():
        return []
    out = []
    for line in MANIFEST.read_text().splitlines():
        line = line.strip()
        if line:
            try:
                out.append(json.loads(line))
            except json.JSONDecodeError:
                continue
    return out


def _append(entry: dict) -> None:
    ROOT.mkdir(parents=True, exist_ok=True)
    with MANIFEST.open("a") as f:
        f.write(json.dumps(entry) + "\n")


def cmd_put(a: argparse.Namespace) -> int:
    src = Path(a.file)
    if not src.is_file():
        print(f"share: not a file: {src}", file=sys.stderr)
        return 1
    digest = hashlib.sha256(src.read_bytes()).hexdigest()[:12]
    ident = f"{int(time.time())}-{digest}"
    dest_dir = ROOT / "files"
    dest_dir.mkdir(parents=True, exist_ok=True)
    dest = dest_dir / f"{ident}-{src.name}"
    shutil.copy2(src, dest)

    _append(
        {
            "id": ident,
            "name": src.name,
            "path": str(dest),
            "bytes": dest.stat().st_size,
            "note": a.note,
            "from": WHO,
            "for": a.audience,
            "at": time.strftime("%Y-%m-%dT%H:%M:%S"),
            "source": _provenance(),
        }
    )
    print(f"share: {ident}  {src.name}  ({dest.stat().st_size} B)  for {a.audience}")
    print(f"       {dest}")
    return 0


def cmd_ls(a: argparse.Namespace) -> int:
    cutoff = time.time() - TTL_DAYS * 86400
    rows = _entries()
    shown = 0
    for e in rows:
        if e.get("id") in _dropped():
            continue
        stamp = int(str(e.get("id", "0")).split("-")[0] or 0)
        if not a.all and stamp < cutoff:
            continue
        if a.audience and e.get("for") not in (a.audience, "all"):
            continue
        if a.sender and e.get("from") != a.sender:
            continue
        src = e.get("source") or {}
        mark = "*" if src.get("dirty") else " "
        exists = "" if Path(e.get("path", "")).exists() else "  [MISSING]"
        print(
            f"{e['id']}  {e['at']}  {e['from']:>9} -> {e['for']:<8} "
            f"@{src.get('commit') or '?'}{mark}  {e['name']}{exists}"
        )
        if e.get("note"):
            print(f"    {e['note']}")
        shown += 1
    if not shown:
        print("share: nothing to show" + ("" if a.all else f" (last {TTL_DAYS} days)"))
    else:
        print(f"\n* = sender's tree was dirty; the commit alone will not reproduce it")
    return 0


def _dropped() -> set[str]:
    return {e["id"] for e in _entries() if e.get("dropped")}


def cmd_get(a: argparse.Namespace) -> int:
    for e in reversed(_entries()):
        if e.get("id") == a.id:
            src = Path(e["path"])
            if not src.exists():
                print(f"share: {a.id} is in the manifest but its file is gone",
                      file=sys.stderr)
                return 1
            dest = Path(a.dest) if a.dest else Path(e["name"])
            if dest.is_dir():
                dest = dest / e["name"]
            shutil.copy2(src, dest)
            print(f"share: {a.id} -> {dest}")
            src_meta = e.get("source") or {}
            if src_meta.get("dirty"):
                print("       NOTE: sender's tree was dirty -- not reproducible "
                      "from its commit alone")
            return 0
    print(f"share: no such id: {a.id}", file=sys.stderr)
    return 1


def cmd_drop(a: argparse.Namespace) -> int:
    for e in _entries():
        if e.get("id") == a.id:
            p = Path(e.get("path", ""))
            if p.exists():
                p.unlink()
            _append({"id": a.id, "dropped": True, "by": WHO,
                     "at": time.strftime("%Y-%m-%dT%H:%M:%S")})
            print(f"share: dropped {a.id}")
            return 0
    print(f"share: no such id: {a.id}", file=sys.stderr)
    return 1


def main() -> int:
    ap = argparse.ArgumentParser(prog="share", description=__doc__.splitlines()[0])
    sub = ap.add_subparsers(dest="cmd", required=True)

    p = sub.add_parser("put", help="offer a file to another agent")
    p.add_argument("file")
    p.add_argument("--note", required=True,
                   help="what it is and why the receiver should care")
    p.add_argument("--for", dest="audience", default="all",
                   choices=["decoder", "port", "referee", "all"])
    p.set_defaults(fn=cmd_put)

    p = sub.add_parser("ls", help="list what is on offer")
    p.add_argument("--for", dest="audience", default=None)
    p.add_argument("--from", dest="sender", default=None)
    p.add_argument("--all", action="store_true", help="include entries past the TTL")
    p.set_defaults(fn=cmd_ls)

    p = sub.add_parser("get", help="copy one out")
    p.add_argument("id")
    p.add_argument("dest", nargs="?")
    p.set_defaults(fn=cmd_get)

    p = sub.add_parser("drop", help="withdraw one")
    p.add_argument("id")
    p.set_defaults(fn=cmd_drop)

    args = ap.parse_args()
    return args.fn(args)


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