"""#302 read-only SSH capture + local join; never applies a build or restarts a daemon."""
import argparse
import base64
from datetime import datetime, timezone
import gzip
import hashlib
import json
from pathlib import Path
import subprocess
import sys

HERE = Path(__file__).resolve().parent


def utc():
    return datetime.now(timezone.utc).isoformat()


def save(path, value):
    path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8")


def literal(value):
    return "'" + value.replace("'", "''") + "'"


def remote(host, script, timeout):
    # Compress into argv: bounded size, no remote file and no stdin-EOF dependency.
    packed = base64.b64encode(gzip.compress(script.encode("utf-8"), mtime=0)).decode()
    bootstrap = ("$m=[IO.MemoryStream]::new([Convert]::FromBase64String('" + packed + "'));"
                 "$z=[IO.Compression.GZipStream]::new($m,[IO.Compression.CompressionMode]::Decompress);"
                 "$r=[IO.StreamReader]::new($z,[Text.Encoding]::UTF8);"
                 "$s=$r.ReadToEnd();$r.Dispose();& ([ScriptBlock]::Create($s))")
    encoded = base64.b64encode(bootstrap.encode("utf-16le")).decode()
    if len(encoded) > 24000:
        raise ValueError("compressed remote script exceeds safe Windows argv bound")
    return subprocess.run(["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", host,
                           "powershell", "-NoProfile", "-EncodedCommand", encoded],
                          stdin=subprocess.DEVNULL, capture_output=True, timeout=timeout)


def snapshot(host, home, out, name):
    script = ("$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; "
              + ("$h=" + literal(home) + "; " if home else "$h=Join-Path $env:LOCALAPPDATA 'spt-core'; ") + r"""
$p=Join-Path $h 'logs\daemon.stderr.log'
$s=[IO.File]::Open($p,[IO.FileMode]::Open,[IO.FileAccess]::Read,([IO.FileShare]::ReadWrite -bor [IO.FileShare]::Delete))
try {
  $began=[DateTime]::UtcNow.ToString('o'); $n=$s.Length
  if($n -gt 16777216){throw 'log exceeds16MiB read-only snapshot bound'}
  $b=New-Object byte[] ([int]$n); $o=0
  while($o -lt $n){$r=$s.Read($b,$o,$n-$o);if($r -eq 0){throw 'log truncated while reading'};$o+=$r}
  [ordered]@{path=$p;begin_utc=$began;end_utc=[DateTime]::UtcNow.ToString('o');offset=0;bytes=$n;data=[Convert]::ToBase64String($b)}|ConvertTo-Json -Compress
} finally {$s.Dispose()}
""")
    result = remote(host, script, 35)
    (out/(name+".stderr.txt")).write_bytes(result.stderr)
    if result.returncode:
        raise RuntimeError(f"read-only log snapshot {name} failed: exit {result.returncode}")
    metadata = json.loads(result.stdout.decode("utf-8-sig"))
    data = base64.b64decode(metadata.pop("data"), validate=True)
    if len(data) != metadata["bytes"]:
        raise ValueError("snapshot byte count mismatch")
    metadata["sha256"] = hashlib.sha256(data).hexdigest()
    (out/(name+".log")).write_bytes(data)
    save(out/(name+".json"), metadata)
    return data, metadata


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--host", default="decid@enlyzeam")
    parser.add_argument("--home")
    parser.add_argument("--binary")
    parser.add_argument("--seconds", type=int, default=180)
    parser.add_argument("--out", type=Path, required=True)
    parser.add_argument("--pty", type=Path)
    args = parser.parse_args()
    if not 10 <= args.seconds <= 900:
        parser.error("seconds must be10..900")
    if not args.host or args.host.startswith("-"):
        parser.error("host must be an SSH destination, not an option")
    args.out.mkdir(parents=True, exist_ok=False)
    receipt = {"host": args.host, "started_utc": utc(), "seconds": args.seconds,
               "resident_actions": ["read daemon log", "node status --json"],
               "driver_writes_on_resident": False, "status": "RUNNING"}
    status = 1
    try:
        before, before_meta = snapshot(args.host, args.home, args.out, "log-before")
        script = "& {\n" + (HERE/"step1-collect.ps1").read_text(encoding="utf-8") + "\n} -Seconds " + str(args.seconds)
        if args.home:
            script += " -HomePath " + literal(args.home)
        if args.binary:
            script += " -Binary " + literal(args.binary)
        captured = remote(args.host, script, args.seconds+30)
        (args.out/"samples.jsonl").write_bytes(captured.stdout)
        (args.out/"collector.stderr.txt").write_bytes(captured.stderr)
        receipt["collector_exit"] = captured.returncode
        after, after_meta = snapshot(args.host, args.home, args.out, "log-after")
        if not after.startswith(before):
            raise RuntimeError("log rotated, truncated or overwritten; evidence retained but epochs not silently stitched")
        receipt["log_append_only"] = True
        receipt["log_bytes_added"] = len(after)-len(before)
        receipt["log_before"] = before_meta
        receipt["log_after"] = after_meta
        argv = [sys.executable, str(HERE/"step1-analyze.py"), "--samples", str(args.out/"samples.jsonl"),
                "--log", str(args.out/"log-after.log"), "--out", str(args.out/"analysis")]
        if args.pty:
            argv += ["--pty", str(args.pty)]
        analysis = subprocess.run(argv, capture_output=True, timeout=30)
        (args.out/"analysis.stdout.txt").write_bytes(analysis.stdout)
        (args.out/"analysis.stderr.txt").write_bytes(analysis.stderr)
        receipt["analysis_exit"] = analysis.returncode
        status = analysis.returncode if captured.returncode == 0 else 1
        receipt["status"] = "DISCRIMINATED" if status == 0 else "INSUFFICIENT_EVIDENCE" if status == 2 else "FAILED"
    except (OSError, ValueError, RuntimeError, subprocess.TimeoutExpired) as error:
        receipt.update(status="FAILED", error=repr(error))
        if isinstance(error, subprocess.TimeoutExpired):
            (args.out/"timeout.stdout.txt").write_bytes(error.stdout or b"")
            (args.out/"timeout.stderr.txt").write_bytes(error.stderr or b"")
            receipt["timeout_limit"] = "SSH client ended; remote collector has its own finite window. No resident process was killed."
    finally:
        receipt.update(ended_utc=utc(), exit_code=status)
        save(args.out/"field-receipt.json", receipt)
        print(json.dumps(receipt))
    return status


if __name__ == "__main__":
    raise SystemExit(main())
