#!/bin/sh
#
# Clawdie agent service — coherent entrypoint for the colibri control plane.
#
# On USB live: wraps colibri-daemon, skills catalog, Glasspane, DeepSeek cache.
# On disk install: adds runtime inventory, watchdog, and persistent agent config.
#
# This service does NOT replace the operator's terminal session (XFCE/bootstrap).
# It ensures the control plane is alive and healthy before the operator logs in.
#
# PROVIDE: clawdie
# REQUIRE: LOGIN colibri_daemon
# KEYWORD: shutdown

. /etc/rc.subr

name="clawdie"
rcvar="clawdie_enable"

load_rc_config "$name"

: ${clawdie_enable:="YES"}
: ${clawdie_user:="clawdie"}
: ${clawdie_group:="clawdie"}
: ${clawdie_config_dir:="/usr/local/etc/clawdie"}
: ${clawdie_data_dir:="/var/db/clawdie"}
: ${clawdie_log_dir:="/var/log/clawdie"}
: ${clawdie_run_dir:="/var/run/clawdie"}

start_precmd="clawdie_prestart"
start_cmd="clawdie_start"
stop_cmd="clawdie_stop"
status_cmd="clawdie_status"
extra_commands="health inventory"
health_cmd="clawdie_health"
inventory_cmd="clawdie_inventory"

PATH="/usr/local/bin:/opt/clawdie/npm-global/bin:${PATH}"
export PATH

SOCKET="/var/run/colibri/colibri.sock"

clawdie_prestart()
{
    install -d -o "${clawdie_user}" -g "${clawdie_group}" -m 0755 "${clawdie_data_dir}"
    install -d -o "${clawdie_user}" -g "${clawdie_group}" -m 0755 "${clawdie_log_dir}"
    install -d -o "${clawdie_user}" -g "${clawdie_group}" -m 0755 "${clawdie_run_dir}"
    install -d -m 0755 "${clawdie_config_dir}"

    export PATH="/usr/local/bin:/opt/clawdie/npm-global/bin:${PATH}"
    export COLIBRI_DAEMON_SOCKET="${SOCKET}"
    export COLIBRI_HOST="$(/bin/hostname)"
    export CLAWDIE_CONFIG_DIR="${clawdie_config_dir}"
    export CLAWDIE_DATA_DIR="${clawdie_data_dir}"
    export CLAWDIE_LOG_DIR="${clawdie_log_dir}"
}

clawdie_start()
{
    touch "${clawdie_log_dir}/agent.log"
    chown "${clawdie_user}:${clawdie_group}" "${clawdie_log_dir}/agent.log" 2>/dev/null || true

    _timeout=10
    _waited=0
    while [ ! -S "${SOCKET}" ] && [ $_waited -lt $_timeout ]; do
        sleep 1
        _waited=$((_waited + 1))
    done

    if [ ! -S "${SOCKET}" ]; then
        echo "${name}: WARNING: colibri socket not ready after ${_timeout}s"
    fi

    echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) ${name} started, host=$(/bin/hostname)" \
        > "${clawdie_run_dir}/startup-marker"

    echo "${name}: colibri control plane ready (host=$COLIBRI_HOST)"
}

clawdie_stop()
{
    echo "${name}: stopping (colibri_daemon handles subprocess lifecycle)"
    rm -f "${clawdie_run_dir}/startup-marker"
}

clawdie_status()
{
    if [ -f "${clawdie_run_dir}/startup-marker" ]; then
        echo "${name}: running (marker: $(cat ${clawdie_run_dir}/startup-marker))"
    else
        echo "${name}: not running (no startup marker)"
        return 1
    fi
}

clawdie_health()
{
    /usr/local/bin/python3.11 - "$SOCKET" << 'PYHEALTH'
import json, socket, sys
sock_path = sys.argv[1]
ok = 0
fail = 0

def query(cmd):
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    s.settimeout(3)
    s.connect(sock_path)
    s.sendall(f'{{"cmd":"{cmd}"}}\n'.encode())
    data = b''
    while True:
        chunk = s.recv(4096)
        if not chunk: break
        data += chunk
        if b'\n' in data: break
    s.close()
    return json.loads(data.decode().strip())

# 1. Daemon reachable
if query("status").get("ok"):
    print("  ✓ colibri-daemon: responding")
    ok += 1
else:
    print("  ✗ colibri-daemon: no response")
    fail += 1

# 2. Skills catalog
skills = query("list-skills").get("data", [])
if len(skills) > 0:
    print(f"  ✓ skills catalog: {len(skills)} skills")
    ok += 1
else:
    print("  ✗ skills catalog: empty or unreachable")
    fail += 1

# 3. Glasspane
snap = query("glasspane-snapshot").get("data", {})
panes = snap.get("panes", [])
print(f"  ✓ glasspane: {len(panes)} panes")
ok += 1

# 4. Runtime inventory
import os
if os.path.exists("/usr/local/bin/colibri-host-status"):
    print("  ✓ runtime inventory: available")
    ok += 1
else:
    print("  - runtime inventory: not installed (USB live mode)")

print()
if fail == 0:
    print(f"clawdie: healthy ({ok} checks passed)")
    sys.exit(0)
else:
    print(f"clawdie: degraded ({ok} passed, {fail} failed)")
    sys.exit(1)
PYHEALTH
}

clawdie_inventory()
{
    /usr/local/bin/python3.11 - "$SOCKET" "$(/bin/hostname)" << 'PYINV'
import json, socket, sys, platform, subprocess

sock_path = sys.argv[1]
hostname = sys.argv[2]

def run(cmd):
    try:
        return subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.DEVNULL).strip()
    except:
        return None

def query(cmd):
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    s.settimeout(3)
    s.connect(sock_path)
    s.sendall(f'{{"cmd":"{cmd}"}}\n'.encode())
    data = b''
    while True:
        chunk = s.recv(4096)
        if not chunk: break
        data += chunk
        if b'\n' in data: break
    s.close()
    return json.loads(data.decode().strip())

inv = {
    'schema': 'clawdie.runtime-version-inventory.v1',
    'host': hostname,
    'os': run('freebsd-version') or platform.system(),
    'node': run('node --version'),
    'npm': run('npm --version'),
    'package_manager': 'pkg',
}

pi_ver = run('/opt/clawdie/npm-global/bin/pi --version 2>&1')
if pi_ver:
    for line in pi_ver.split('\n'):
        line = line.strip()
        if line:
            parts = line.split()
            inv['pi'] = parts[-1] if parts else line
            break

try:
    status = query('status').get('data', {})
    inv['notes'] = [
        f"colibri host={status.get('host', '?')}",
        f"colibri version={status.get('version', '?')}",
        f"agents={status.get('agents', 0)}",
    ]
except Exception:
    inv['notes'] = ['colibri daemon unreachable']

print(json.dumps(inv, indent=2))
PYINV
}

load_rc_config "$name"
: ${clawdie_enable:="YES"}
run_rc_command "$1"
