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

ROOT=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd)
TMP=$(mktemp -d)
cleanup() { if [[ ${KEEP_PHASE1_TEST_TMP:-0} == 1 ]]; then printf 'phase1 test tmp retained: %s\n' "$TMP" >&2; else rm -rf "$TMP"; fi; }
trap cleanup EXIT
export HOME="$TMP/home"
export FAKE_STATE="$TMP/state"
mkdir -p "$HOME" "$TMP/bin" "$FAKE_STATE" "$TMP/repo/modules/workstation/claude"

cat >"$TMP/repo/modules/workstation/claude/buildbox-hosts.json" <<'JSON'
{
  "schema_version": 1,
  "hosts": [
    {
      "name": "debian3",
      "state": "reachable",
      "access": {
        "tailscale_ip": {"host": "100.101.104.41", "port": 2222, "user": "user", "identity_file": null},
        "tailscale_ssh": {"host": "debian3.example.ts.net", "port": 22, "user": "user", "identity_file": null}
      }
    },
    {
      "name": "debian1",
      "state": "reachable",
      "access": {
        "tailscale_ip": {"host": "100.106.253.50", "port": 2222, "user": "user", "identity_file": null},
        "tailscale_ssh": {"host": "debian1.example.ts.net", "port": 22, "user": "user", "identity_file": null}
      }
    },
    {
      "name": "debian2",
      "state": "unreachable",
      "access": {
        "tailscale_ip": {"host": "100.79.69.43", "port": 2222, "user": "user", "identity_file": null}
      }
    }
  ]
}
JSON

cat >"$TMP/kubeconfig" <<'YAML'
apiVersion: v1
kind: Config
clusters: []
contexts: []
current-context: ""
users: []
YAML
chmod 600 "$TMP/kubeconfig"

# Reproduce the live debian3 topology that invalidated Phase 1 v3:
# - /usr/local/bin/k3s is the outer command launcher.
# - the running extracted applet is a different executable.
# - agent-token is represented as a symlink to server/token, never archived as a link.
python3 - "$TMP/prechange.tar" "$TMP/postchange.tar" <<'PY'
from __future__ import annotations
import hashlib
import json
import os
import sqlite3
import stat
import sys
import tarfile
import tempfile
from pathlib import Path

LAUNCHER_SHA = "a" * 64
RUNTIME_SHA = "d" * 64
CA_SHA = "b" * 64
VERSION = "v1.36.3+k3s1"


def write_file(path: Path, data: bytes | str, mode: int = 0o600) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    if isinstance(data, str):
        path.write_text(data)
    else:
        path.write_bytes(data)
    path.chmod(mode)


def build(archive: Path, purpose: str) -> None:
    with tempfile.TemporaryDirectory() as temporary:
        root = Path(temporary)
        payload = root / "payload"
        db = payload / "datastore/db/state.db"
        db.parent.mkdir(parents=True)
        connection = sqlite3.connect(db)
        connection.execute("create table proof(value text not null)")
        connection.execute("insert into proof values ('phase1-qualified')")
        connection.commit()
        connection.close()
        db.chmod(0o600)

        write_file(payload / "server/token", "K10fixture::0123456789abcdef0123456789abcdef\n")
        write_file(
            payload / "config/etc-rancher-k3s/config.yaml",
            "# pre-existing installer configuration retained by Phase 1\n",
        )

        if purpose == "postchange":
            write_file(
                payload / "config/etc-rancher-k3s/config.yaml.d/90-overdeck-control-plane.yaml",
                '# Managed by Overdeck tools/k3s. Do not hand-edit.\n'
                'write-kubeconfig-mode: "0600"\n'
                'tls-san+:\n'
                '  - "100.101.104.41"\n',
            )
            contract = {
                "schema_version": 2,
                "managed_by": "overdeck-k3s-phase1",
                "server": {
                    "hostname": "debian3",
                    "machine_id": "fixture-machine-id-debian3",
                    "tailscale_ipv4": "100.101.104.41",
                },
                "api": {"endpoint": "https://100.101.104.41:6443", "tls_sans": ["100.101.104.41"]},
            }
            write_file(
                payload / "config/etc-rancher-k3s/overdeck/control-plane.json",
                json.dumps(contract, indent=2, sort_keys=True) + "\n",
            )
            lock = {
                "schema_version": 2,
                "managed_by": "overdeck-k3s-phase1",
                "version": VERSION,
                "launcher": {
                    "path": "/usr/local/bin/k3s",
                    "resolved_path": "/usr/local/bin/k3s",
                    "sha256": LAUNCHER_SHA,
                    "release_url": "https://github.com/k3s-io/k3s/releases/download/v1.36.3%2Bk3s1/k3s",
                },
                "runtime": {
                    "present": True,
                    "resolved_path": "/var/lib/rancher/k3s/data/fixture/bin/k3s",
                    "sha256": RUNTIME_SHA,
                },
            }
            write_file(
                payload / "config/etc-rancher-k3s/overdeck/version-lock.json",
                json.dumps(lock, indent=2, sort_keys=True) + "\n",
            )

        files = []
        for path in sorted(payload.rglob("*")):
            if not path.is_file() or path.is_symlink():
                continue
            info = path.stat()
            files.append(
                {
                    "path": path.relative_to(root).as_posix(),
                    "size": info.st_size,
                    "sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
                    "mode": f"{stat.S_IMODE(info.st_mode):04o}",
                    "uid": 0,
                    "gid": 0,
                }
            )

        manifest = {
            "schema_version": 2,
            "created_utc": "2026-08-11T00:00:00Z",
            "transaction_id": f"backup-{purpose}-fixture",
            "purpose": purpose,
            "server": {
                "hostname": "debian3",
                "machine_id": "fixture-machine-id-debian3",
                "tailscale_ipv4": "100.101.104.41",
            },
            "k3s": {
                "version": VERSION,
                "launcher": {
                    "invocation_path": "/usr/local/bin/k3s",
                    "resolved_path": "/usr/local/bin/k3s",
                    "sha256": LAUNCHER_SHA,
                },
                "runtime": {
                    "present": True,
                    "resolved_path": "/var/lib/rancher/k3s/data/fixture/bin/k3s",
                    "sha256": RUNTIME_SHA,
                },
                "cacerts_sha256": CA_SHA,
            },
            "datastore": {"type": "sqlite", "state_db": "payload/datastore/db/state.db"},
            "server_token_path": "payload/server/token",
            "agent_token": {
                "classification": "symlink-to-server-token",
                "restore": "symlink-to-token",
                "link_target": "token",
                "payload_path": None,
            },
            "systemd": {
                "unit_fragment": "/etc/systemd/system/k3s.service",
                "service_path": "/etc/systemd/system/k3s.service",
                "environment_path": "/etc/systemd/system/k3s.service.env",
            },
            "files": files,
        }
        write_file(root / "manifest.json", json.dumps(manifest, indent=2, sort_keys=True) + "\n")
        with tarfile.open(archive, "w", format=tarfile.PAX_FORMAT) as handle:
            handle.add(root / "manifest.json", arcname="manifest.json", recursive=False)
            handle.add(payload, arcname="payload", recursive=True)


build(Path(sys.argv[1]), "prechange")
build(Path(sys.argv[2]), "postchange")
PY

export FAKE_PRE_TAR="$TMP/prechange.tar"
export FAKE_POST_TAR="$TMP/postchange.tar"
export FAKE_PRE_SIZE FAKE_PRE_SHA FAKE_POST_SIZE FAKE_POST_SHA FAKE_REMOTE_HELPER_SHA
FAKE_PRE_SIZE=$(stat -c %s "$FAKE_PRE_TAR")
FAKE_PRE_SHA=$(sha256sum "$FAKE_PRE_TAR" | awk '{print $1}')
FAKE_POST_SIZE=$(stat -c %s "$FAKE_POST_TAR")
FAKE_POST_SHA=$(sha256sum "$FAKE_POST_TAR" | awk '{print $1}')
FAKE_REMOTE_HELPER_SHA=$(sha256sum "$ROOT/tools/k3s/remote/phase1-server.py" | awk '{print $1}')
export FAKE_PRE_SIZE FAKE_PRE_SHA FAKE_POST_SIZE FAKE_POST_SHA FAKE_REMOTE_HELPER_SHA

cat >"$TMP/bin/ssh" <<'SHIM'
#!/usr/bin/env bash
set -euo pipefail
joined=" $* "
printf '%s\n' "$*" >>"$FAKE_STATE/ssh.log"

if [[ "$joined" == *" umask 077; cat > /tmp/overdeck-k3s-phase1-"* ]]; then
  cat >"$FAKE_STATE/uploaded-helper.py"
  exit 0
fi
if [[ "$joined" == *" chmod 0700 /tmp/overdeck-k3s-phase1-"* ]]; then
  exit 0
fi
if [[ "$joined" == *" sha256sum /tmp/overdeck-k3s-phase1-"* ]]; then
  remote_path=${joined##* sha256sum }
  remote_path=${remote_path%% *}
  printf '%s  %s\n' "$FAKE_REMOTE_HELPER_SHA" "$remote_path"
  exit 0
fi
if [[ "$joined" == *" rm -f /tmp/overdeck-k3s-phase1-"* ]]; then
  rm -f "$FAKE_STATE/uploaded-helper.py"
  exit 0
fi

if [[ "$joined" == *"/tmp/overdeck-k3s-phase1-"*".py inspect "* ]]; then
  cat <<'JSON'
{
  "schema_version": 2,
  "generated_utc": "2026-08-11T00:00:00Z",
  "hostname": "debian3",
  "machine_id": "fixture-machine-id-debian3",
  "service": {
    "state": "active",
    "ready": {
      "ready": true,
      "service": "active",
      "classification": "ready",
      "detail": "ok",
      "launcher_role": "outer-command-launcher",
      "launcher_path": "/usr/local/bin/k3s"
    }
  },
  "k3s": {
    "version": "v1.36.3+k3s1",
    "launcher": {
      "role": "outer-command-launcher",
      "source": "systemd-execstart",
      "invocation_path": "/usr/local/bin/k3s",
      "resolved_path": "/usr/local/bin/k3s",
      "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "version": "v1.36.3+k3s1"
    },
    "runtime": {
      "present": true,
      "role": "running-executable-evidence",
      "resolved_path": "/var/lib/rancher/k3s/data/fixture/bin/k3s",
      "sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
    },
    "unit_fragment": "/etc/systemd/system/k3s.service"
  },
  "datastore": {"type": "sqlite", "db_dir": "/var/lib/rancher/k3s/server/db"},
  "server_token": {"exists": true, "supported": true, "classification": "regular"},
  "agent_token": {
    "exists": true,
    "supported": true,
    "classification": "symlink-to-server-token",
    "target": "/var/lib/rancher/k3s/server/token",
    "restore": "symlink-to-token"
  },
  "tailscale": {
    "interface_present": true,
    "command_present": true,
    "ipv4": "100.101.104.41",
    "dns_name": "debian3.example.ts.net"
  },
  "nodes": [
    {"name": "debian3", "ready": true},
    {"name": "debian1", "ready": true},
    {"name": "e14", "ready": true}
  ],
  "node_probe": {
    "ok": true,
    "classification": "ready",
    "ready_count": 3,
    "total_count": 3,
    "nodes": [
      {"name": "debian3", "ready": true},
      {"name": "debian1", "ready": true},
      {"name": "e14", "ready": true}
    ]
  },
  "serving_certificate": {"exists": true, "sans": ["100.101.104.41"]},
  "active_transactions": [],
  "cacerts_probe": {
    "ok": true,
    "size": 123,
    "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  },
  "cacerts_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  "cacerts_size": 123
}
JSON
  exit 0
fi

if [[ "$joined" == *"/tmp/overdeck-k3s-phase1-"*".py plan "* ]]; then
  cat <<'JSON'
{
  "schema_version": 1,
  "changed": true,
  "files": [],
  "desired_managed_config_b64": "eA==",
  "desired_contract": {"schema_version": 2, "managed_by": "overdeck-k3s-phase1"},
  "desired_version_lock": {"schema_version": 2, "managed_by": "overdeck-k3s-phase1"},
  "observed": {"service": {"state": "active"}}
}
JSON
  exit 0
fi

if [[ "$joined" == *"/tmp/overdeck-k3s-phase1-"*".py converge "* ]]; then
  tx=$(sed -n 's/.*--transaction \([^ ]*\).*/\1/p' <<<"$joined")
  printf '{"schema_version":1,"transaction_id":"%s","changed":true,"status":"converged","files":[],"readiness":{"ready":true}}\n' "$tx"
  exit 0
fi

if [[ "$joined" == *"/tmp/overdeck-k3s-phase1-"*".py backup "* ]]; then
  tx=$(sed -n 's/.*--transaction \([^ ]*\).*/\1/p' <<<"$joined")
  purpose=$(sed -n 's/.*--purpose \([^ ]*\).*/\1/p' <<<"$joined")
  case "$purpose" in
    prechange)
      archive=/remote/prechange.tar; size=$FAKE_PRE_SIZE; sha=$FAKE_PRE_SHA ;;
    postchange)
      archive=/remote/postchange.tar; size=$FAKE_POST_SIZE; sha=$FAKE_POST_SHA ;;
    *)
      printf 'unexpected backup purpose: %s\n' "$purpose" >&2; exit 93 ;;
  esac
  printf '{"schema_version":2,"transaction_id":"%s","purpose":"%s","status":"staged","archive_path":"%s","archive_size":%s,"archive_sha256":"%s","manifest":{"created_utc":"2026-08-11T00:00:00Z","server":{"hostname":"debian3"},"k3s":{"version":"v1.36.3+k3s1","launcher":{"sha256":"%s"}},"datastore":{"type":"sqlite"},"file_count":4}}\n' "$tx" "$purpose" "$archive" "$size" "$sha" "$(printf 'a%.0s' {1..64})"
  exit 0
fi

if [[ "$joined" == *" sudo -n cat /remote/prechange.tar "* ]]; then
  cat "$FAKE_PRE_TAR"
  exit 0
fi
if [[ "$joined" == *" sudo -n cat /remote/postchange.tar "* ]]; then
  cat "$FAKE_POST_TAR"
  exit 0
fi
if [[ "$joined" == *"/tmp/overdeck-k3s-phase1-"*".py cleanup-backup "* ]]; then
  tx=$(sed -n 's/.*--transaction \([^ ]*\).*/\1/p' <<<"$joined")
  printf '{"transaction_id":"%s","status":"cleaned"}\n' "$tx"
  exit 0
fi
if [[ "$joined" == *"/tmp/overdeck-k3s-phase1-"*".py finalize "* ]]; then
  tx=$(sed -n 's/.*--transaction \([^ ]*\).*/\1/p' <<<"$joined")
  printf '{"transaction_id":"%s","status":"finalized"}\n' "$tx"
  exit 0
fi
if [[ "$joined" == *"/tmp/overdeck-k3s-phase1-"*".py rollback "* ]]; then
  tx=$(sed -n 's/.*--transaction \([^ ]*\).*/\1/p' <<<"$joined")
  printf '{"transaction_id":"%s","status":"rolled-back","readiness":{"ready":true}}\n' "$tx"
  exit 0
fi

if [[ "$joined" == *" python3 - https://100.101.104.41:6443 "* ]]; then
  cat >/dev/null
  printf '%s\n' '{"status":"reachable","url":"https://100.101.104.41:6443/cacerts","sha256":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","size":123,"tls_verified":true,"peer_sans":["100.101.104.41"]}'
  exit 0
fi

printf 'unexpected fake ssh invocation: %s\n' "$*" >&2
exit 90
SHIM
chmod +x "$TMP/bin/ssh"

cat >"$TMP/bin/kubectl" <<'SHIM'
#!/usr/bin/env bash
set -euo pipefail
joined=" $* "
if [[ "$joined" == *" config view "* ]]; then
  printf '%s\n' '{"clusters":[{"cluster":{"server":"https://127.0.0.1:6443"}}]}'
elif [[ "$joined" == *" get --raw=/readyz "* ]]; then
  printf 'ok\n'
else
  printf 'unexpected kubectl: %s\n' "$*" >&2
  exit 91
fi
SHIM
chmod +x "$TMP/bin/kubectl"

cat >"$TMP/bin/age-keygen" <<'SHIM'
#!/usr/bin/env bash
set -euo pipefail
if [[ ${1:-} == -o ]]; then
  printf 'AGE-SECRET-KEY-FAKE\n' >"$2"
  exit 0
fi
if [[ ${1:-} == -y ]]; then
  printf 'age1fixture000000000000000000000000000000000000000000000000000\n'
  exit 0
fi
exit 92
SHIM
chmod +x "$TMP/bin/age-keygen"

cat >"$TMP/bin/age" <<'SHIM'
#!/usr/bin/env bash
set -euo pipefail
out=""
decrypt=0
input=""
while (($#)); do
  case "$1" in
    -d) decrypt=1; shift ;;
    -i|-r) shift 2 ;;
    -o) out=$2; shift 2 ;;
    *) input=$1; shift ;;
  esac
done
[[ -n "$out" ]]
if ((decrypt)); then
  cp "$input" "$out"
else
  cat >"$out"
fi
SHIM
chmod +x "$TMP/bin/age"

cat >"$TMP/bin/systemctl" <<'SHIM'
#!/usr/bin/env bash
set -euo pipefail
joined=" $* "
if [[ "$joined" == *" --user show-environment "* ]]; then
  printf 'PATH=/usr/bin\n'
  exit 0
fi
if [[ "$joined" == *" is-enabled overdeck-k3s-backup.timer "* ]]; then
  [[ -f "$FAKE_STATE/timer-enabled" ]]
  exit
fi
if [[ "$joined" == *" is-active overdeck-k3s-backup.service "* ]]; then
  exit 3
fi
if [[ "$joined" == *" is-active overdeck-k3s-backup.timer "* ]]; then
  [[ -f "$FAKE_STATE/timer-active" ]]
  exit
fi
if [[ "$joined" == *" enable --now overdeck-k3s-backup.timer "* ]]; then
  touch "$FAKE_STATE/timer-enabled" "$FAKE_STATE/timer-active"
  exit 0
fi
if [[ "$joined" == *" disable --now overdeck-k3s-backup.timer "* ]]; then
  rm -f "$FAKE_STATE/timer-enabled" "$FAKE_STATE/timer-active"
  exit 0
fi
if [[ "$joined" == *" stop overdeck-k3s-backup.timer "* ]]; then
  rm -f "$FAKE_STATE/timer-active"
  exit 0
fi
if [[ "$joined" == *" daemon-reload "* ]]; then
  exit 0
fi
if [[ "$joined" == *" show overdeck-k3s-backup.timer "* ]]; then
  printf 'ActiveState=active\nUnitFileState=enabled\nNextElapseUSecRealtime=Tue 2026-08-11 03:17:00 +07\n'
  exit 0
fi
printf 'unexpected systemctl: %s\n' "$*" >&2
exit 94
SHIM
chmod +x "$TMP/bin/systemctl"

cat >"$TMP/bin/systemd-analyze" <<'SHIM'
#!/usr/bin/env bash
exit 0
SHIM
chmod +x "$TMP/bin/systemd-analyze"

cat >"$TMP/bin/loginctl" <<'SHIM'
#!/usr/bin/env bash
printf 'yes\n'
SHIM
chmod +x "$TMP/bin/loginctl"

export PATH="$TMP/bin:$PATH"
RECEIPT="$TMP/receipt"
BACKUPS="$TMP/backups"
INSTALL_ROOT="$TMP/install"
CONFIG="$TMP/config/backup.json"
IDENTITY="$TMP/config/backup.agekey"

python3 "$ROOT/tools/k3s/phase1-control-plane.py" \
  --mode apply \
  --repo-root "$TMP/repo" \
  --server debian3 \
  --kubeconfig "$TMP/kubeconfig" \
  --backup-dir "$BACKUPS" \
  --age-identity "$IDENTITY" \
  --receipt-dir "$RECEIPT" \
  --install-root "$INSTALL_ROOT" \
  --timer-config "$CONFIG" \
  --no-enable-linger

python3 - "$RECEIPT" "$CONFIG" "$BACKUPS" "$FAKE_STATE/ssh.log" <<'PY'
from __future__ import annotations
import json
import sys
from pathlib import Path

receipt = Path(sys.argv[1])
config = json.loads(Path(sys.argv[2]).read_text())
backups = Path(sys.argv[3])
ssh_log = Path(sys.argv[4]).read_text().splitlines()
result = json.loads((receipt / "phase1-result.json").read_text())
qualification = json.loads((receipt / "qualification.json").read_text())

assert result["status"] == "success", result
assert result["git_publication_allowed"] is True
assert result["control_plane_changed"] is True
assert qualification["status"] == "passed", qualification
assert qualification["cluster_configuration_mutated"] is False
assert config["server"] == "debian3"

archives = sorted(backups.glob("*.tar.age"))
assert len(archives) == 2, archives
assert any("-prechange-" in item.name for item in archives)
assert any("-postchange-" in item.name for item in archives)
for archive in archives:
    metadata = archive.with_suffix("").with_suffix(".json")
    assert metadata.is_file(), metadata
    document = json.loads(metadata.read_text())
    assert document["status"] == "verified"

pre = json.loads((receipt / "backup-prechange-verify.json").read_text())
post = json.loads((receipt / "backup-postchange-verify.json").read_text())
assert pre["canonical_config"]["present"] is False
assert post["canonical_config"]["present"] is True
for document in (pre, post):
    agent = document["isolated_restore"]["agent_token"]
    assert agent["classification"] == "symlink-to-server-token"
    assert agent["is_symlink"] is True
    assert agent["link_target"] == "token"
    assert document["isolated_restore"]["live_root_modified"] is False

backup_pre = next(i for i, line in enumerate(ssh_log) if " backup " in f" {line} " and "--purpose prechange" in line)
converge = next(i for i, line in enumerate(ssh_log) if " converge " in f" {line} ")
backup_post = next(i for i, line in enumerate(ssh_log) if " backup " in f" {line} " and "--purpose postchange" in line)
finalize = next(i for i, line in enumerate(ssh_log) if " finalize " in f" {line} ")
assert backup_pre < converge < backup_post < finalize, (backup_pre, converge, backup_post, finalize)
assert not (receipt / ".timer-rollback").exists()
PY

[[ -f "$FAKE_STATE/timer-enabled" ]]
[[ -f "$FAKE_STATE/timer-active" ]]
grep -F -- "/usr/local/bin/k3s" "$RECEIPT/server-inspect.json" >/dev/null
grep -F -- "running-executable-evidence" "$RECEIPT/server-inspect.json" >/dev/null

echo "phase1-control-plane.test.sh: PASS"
