import { join } from "node:path";
import { shq } from "./remote-ssh.mjs";
import {
  launcherShellPath,
  seatGuardRootShellPath,
  seatProfileShellPath,
  seatStateDirShellPath,
  seatTmuxSocketShellPath,
  shellPathAssign,
} from "./home-path.mjs";
import { SEAT_SLICE, SEAT_TMUX_MEDIATOR_BIN, SEAT_CONTROL_BASE, SEAT_CONTROL_NETNS_INODE, SEAT_CONTROL_NETNS_INODE_MODE } from "./seat-constants.mjs";
import { dryRunSeatLaunchPlan, planSeatLaunch, SEAT_SANDBOX_SETTINGS } from "./seat-launch-planner.mjs";
import { validateSeatProxyEnvAllowlist } from "./seat-proxy.mjs";
import { implementerUsernameForSeat, seatGuardBinPath } from "./seat-runtime.mjs";
import { deriveNetnsLayout } from "./seat-netns.mjs";

const SCOPE_PROP_KEYS = [
  "ControlGroup",
  "MemoryMax",
  "MemorySwapMax",
  "CPUQuotaPerSecUSec",
  "CPUWeight",
  "TasksMax",
  "OOMPolicy",
  "Slice",
];

export const SEAT_SCOPE_NUMERIC = {
  MemoryMax: "12884901888",
  MemorySwapMax: "1073741824",
  CPUQuotaPerSecUSec: "4s",
  CPUWeight: "1",
  TasksMax: "256",
  OOMPolicy: "kill",
  Slice: SEAT_SLICE,
};

export function runPerSeatDoctor({
  boundModel,
  seatId,
  port,
  nsIp,
  proxyEnvContent,
  launchPlan,
  projectSettings = {},
  liveProbe = null,
}) {
  const errors = [];
  const layout = deriveNetnsLayout(seatId);
  const proxyNsIp = nsIp ?? layout.nsIp;
  const envCheck = validateSeatProxyEnvAllowlist(proxyEnvContent, {
    seatId,
    boundModel,
    port,
    nsIp: proxyNsIp,
  });
  if (!envCheck.ok) errors.push(`seat-proxy-env:${envCheck.reason}`);
  const dry = dryRunSeatLaunchPlan(launchPlan, projectSettings, boundModel);
  if (!dry.ok) errors.push(...dry.errors.map((e) => `launch-plan:${e}`));
  if (launchPlan.env?.CCP_CONFIG_DIR) errors.push("seat-env-ccp-config-dir");
  if (launchPlan.env?.ANTHROPIC_API_KEY) errors.push("seat-env-anthropic-api-key");
  if (launchPlan.env?.OPENAI_API_KEY) errors.push("seat-env-openai-api-key");
  if (!liveProbe) {
    errors.push("doctor-live-missing");
    return { ok: false, errors };
  }
  const checks = [
    ["traverse-proxy-base", liveProbe.proxyCanTraverseBase === true],
    ["traverse-proxy-instance", liveProbe.proxyCanTraverseInstance === true],
    ["traverse-ssh-base", liveProbe.sshCannotTraverseBase === true],
    ["traverse-ssh-instance", liveProbe.sshCannotReadInstance === true],
    ["traverse-implementer-base", liveProbe.implementerCannotTraverseBase === true],
    ["traverse-implementer-instance", liveProbe.implementerCannotReadInstance === true],
    ["proxy-unit-active", liveProbe.proxyUnitActive === true],
    ["proxy-host-loopback", liveProbe.proxyNotOnHostLoopback === true],
    ["proxy-netns-loopback", liveProbe.proxyNotOnNetnsLoopback === true],
    ["proxy-netns-listener", liveProbe.proxyInNetns === true],
    ["scope-active", liveProbe.scopeActive === true],
    ["scope-cgroup", liveProbe.scopeControlGroupOk === true],
    ["scope-slice", liveProbe.scopeSliceOk === true],
    ["scope-props", liveProbe.scopePropsOk === true],
    ["scope-netns-inode", liveProbe.scopeNetnsInodeOk === true],
    ["launcher-env", liveProbe.launcherEnvOk === true],
    ["implementer-uid", liveProbe.implementerUidOk === true],
    ["implementer-env-clean", liveProbe.implementerEnvClean === true],
    ["implementer-credential", liveProbe.implementerCredentialOk === true],
    ["effective-seat-limits", liveProbe.effectiveSeatLimitsOk === true],
    ["proxy-netns-inode", liveProbe.proxyNetnsInodeOk === true],
    ["claude-netns-inode", liveProbe.claudeNetnsInodeOk === true],
    ["tmux-socket", liveProbe.tmuxSocketPresent === true],
    ["seat-guard-current", liveProbe.guardCurrentExists === true],
    ["seat-guard-binaries", liveProbe.guardBinariesOk === true],
    ["one-model-bound", liveProbe.oneModelBound === true],
    ["cmdline-security", liveProbe.cmdlineSecurityOk === true],
    ["proxy-env-clean", liveProbe.proxyEnvClean === true],
    ["claude-env-clean", liveProbe.claudeEnvClean === true],
    ["settings-exact", liveProbe.settingsExact === true],
    ["mcp-empty", liveProbe.mcpEmpty === true],
    ["forwarded-exact", liveProbe.forwardedExact === true],
    ["seat-env-json", liveProbe.seatEnvJsonOk === true],
    ["seat-json-valid", liveProbe.seatJsonValid === true],
    ["checkout-present", liveProbe.checkoutPresent === true],
    ["checkout-git", liveProbe.checkoutGitOk === true],
    ["checkout-git-author", liveProbe.checkoutGitAuthorOk === true],
    ["profile-clean", liveProbe.profileNoCredentials === true],
    ["artifact-modes", liveProbe.artifactModesOk === true],
    ["sandbox-prereqs", liveProbe.sandboxPrereqsOk === true],
  ];
  for (const [name, ok] of checks) {
    if (!ok) errors.push(name);
  }
  return { ok: errors.length === 0, errors };
}

export function buildTraverseProbeScript(seatId) {
  const implementerUser = implementerUsernameForSeat(seatId);
  return [
    "set -euo pipefail",
    `seat_id=${JSON.stringify(seatId)}`,
    `implementer_user=${JSON.stringify(implementerUser)}`,
    "proxy_base_ok=0",
    "proxy_inst_ok=0",
    "ssh_base_ok=0",
    "ssh_inst_ok=0",
    "impl_base_ok=0",
    "impl_inst_ok=0",
    "ssh_user=$(id -un)",
    "if sudo -n -u overdeck-seat-proxy test -x /var/lib/overdeck/seat-proxy; then proxy_base_ok=1; fi",
    "if sudo -n -u overdeck-seat-proxy test -d \"/var/lib/overdeck/seat-proxy/$seat_id\"; then proxy_inst_ok=1; fi",
    "if sudo -n -u \"$ssh_user\" test -x /var/lib/overdeck/seat-proxy; then ssh_base_ok=1; fi",
    "if sudo -n -u \"$ssh_user\" test -r \"/var/lib/overdeck/seat-proxy/$seat_id/seat-proxy.env\"; then ssh_inst_ok=1; fi",
    "if id \"$implementer_user\" >/dev/null 2>&1; then",
    "  if sudo -n -u \"$implementer_user\" test -x /var/lib/overdeck/seat-proxy; then impl_base_ok=1; fi",
    "  if sudo -n -u \"$implementer_user\" test -r \"/var/lib/overdeck/seat-proxy/$seat_id/seat-proxy.env\"; then impl_inst_ok=1; fi",
    "fi",
    "printf 'proxy_base=%s proxy_inst=%s ssh_base=%s ssh_inst=%s impl_base=%s impl_inst=%s\\n' \"$proxy_base_ok\" \"$proxy_inst_ok\" \"$ssh_base_ok\" \"$ssh_inst_ok\" \"$impl_base_ok\" \"$impl_inst_ok\"",
  ].join("\n");
}

export function buildLiveDoctorProbeScript({
  seatId,
  port,
  boundModel,
  accountSlug,
  unitName,
  identity = null,
}) {
  const layout = deriveNetnsLayout(seatId);
  const socket = seatTmuxSocketShellPath(seatId);
  const stateDir = seatStateDirShellPath(seatId);
  const guardPath = seatGuardRootShellPath();
  const profile = seatProfileShellPath(accountSlug, seatId);
  const launcher = launcherShellPath();
  const expectedProps = Object.entries(SEAT_SCOPE_NUMERIC)
    .map(([k, v]) => `${k}=${v}`)
    .join("|");
  const origin = identity?.origin ?? "";
  const branch = identity?.branchFull ?? "";
  const upstream = identity?.upstreamFull ?? "";
  const implementerUser = implementerUsernameForSeat(seatId);
  return [
    "set -euo pipefail",
    `seat_id=${shq(seatId)}`,
    `port=${port}`,
    `bound_model=${shq(boundModel)}`,
    `unit=${shq(unitName)}`,
    shellPathAssign("socket", socket),
    shellPathAssign("state_dir", stateDir),
    `netns_name=${shq(layout.netnsName)}`,
    `netns_path=${shq(layout.netnsPath)}`,
    `ns_ip=${shq(layout.nsIp)}`,
    shellPathAssign("guard_path", guardPath),
    shellPathAssign("profile", profile),
    shellPathAssign("launcher", launcher),
    `expected_props=${shq(expectedProps)}`,
    `want_origin=${shq(origin)}`,
    `want_branch=${shq(branch)}`,
    `want_upstream=${shq(upstream)}`,
    "python3 - <<'PY'",
    "import json, os, re, stat, subprocess, sys",
    "seat_id = os.environ['OD_SEAT_ID']",
    "port = os.environ['OD_PORT']",
    "bound_model = os.environ['OD_BOUND_MODEL']",
    "unit = os.environ['OD_UNIT']",
    "socket = os.environ['OD_SOCKET']",
    "state_dir = os.environ['OD_STATE_DIR']",
    "netns_path = os.environ['OD_NETNS_PATH']",
    "netns_name = os.environ['OD_NETNS_NAME']",
    "ns_ip = os.environ['OD_NS_IP']",
    "want_sandbox = json.loads(os.environ['OD_SANDBOX_JSON'])",
    "guard_path = os.environ['OD_GUARD_PATH']",
    "profile = os.environ['OD_PROFILE']",
    "launcher = os.environ['OD_LAUNCHER']",
    "expected_props = os.environ['OD_EXPECTED_PROPS']",
    "want_origin = os.environ.get('OD_WANT_ORIGIN', '')",
    "want_branch = os.environ.get('OD_WANT_BRANCH', '')",
    "want_upstream = os.environ.get('OD_WANT_UPSTREAM', '')",
    "",
    "def sh(cmd):",
    "    return subprocess.run(cmd, shell=True, text=True, capture_output=True)",
    "",
    "def read_proc(pid, name):",
    "    try:",
    "        with open(f'/proc/{pid}/{name}', 'rb') as fh:",
    "            return fh.read()",
    "    except OSError:",
    "        return b''",
    "",
    "def proc_environ(pid):",
    "    raw = read_proc(pid, 'environ').decode('utf-8', 'replace').split('\\0')",
    "    env = {}",
    "    for item in raw:",
    "        if '=' in item:",
    "            k, v = item.split('=', 1)",
    "            env[k] = v",
    "    return env",
    "",
    "def ns_inode(path):",
    "    st = os.stat(path)",
    "    return f'{st.st_dev}:{st.st_ino}'",
    "",
    "def check_recorded_netns_inode():",
    "    path = os.environ.get('OD_NETNS_INODE_FILE', '')",
    "    if not path:",
    "        return 'netns-inode-missing'",
    "    try:",
    "        if not os.path.isfile(path):",
    "            return 'netns-inode-missing'",
    "        st = os.lstat(path)",
    "        if stat.S_ISLNK(st.st_mode) or not stat.S_ISREG(st.st_mode):",
    "            return 'netns-inode-unreadable'",
    "        if st.st_uid != 0 or st.st_gid != 0:",
    "            return 'netns-inode-owner'",
    "        if stat.S_IMODE(st.st_mode) != 0o644:",
    "            return 'netns-inode-mode'",
    "        with open(path, encoding='utf-8') as fh:",
    "            recorded = fh.read().strip()",
    "        if not re.fullmatch(r'\\d+:\\d+', recorded):",
    "            return 'netns-inode-nonnumeric'",
    "        live = ns_inode(netns_path)",
    "        return 'ok' if recorded == live else 'netns-inode-mismatch'",
    "    except OSError:",
    "        return 'netns-inode-unreadable'",
    "",
    "def find_pids(pattern):",
    "    out = sh('ps -eo pid=,args=')",
    "    hits = []",
    "    for line in out.stdout.splitlines():",
    "        line = line.strip()",
    "        if not line:",
    "            continue",
    "        pid_s, args = line.split(None, 1)",
    "        if re.search(pattern, args):",
    "            hits.append(int(pid_s))",
    "    return hits",
    "",
    "out = {}",
    "out['proxy_unit'] = 1 if sh(f'sudo -n systemctl is-active --quiet overdeck-seat-proxy@{seat_id}.service').returncode == 0 else 0",
    "host_listen = sh(f\"ss -H -ltn | awk '{{print $4}}' | grep -q ':{port}$'\")",
    "out['host_listen'] = 1 if host_listen.returncode == 0 else 0",
    "ns_listen = sh(f\"sudo -n ip netns exec {netns_name} ss -H -ltn | awk '{{print $4}}' | grep -q '{ns_ip}:{port}$'\")",
    "out['ns_listen'] = 1 if ns_listen.returncode == 0 else 0",
    "ns_loopback = sh(f\"sudo -n ip netns exec {netns_name} ss -H -ltn | awk '{{print $4}}' | grep -q '127.0.0.1:{port}$'\")",
    "out['ns_loopback_listen'] = 1 if ns_loopback.returncode == 0 else 0",
    "out['sandbox_prereqs'] = 1 if sh('command -v bwrap >/dev/null 2>&1 && command -v socat >/dev/null 2>&1').returncode == 0 else 0",
    "out['scope'] = 1 if sh(f'systemctl --user is-active --quiet {unit}').returncode == 0 else 0",
    "out['socket'] = 1 if os.path.exists(socket) else 0",
    "out['guard'] = 1 if os.path.isdir(os.path.join(guard_path, 'bin')) and os.access(os.path.join(guard_path, 'bin', 'claude'), os.X_OK) else 0",
    "guard_bins_ok = 1",
    "for name in ('claude', 'claudex', 'claude-code-proxy'):",
    "    if not os.access(os.path.join(guard_path, 'bin', name), os.X_OK):",
    "        guard_bins_ok = 0",
    "out['guard_bins'] = guard_bins_ok",
    "scope_props = {}",
    "show = sh(f'systemctl --user show {unit} --property={\",\".join([\"ControlGroup\",\"MemoryMax\",\"MemorySwapMax\",\"CPUQuotaPerSecUSec\",\"CPUWeight\",\"TasksMax\",\"OOMPolicy\",\"Slice\"])}')",
    "for line in show.stdout.splitlines():",
    "    if '=' in line:",
    "        k, v = line.split('=', 1)",
    "        scope_props[k] = v",
    "out['scope_cgroup'] = 1 if scope_props.get('ControlGroup', '').endswith(f'agent-seat-{seat_id}.scope') else 0",
    "out['scope_slice'] = 1 if scope_props.get('Slice') == 'agent-seat.slice' else 0",
    "want_map = dict(item.split('=', 1) for item in expected_props.split('|') if item)",
    "props_ok = 1",
    "for key, want in want_map.items():",
    "    if scope_props.get(key, '') != want:",
    "        props_ok = 0",
    "out['scope_props'] = props_ok",
    "proxy_pids = find_pids(r'claude-code-proxy serve')",
    "claude_pids = find_pids(r'/.local/bin/claude .*--model')",
    "out['proxy_ns'] = 0",
    "out['claude_ns'] = 0",
    "out['scope_ns'] = 0",
    "scope_ns_marker = check_recorded_netns_inode()",
    "out['scope_ns'] = 1 if scope_ns_marker == 'ok' else 0",
    "out['launcher_env'] = 0",
    "out['implementer_uid'] = 0",
    "out['implementer_env_clean'] = 0",
    "out['effective_limits'] = 0",
    "if proxy_pids:",
    "    out['proxy_ns'] = 1 if out['ns_listen'] else 0",
    "pane_pid = None",
    `mediator = ${JSON.stringify(SEAT_TMUX_MEDIATOR_BIN)}`,
    "tmux = subprocess.run(",
    "    ['sudo', '-n', mediator, '--seat-id', seat_id, '--socket', socket, 'list-panes', '-F', '#{pane_pid}', '-t', 'main'],",
    "    text=True,",
    "    capture_output=True,",
    ")",
    "pane_line = next((ln.strip() for ln in tmux.stdout.splitlines() if ln.strip()), '')",
    "if pane_line.isdigit():",
    "    pane_pid = int(pane_line)",
    "    cg = read_proc(pane_pid, 'cgroup').decode('utf-8', 'replace')",
    "    out['scope_cgroup'] = 1 if f'agent-seat-{seat_id}.scope' in cg else out['scope_cgroup']",
    "    env = proc_environ(pane_pid)",
    "    out['launcher_env'] = 1 if env.get('OVERDECK_SEAT_ID') == seat_id and env.get('OVERDECK_SEAT_HOST') and env.get('OVERDECK_SEAT_MODEL') else 0",
    "    try:",
    "        import pwd",
    "        impl_user = os.environ.get('OD_IMPLEMENTER_USER', '')",
    "        impl_uid = pwd.getpwnam(impl_user).pw_uid",
    "    except Exception:",
    "        impl_uid = -1",
    "    st = read_proc(pane_pid, 'status').decode('utf-8', 'replace').splitlines()",
    "    uid_line = next((l for l in st if l.startswith('Uid:')), '')",
    "    uid = int(uid_line.split()[1]) if uid_line else -1",
    "    out['implementer_uid'] = 1 if uid == impl_uid else 0",
    "    out['implementer_env_clean'] = 1 if not env.get('DBUS_SESSION_BUS_ADDRESS') and not env.get('XDG_RUNTIME_DIR') else 0",
    "    cg_rel = read_proc(pane_pid, 'cgroup').decode('utf-8', 'replace').strip().split('::', 1)[-1]",
    "    if 'tmux-spawn' in cg_rel and f'agent-seat-{seat_id}.scope' not in cg_rel:",
    "        out['effective_limits'] = 0",
    "    else:",
    "        cg_path = '/sys/fs/cgroup' + cg_rel",
    "        mem = open(os.path.join(cg_path, 'memory.max')).read().strip() if os.path.exists(os.path.join(cg_path, 'memory.max')) else ''",
    "        swap = open(os.path.join(cg_path, 'memory.swap.max')).read().strip() if os.path.exists(os.path.join(cg_path, 'memory.swap.max')) else ''",
    "        cpu = open(os.path.join(cg_path, 'cpu.max')).read().strip() if os.path.exists(os.path.join(cg_path, 'cpu.max')) else ''",
    "        out['effective_limits'] = 1 if mem == '12884901888' and swap == '1073741824' and cpu == '400000 100000' else 0",
    "else:",
    "    out['implementer_uid'] = 0",
    "    out['implementer_env_clean'] = 0",
    "    out['effective_limits'] = 0",
    "out['implementer_credential'] = 1 if sh(f'sudo -n -u {os.environ.get(\"OD_IMPLEMENTER_USER\", \"nobody\")} test -r $HOME/.claude/.credentials.json').returncode != 0 else 0",
    "out['one_model'] = 0",
    "out['cmdline_security'] = 0",
    "if claude_pids:",
    "    args = read_proc(claude_pids[0], 'cmdline').decode('utf-8', 'replace').split('\\0')",
    "    args = [a for a in args if a]",
    "    model_count = sum(1 for a in args if a == '--model' or a.startswith('--model='))",
    "    out['one_model'] = 1 if model_count == 1 and bound_model in args else 0",
    "    required = ['--setting-sources', '', '--disallowedTools', 'Agent', 'Workflow', '--agents', '{}', '--strict-mcp-config', '--mcp-config']",
    "    ok = 1",
    "    for token in required:",
    "        if token not in args:",
    "            ok = 0",
    "    out['cmdline_security'] = ok",
    "if claude_pids:",
    "    out['claude_ns'] = 1 if out['scope_ns'] and out['one_model'] else 0",
    "out['proxy_env_clean'] = 1",
    "if proxy_pids:",
    "    env = read_proc(proxy_pids[0], 'environ').decode('utf-8', 'replace').split('\\0')",
    "    joined = '\\n'.join(env)",
    "    if any(x in joined for x in ('CCP_CODEX_MODEL=', 'OPENAI_API_KEY=', 'CODEX_', 'access_token')):",
    "        out['proxy_env_clean'] = 0",
    "out['claude_env_clean'] = 1",
    "if claude_pids:",
    "    env = read_proc(claude_pids[0], 'environ').decode('utf-8', 'replace').split('\\0')",
    "    joined = '\\n'.join(env)",
    "    if f'{ns_ip}:{port}' not in joined:",
    "        out['claude_env_clean'] = 0",
    "    if '127.0.0.1:' in joined or 'localhost:' in joined:",
    "        out['claude_env_clean'] = 0",
    "    if any(x in joined for x in ('ANTHROPIC_API_KEY=', 'OPENAI_API_KEY=', 'CCP_', 'CODEX_', 'access_token')):",
    "        out['claude_env_clean'] = 0",
    "out['settings_exact'] = 0",
    "out['mcp_empty'] = 0",
    "out['forwarded_exact'] = 0",
    "out['seat_env_json'] = 0",
    "out['artifact_modes'] = 0",
    "try:",
    "    trusted_dir = os.path.join(state_dir, 'trusted-config')",
    "    settings = json.load(open(os.path.join(trusted_dir, 'seat-settings.json')))",
    "    out['settings_exact'] = 1 if settings.get('enableWorkflows') is False and settings.get('availableModels') == [bound_model] and settings.get('sandbox') == want_sandbox else 0",
    "    mcp = json.load(open(os.path.join(trusted_dir, 'empty-mcp.json')))",
    "    out['mcp_empty'] = 1 if mcp == {'mcpServers': {}} else 0",
    "    forwarded = json.load(open(os.path.join(trusted_dir, 'claude-forwarded.json')))",
    "    out['forwarded_exact'] = 1 if isinstance(forwarded, list) else 0",
    "    seat_env = json.load(open(os.path.join(trusted_dir, 'seat-env.json')))",
    "    out['seat_env_json'] = 1 if isinstance(seat_env, dict) and f'{ns_ip}:{port}' in seat_env.get('ANTHROPIC_BASE_URL', '') and '127.0.0.1' not in seat_env.get('ANTHROPIC_BASE_URL', '') else 0",
    "    modes_ok = 1",
    "    for rel in ('seat-settings.json', 'empty-mcp.json', 'claude-forwarded.json', 'seat-env.json'):",
    "        mode = oct(os.stat(os.path.join(trusted_dir, rel)).st_mode & 0o777)",
    "        if mode != '0o600':",
    "            modes_ok = 0",
    "    out['artifact_modes'] = modes_ok",
    "except Exception:",
    "    pass",
    "out['seat_json'] = 0",
    "out['checkout'] = 0",
    "out['checkout_git'] = 0",
    "out['checkout_git_author'] = 0",
    "seat_json_path = os.path.expanduser(f'~/seats/{seat_id}/seat.json')",
    "if os.path.isfile(seat_json_path):",
    "    try:",
    "        seat = json.load(open(seat_json_path))",
    "        ok = seat.get('model') == bound_model and seat.get('schemaVersion') == 1",
    "        if want_origin:",
    "            ok = ok and seat.get('origin') == want_origin and seat.get('branch') == want_branch and seat.get('upstream') == want_upstream",
    "        out['seat_json'] = 1 if ok else 0",
    "    except Exception:",
    "        out['seat_json'] = 0",
    "repo = os.path.expanduser(f'~/seats/{seat_id}/repo')",
    "out['checkout'] = 1 if os.path.isdir(os.path.join(repo, '.git')) else 0",
    "if out['checkout']:",
    "    origin = sh(f'git -C {repo} remote get-url origin').stdout.strip()",
    "    branch = sh(f'git -C {repo} symbolic-ref -q HEAD').stdout.strip()",
    "    upstream = sh(f'git -C {repo} rev-parse --abbrev-ref --symbolic-full-name @{{upstream}}').stdout.strip()",
    "    ok = True",
    "    if want_origin:",
    "        ok = origin == want_origin and branch == want_branch and upstream == want_upstream",
    "    out['checkout_git'] = 1 if ok else 0",
    "    want_git_name = f'Overdeck Seat {seat_id}'",
    "    want_git_email = f'{seat_id}@overdeck.invalid'",
    "    git_name = sh(f'git -C {repo} config --local --get user.name').stdout.strip()",
    "    git_email = sh(f'git -C {repo} config --local --get user.email').stdout.strip()",
    "    out['checkout_git_author'] = 1 if git_name == want_git_name and git_email == want_git_email else 0",
    "out['profile_clean'] = 1",
    "claude_json = os.path.join(profile, '.claude.json')",
    "if os.path.isfile(claude_json):",
    "    text = open(claude_json, encoding='utf-8').read()",
    "    if re.search(r'apikey|oauth|access_token|refresh_token|openai|anthropic', text, re.I):",
    "        out['profile_clean'] = 0",
    "parts = ' '.join(f'{k}={v}' for k, v in out.items())",
    "print('doctor ' + parts)",
    "PY",
  ].join("\n").replace(
    "python3 - <<'PY'",
    [
      "export OD_SEAT_ID=\"$seat_id\" OD_PORT=\"$port\" OD_BOUND_MODEL=\"$bound_model\" OD_UNIT=\"$unit\"",
      "export OD_SOCKET=\"$socket\" OD_STATE_DIR=\"$state_dir\" OD_NETNS_PATH=\"$netns_path\"",
      "export OD_NETNS_NAME=\"$netns_name\" OD_NS_IP=\"$ns_ip\" OD_GUARD_PATH=\"$guard_path\" OD_PROFILE=\"$profile\"",
      `export OD_LAUNCHER="$launcher" OD_EXPECTED_PROPS=${shq(expectedProps)}`,
      `export OD_SANDBOX_JSON=${shq(JSON.stringify(SEAT_SANDBOX_SETTINGS))}`,
      `export OD_IMPLEMENTER_USER=${shq(implementerUser)} OD_WANT_ORIGIN=${shq(origin)} OD_WANT_BRANCH=${shq(branch)} OD_WANT_UPSTREAM=${shq(upstream)}`,
      `export OD_NETNS_INODE_FILE=${shq(`${SEAT_CONTROL_BASE}/${seatId}/${SEAT_CONTROL_NETNS_INODE}`)}`,
      "python3 - <<'PY'",
    ].join("\n"),
  );
}

export function parseTraverseProbe(stdout) {
  const line = String(stdout || "").split("\n").find((l) => l.startsWith("proxy_base="));
  if (!line) return null;
  const parts = Object.fromEntries(line.split(" ").map((p) => p.split("=")));
  return {
    proxyCanTraverseBase: parts.proxy_base === "1",
    proxyCanTraverseInstance: parts.proxy_inst === "1",
    sshCannotTraverseBase: parts.ssh_base !== "1",
    sshCannotReadInstance: parts.ssh_inst !== "1",
    implementerCannotTraverseBase: parts.impl_base !== "1",
    implementerCannotReadInstance: parts.impl_inst !== "1",
  };
}

export function parseLiveDoctorProbe(stdout) {
  const line = String(stdout || "").split("\n").find((l) => l.startsWith("doctor "));
  if (!line) return null;
  const parts = Object.fromEntries(line.slice("doctor ".length).split(" ").map((p) => p.split("=")));
  const traverse = parseTraverseProbe(stdout) || {};
  return {
    ...traverse,
    proxyUnitActive: parts.proxy_unit === "1",
    proxyNotOnHostLoopback: parts.host_listen !== "1",
    proxyNotOnNetnsLoopback: parts.ns_loopback_listen !== "1",
    proxyInNetns: parts.ns_listen === "1",
    scopeActive: parts.scope === "1",
    scopeControlGroupOk: parts.scope_cgroup === "1",
    scopeSliceOk: parts.scope_slice === "1",
    scopePropsOk: parts.scope_props === "1",
    scopeNetnsInodeOk: parts.scope_ns === "1",
    launcherEnvOk: parts.launcher_env === "1",
    implementerUidOk: parts.implementer_uid === "1",
    implementerEnvClean: parts.implementer_env_clean === "1",
    implementerCredentialOk: parts.implementer_credential === "1",
    effectiveSeatLimitsOk: parts.effective_limits === "1",
    proxyNetnsInodeOk: parts.proxy_ns === "1",
    claudeNetnsInodeOk: parts.claude_ns === "1",
    tmuxSocketPresent: parts.socket === "1",
    guardCurrentExists: parts.guard === "1",
    guardBinariesOk: parts.guard_bins === "1",
    oneModelBound: parts.one_model === "1",
    cmdlineSecurityOk: parts.cmdline_security === "1",
    proxyEnvClean: parts.proxy_env_clean === "1",
    claudeEnvClean: parts.claude_env_clean === "1",
    settingsExact: parts.settings_exact === "1",
    mcpEmpty: parts.mcp_empty === "1",
    forwardedExact: parts.forwarded_exact === "1",
    seatEnvJsonOk: parts.seat_env_json === "1",
    seatJsonValid: parts.seat_json === "1",
    checkoutPresent: parts.checkout === "1",
    checkoutGitOk: parts.checkout_git === "1",
    checkoutGitAuthorOk: parts.checkout_git_author === "1",
    profileNoCredentials: parts.profile_clean === "1",
    artifactModesOk: parts.artifact_modes === "1",
    sandboxPrereqsOk: parts.sandbox_prereqs === "1",
  };
}

export function buildDoctorLaunchPlan(home, boundModel, claudeBinary, claudeArgv) {
  const settingsDir = join(home, ".local/state/overdeck/seats", "doctor-dry-run");
  const mcpConfigPath = join(settingsDir, "empty-mcp.json");
  return planSeatLaunch({
    boundModel,
    claudeBinary,
    claudeArgv,
    settingsDir,
    mcpConfigPath,
    seatEnv: {
      PATH: "/usr/bin:/bin",
      ANTHROPIC_BASE_URL: "http://169.254.1.2:0/v1",
      OVERDECK_SEAT_HOST: "doctor",
      OVERDECK_SEAT_ID: "doctor",
    },
    home,
  });
}

export function guardBinFromHome(home) {
  return seatGuardBinPath(home);
}
