#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
SCENARIO_ROOT="$ROOT/.agents/skills/blacksite-native-qa/references/scenarios"
STATE_ROOT="$ROOT/.codex/session"
EVIDENCE_ROOT="$ROOT/.codex/evidence"

usage() {
  echo "usage: $0 plan|launch|attach|capture|record|status|restore|close <scenario-name> [arguments]" >&2
  exit 2
}

[[ $# -ge 2 ]] || usage
ACTION="$1"
NAME="$2"
SCENARIO="$SCENARIO_ROOT/$NAME.yaml"
[[ -f "$SCENARIO" ]] || { echo "native-qa: unknown scenario $NAME" >&2; exit 2; }

field() {
  local key="$1"
  sed -n "s/^${key}:[[:space:]]*//p" "$SCENARIO" | head -n 1
}

LANE="$(field build_lane)"
[[ -n "$LANE" ]] || LANE="dev"
PACKAGE="$(field package)"
[[ -n "$PACKAGE" ]] || PACKAGE="editor"
BINARY_NAME="$(field binary)"
[[ -n "$BINARY_NAME" ]] || BINARY_NAME="editor"
FIXTURE="$(field fixture)"
mkdir -p "$STATE_ROOT" "$EVIDENCE_ROOT"

lane_json() {
  python "$ROOT/scripts/codex/cargo_lane.py" env "$LANE" --json
}

list_section() {
  local section="$1"
  awk -v section="$section" '
    $0 == section ":" { active = 1; next }
    active && /^[^[:space:]]/ { exit }
    active && /^[[:space:]]+-[[:space:]]+/ {
      sub(/^[[:space:]]+-[[:space:]]+/, "")
      print
    }
  ' "$SCENARIO"
}

snapshot_fixture() {
  local record="$STATE_ROOT/native-$NAME.json"
  local snapshot="$STATE_ROOT/native-$NAME.fixture.snapshot"
  python - "$ROOT" "$FIXTURE" "$record" "$snapshot" "$LANE" "$SCENARIO" <<'PY'
import hashlib, json, os, pathlib, shutil, sys, time

root = pathlib.Path(sys.argv[1]).resolve()
fixture_text, record_text, snapshot_text, lane, scenario = sys.argv[2:]
record = pathlib.Path(record_text)
snapshot = pathlib.Path(snapshot_text)
if record.is_file():
    previous = json.loads(record.read_text(encoding="utf-8"))
    pid = previous.get("pid")
    if pid:
        try:
            os.kill(int(pid), 0)
        except ProcessLookupError:
            pass
        except PermissionError:
            raise SystemExit(f"native-qa: cannot verify existing runner PID {pid}; refusing launch")
        else:
            raise SystemExit(f"native-qa: existing runner PID {pid} is still active")
    old_fixture = previous.get("fixture")
    if old_fixture and not old_fixture.get("restored"):
        raise SystemExit("native-qa: restore the previous fixture before launching this scenario again")
    record.unlink()
    snapshot.unlink(missing_ok=True)
data = {
    "pid": None,
    "lane": lane,
    "scenario": scenario,
    "spawned_by_runner": False,
    "prepared_at": time.time(),
    "assertions": {},
    "evidence": [],
}
if fixture_text:
    fixture = (root / fixture_text).resolve()
    if fixture != root and root not in fixture.parents:
        raise SystemExit(f"fixture escapes workspace: {fixture}")
    if fixture.exists() and not fixture.is_file():
        raise SystemExit(f"fixture is not a regular file: {fixture}")
    existed = fixture.is_file()
    baseline = hashlib.sha256(fixture.read_bytes()).hexdigest() if existed else None
    if existed:
        shutil.copy2(fixture, snapshot)
    data["fixture"] = {
        "path": str(fixture),
        "existed": existed,
        "baseline_sha256": baseline,
        "snapshot": str(snapshot) if existed else None,
        "restored": False,
    }
record.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
PY
}

case "$ACTION" in
  plan)
    LANE_JSON="$(lane_json)" PACKAGE="$PACKAGE" BINARY_NAME="$BINARY_NAME" python - "$SCENARIO" "$FIXTURE" <<'PY'
import json, os, pathlib, sys
layout = json.loads(os.environ["LANE_JSON"])
scenario = pathlib.Path(sys.argv[1])
fixture = sys.argv[2]
binary = pathlib.Path(layout["target_dir"]) / layout["profile_dir"] / os.environ["BINARY_NAME"]
print(f"PASS native-qa-plan — {scenario.stem}")
print(f"Lane: {layout['lane']} ({layout['mode']})")
print(f"Package: {os.environ['PACKAGE']}")
print(f"Binary: {binary}")
print(f"Runtime deps: {layout['runtime_deps']}")
print(f"Fixture: {fixture or 'scenario-defined'}")
print("Visual interaction remains manual/user-controlled until explicitly delegated.")
PY
    echo "Purpose: $(field purpose)"
    echo "Preconditions: $(field preconditions)"
    echo "Steps:"
    list_section steps | nl -w2 -s'. '
    echo "Assertions:"
    list_section assertions | nl -w2 -s'. '
    echo "Evidence: $(field evidence)"
    echo "Cleanup: $(field cleanup)"
    ;;
  launch)
    snapshot_fixture
    python "$ROOT/scripts/codex/build_storage.py" enforce --phase pre
    python "$ROOT/scripts/codex/summarize_command.py" \
      --gate "native-$NAME-build" \
      --log "$ROOT/.codex/logs/native-$NAME-build.log" \
      --json-result "$ROOT/.codex/logs/native-$NAME-build.json" \
      -- python "$ROOT/scripts/codex/cargo_lane.py" exec "$LANE" -- cargo build -p "$PACKAGE" --bin "$BINARY_NAME"
    python "$ROOT/scripts/codex/build_storage.py" enforce --phase post
    LAUNCH_ARGS=()
    if [[ "$BINARY_NAME" = "editor" ]]; then
      LAUNCH_ARGS=(--project "$ROOT")
    fi
    nohup python "$ROOT/scripts/codex/cargo_lane.py" run "$LANE" -- \
      "target/debug/$BINARY_NAME" "${LAUNCH_ARGS[@]}" \
      >"$ROOT/.codex/logs/native-$NAME-launch.log" 2>&1 &
    PID=$!
    python - "$STATE_ROOT/native-$NAME.json" "$PID" <<'PY'
import json, pathlib, sys, time
path = pathlib.Path(sys.argv[1])
data = json.loads(path.read_text(encoding="utf-8"))
data.update({"pid": int(sys.argv[2]), "spawned_at": time.time(), "spawned_by_runner": True})
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
PY
    echo "PASS native-qa-launch — $NAME — PID $PID"
    ;;
  attach)
    [[ $# -eq 3 ]] || usage
    PID="$3"
    kill -0 "$PID" 2>/dev/null || { echo "native-qa: PID $PID is not running" >&2; exit 2; }
    CMDLINE="$(tr '\0' ' ' <"/proc/$PID/cmdline")"
    [[ "$CMDLINE" == *"$BINARY_NAME"* ]] || { echo "native-qa: refusing unexpected process: $CMDLINE" >&2; exit 2; }
    snapshot_fixture
    python - "$STATE_ROOT/native-$NAME.json" "$PID" <<'PY'
import json, pathlib, sys, time
path = pathlib.Path(sys.argv[1])
data = json.loads(path.read_text(encoding="utf-8"))
data.update({"pid": int(sys.argv[2]), "attached_at": time.time(), "spawned_by_runner": False})
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
PY
    echo "PASS native-qa-attach — $NAME — PID $PID"
    ;;
  capture)
    [[ $# -eq 3 ]] || usage
    PID="$3"
    kill -0 "$PID" 2>/dev/null || { echo "native-qa: PID $PID is not running" >&2; exit 2; }
    CLIENTS="$(hyprctl clients -j)"
    GEOMETRY="$(CLIENTS_JSON="$CLIENTS" python - "$PID" <<'PY'
import json, os, sys
pid = int(sys.argv[1])
matches = [c for c in json.loads(os.environ["CLIENTS_JSON"]) if c.get("pid") == pid]
if len(matches) != 1:
    raise SystemExit(f"expected one Hyprland window for PID {pid}, found {len(matches)}")
client = matches[0]
x, y = client["at"]
w, h = client["size"]
if w <= 0 or h <= 0:
    raise SystemExit("target window has invalid geometry")
print(f"{x},{y} {w}x{h}")
PY
)"
    STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
    IMAGE="$EVIDENCE_ROOT/$NAME-$STAMP.png"
    grim -g "$GEOMETRY" "$IMAGE"
    HASH="$(sha256sum "$IMAGE" | awk '{print $1}')"
    python - "$STATE_ROOT/native-$NAME-evidence.json" "$PID" "$IMAGE" "$HASH" "$GEOMETRY" <<'PY'
import json, pathlib, sys, time
path = pathlib.Path(sys.argv[1])
path.write_text(json.dumps({
    "pid": int(sys.argv[2]), "image": sys.argv[3], "sha256": sys.argv[4],
    "geometry": sys.argv[5], "captured_at": time.time(),
}, indent=2) + "\n", encoding="utf-8")
PY
    python - "$STATE_ROOT/native-$NAME.json" "$IMAGE" "$HASH" "$GEOMETRY" <<'PY'
import json, pathlib, sys, time
path = pathlib.Path(sys.argv[1])
if not path.is_file():
    raise SystemExit("native-qa: missing runner record")
data = json.loads(path.read_text(encoding="utf-8"))
data.setdefault("evidence", []).append({
    "image": sys.argv[2], "sha256": sys.argv[3], "geometry": sys.argv[4],
    "captured_at": time.time(),
})
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
PY
    echo "PASS native-qa-capture — $NAME — $IMAGE — $HASH"
    ;;
  record)
    [[ $# -ge 4 && $# -le 5 ]] || usage
    INDEX="$3"
    RESULT="$4"
    NOTE="${5:-}"
    [[ "$INDEX" =~ ^[1-9][0-9]*$ ]] || { echo "native-qa: assertion index must be positive" >&2; exit 2; }
    [[ "$RESULT" = "pass" || "$RESULT" = "fail" ]] || { echo "native-qa: result must be pass or fail" >&2; exit 2; }
    ASSERTION_COUNT="$(list_section assertions | wc -l)"
    (( INDEX <= ASSERTION_COUNT )) || { echo "native-qa: assertion $INDEX exceeds count $ASSERTION_COUNT" >&2; exit 2; }
    ASSERTION_TEXT="$(list_section assertions | sed -n "${INDEX}p")"
    python - "$STATE_ROOT/native-$NAME.json" "$INDEX" "$RESULT" "$NOTE" "$ASSERTION_TEXT" <<'PY'
import json, pathlib, sys, time
path = pathlib.Path(sys.argv[1])
if not path.is_file():
    raise SystemExit("native-qa: missing runner record")
data = json.loads(path.read_text(encoding="utf-8"))
data.setdefault("assertions", {})[sys.argv[2]] = {
    "result": sys.argv[3], "note": sys.argv[4], "text": sys.argv[5],
    "recorded_at": time.time(),
}
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
PY
    echo "PASS native-qa-record — $NAME — assertion $INDEX = $RESULT"
    ;;
  status)
    [[ $# -eq 2 ]] || usage
    RECORD="$STATE_ROOT/native-$NAME.json"
    [[ -f "$RECORD" ]] || { echo "native-qa: no runner record for $NAME" >&2; exit 2; }
    ASSERTION_COUNT="$(list_section assertions | wc -l)"
    python - "$RECORD" "$ASSERTION_COUNT" <<'PY'
import json, pathlib, sys, time
path = pathlib.Path(sys.argv[1])
data = json.loads(path.read_text(encoding="utf-8"))
expected = int(sys.argv[2])
results = data.get("assertions", {})
passed = sum(item.get("result") == "pass" for item in results.values())
failed = sum(item.get("result") == "fail" for item in results.values())
print(f"native-qa-status — assertions {passed} pass, {failed} fail, {expected - len(results)} unrecorded")
print(f"evidence: {len(data.get('evidence', []))} capture(s)")
fixture = data.get("fixture")
if fixture:
    print(f"fixture restored: {bool(fixture.get('restored'))}")
complete = not failed and len(results) == expected and bool(data.get("evidence"))
data["scenario_result"] = {"result": "PASS" if complete else "FAIL", "recorded_at": time.time()}
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
if not complete:
    raise SystemExit(1)
PY
    ;;
  restore)
    [[ $# -eq 3 ]] || usage
    MODE="$3"
    [[ "$MODE" = "--dry-run" || "$MODE" = "--apply" ]] || usage
    RECORD="$STATE_ROOT/native-$NAME.json"
    [[ -f "$RECORD" ]] || { echo "native-qa: no runner record for $NAME" >&2; exit 2; }
    python - "$RECORD" "$MODE" "$ROOT" <<'PY'
import hashlib, json, os, pathlib, shutil, sys, tempfile, time

record = pathlib.Path(sys.argv[1])
apply = sys.argv[2] == "--apply"
root = pathlib.Path(sys.argv[3]).resolve()
data = json.loads(record.read_text(encoding="utf-8"))
fixture = data.get("fixture")
if not fixture:
    print("PASS native-qa-restore — scenario has no fixture")
    raise SystemExit(0)
path = pathlib.Path(fixture["path"]).resolve()
if path != root and root not in path.parents:
    raise SystemExit(f"native-qa: fixture escapes workspace: {path}")
pid = data.get("pid")
if apply and pid:
    try:
        os.kill(int(pid), 0)
    except ProcessLookupError:
        pass
    except PermissionError:
        raise SystemExit(f"native-qa: cannot verify runner PID {pid}; refusing fixture restore")
    else:
        raise SystemExit(f"native-qa: close runner PID {pid} before restoring the fixture")
current = hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None
baseline = fixture.get("baseline_sha256")
action = "restore snapshot" if fixture.get("existed") else "remove scenario-created fixture"
print(f"native-qa-restore-plan — {action}")
print(f"fixture: {path}")
print(f"baseline sha256: {baseline or '(absent)'}")
print(f"current sha256: {current or '(absent)'}")
sys.stdout.flush()
if not apply:
    raise SystemExit(0)
if fixture.get("existed"):
    snapshot = pathlib.Path(fixture["snapshot"])
    if not snapshot.is_file():
        raise SystemExit(f"native-qa: missing fixture snapshot: {snapshot}")
    snapshot_hash = hashlib.sha256(snapshot.read_bytes()).hexdigest()
    if snapshot_hash != baseline:
        raise SystemExit("native-qa: fixture snapshot hash does not match recorded baseline")
    path.parent.mkdir(parents=True, exist_ok=True)
    with tempfile.NamedTemporaryFile(dir=path.parent, prefix=f".{path.name}.", delete=False) as handle:
        temp = pathlib.Path(handle.name)
        handle.write(snapshot.read_bytes())
    os.replace(temp, path)
else:
    if path.exists() and not path.is_file():
        raise SystemExit(f"native-qa: refusing to remove non-file fixture path: {path}")
    path.unlink(missing_ok=True)
fixture["restored"] = True
fixture["restored_at"] = time.time()
record.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
print("PASS native-qa-restore")
PY
    ;;
  close)
    [[ $# -eq 3 ]] || usage
    PID="$3"
    RECORD="$STATE_ROOT/native-$NAME.json"
    [[ -f "$RECORD" ]] || { echo "native-qa: no runner record for $NAME" >&2; exit 2; }
    RECORDED="$(python -c 'import json,sys; print(json.load(open(sys.argv[1]))["pid"])' "$RECORD")"
    [[ "$RECORDED" = "$PID" ]] || { echo "native-qa: PID does not match runner record" >&2; exit 2; }
    SPAWNED="$(python -c 'import json,sys; print(str(bool(json.load(open(sys.argv[1])).get("spawned_by_runner"))).lower())' "$RECORD")"
    if [[ "$SPAWNED" = "true" ]] && kill -0 "$PID" 2>/dev/null; then
      CMDLINE="$(tr '\0' ' ' <"/proc/$PID/cmdline")"
      [[ "$CMDLINE" == *"$BINARY_NAME"* ]] || { echo "native-qa: refusing to stop unexpected process: $CMDLINE" >&2; exit 2; }
      kill -TERM "$PID"
    fi
    python - "$RECORD" <<'PY'
import json, pathlib, time, sys
path = pathlib.Path(sys.argv[1])
data = json.loads(path.read_text(encoding="utf-8"))
data["closed_at"] = time.time()
data["detached_only"] = not bool(data.get("spawned_by_runner"))
if data["detached_only"]:
    data["detached_pid"] = data.get("pid")
    data["pid"] = None
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
PY
    echo "PASS native-qa-close — $NAME — PID $PID"
    ;;
  *) usage ;;
esac
