#!/usr/bin/env bash
# =============================================================================
# veil.sh -- hardened approval lifecycle
# =============================================================================

set -euo pipefail

TOOL_NAME="veil"
TOOL_VERSION="5.0.0"
TOOL_LOG="/opt/binary/logs/veil/veil.log"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/common.sh"

PENDING_DIR="${DATA_DIR}/pending-approvals"
RAW_DIR="${PENDING_DIR}/raw"
APPROVED_DIR="${PENDING_DIR}/approved"
DENIED_DIR="${PENDING_DIR}/denied"
EXECUTED_DIR="${PENDING_DIR}/executed"
FAILED_DIR="${PENDING_DIR}/failed"
AUDIT_LOG="${PENDING_DIR}/veil-events.log"

MANUAL_REVIEW_FLAG="${CONF_DIR}/MANUAL_REVIEW.flag"
STRIKE_THRESHOLD="${STRIKE_THRESHOLD:-3}"

mkdir -p "$PENDING_DIR" "$RAW_DIR" "$APPROVED_DIR" "$DENIED_DIR" "$EXECUTED_DIR" "$FAILED_DIR"

append_event() {
    local event="$1" req_id="${2:-none}" detail="${3:-}"
    umask 077
    printf '{"ts":"%s","event":"%s","request_id":"%s","node":"%s","actor":"%s","session_id":"%s","detail":"%s"}\n' \
        "$(ts)" \
        "$(json_escape "$event")" \
        "$(json_escape "$req_id")" \
        "$(json_escape "${NODE_ID:-unknown}")" \
        "$(json_escape "$(whoami)")" \
        "$(json_escape "${HELLO_SESSION_ID:-none}")" \
        "$(json_escape "$detail")" >> "$AUDIT_LOG"
    chmod 600 "$AUDIT_LOG" 2>/dev/null || true
}

get_strike_count() {
    [ -f "$STRIKE_FILE" ] || { echo 0; return; }
    wc -l < "$STRIKE_FILE"
}

add_strike() {
    local reason="${1:-unspecified}"
    mkdir -p "$(dirname "$STRIKE_FILE")"
    echo "[$(ts)] action=strike reason=$(json_escape "$reason") caller=$(whoami)" >> "$STRIKE_FILE"
    local count
    count="$(get_strike_count)"
    tool_audit "strike" "count=${count} reason=${reason}"
    append_event "strike" "none" "count=${count} reason=${reason}"
    if [[ "$count" -ge "$STRIKE_THRESHOLD" ]]; then
        {
            echo "NODE=${NODE_ID}"
            echo "CALLER=$(whoami)"
            echo "STRIKES=${count}"
            echo "LAST_REASON=${reason}"
            echo "TIMESTAMP=$(ts)"
        } > "$MANUAL_REVIEW_FLAG"
        chmod 600 "$MANUAL_REVIEW_FLAG" 2>/dev/null || true
    fi
}

request_json_file() { printf '%s/%s.json\n' "$PENDING_DIR" "$1"; }
request_args_file() { printf '%s/%s.args\n' "$RAW_DIR" "$1"; }

queue_request() {
    local tool="$1" verb="$2"
    shift 2 || true

    # Rate limit: prevent queue flooding (max 50 pending requests)
    local pending_count
    pending_count=$(find "$PENDING_DIR" -maxdepth 1 -name "*.json" 2>/dev/null | wc -l)
    if (( pending_count >= 50 )); then
        log_error "Queue full (${pending_count} pending). Rejecting request."
        return 1
    fi

    local policy
    policy="$(bash "${SCRIPT_DIR}/$(_resolve_tool a1f1)" __classify "$tool" "$verb" 2>/dev/null || echo write)"

    local request_id="v-$(date -u +%Y%m%d%H%M%S)-$$-$(head -c 4 /dev/urandom | xxd -p 2>/dev/null || echo $$)"
    local args_file json_file args_redacted args_sha256
    args_file="$(request_args_file "$request_id")"
    json_file="$(request_json_file "$request_id")"
    args_redacted="$(_redacted_detail "$policy" "$*")"
    args_sha256="$(printf '%s\0' "$@" | sha256sum | awk '{print $1}')"

    umask 077
    printf '%s\0' "$@" > "$args_file"

    cat > "$json_file" <<EOF
{
  "request_id": "$(json_escape "$request_id")",
  "timestamp": "$(json_escape "$(ts)")",
  "node": "$(json_escape "${NODE_ID:-unknown}")",
  "caller": "$(json_escape "$(whoami)")",
  "session_id": "$(json_escape "${HELLO_SESSION_ID:-none}")",
  "actor_scope": "$(json_escape "${HELLO_SCOPE:-unknown}")",
  "reason": "$(json_escape "${HELLO_PURPOSE:-not provided}")",
  "ssh_client": "$(json_escape "${SSH_CLIENT:-local}")",
  "tool": "$(json_escape "$tool")",
  "verb": "$(json_escape "$verb")",
  "policy": "$(json_escape "$policy")",
  "args_redacted": "$(json_escape "$args_redacted")",
  "args_sha256": "$(json_escape "$args_sha256")",
  "args_ref": "$(json_escape "$(basename "$args_file")")",
  "status": "pending"
}
EOF
    chmod 600 "$args_file" "$json_file"

    tool_audit "queued" "request_id=${request_id} tool=${tool} verb=${verb} policy=${policy} session_id=${HELLO_SESSION_ID:-none} digest=${args_sha256}"
    append_event "queued" "$request_id" "tool=${tool} verb=${verb} policy=${policy}"

    [[ "$policy" = "break-glass" ]] && add_strike "break-glass request queued: ${tool} ${verb}"
    printf '{"status":"queued","request_id":"%s","policy":"%s"}\n' "$request_id" "$policy"
}

_move_request() {
    local req_id="$1" target_dir="$2" status="$3"
    validate_name "$req_id" "request-id" || return 1
    local json_file args_file
    json_file="$(request_json_file "$req_id")"
    args_file="$(request_args_file "$req_id")"
    [ -f "$json_file" ] || { log_error "Request not found: ${req_id}"; return 1; }

    local actor actor_ts
    actor="$(whoami)"
    actor_ts="$(ts)"
    python3 - "$json_file" "$status" "$actor" "$actor_ts" <<'PY'
import json, pathlib, sys
p = pathlib.Path(sys.argv[1])
data = json.loads(p.read_text())
data["status"] = sys.argv[2]
actor = sys.argv[3]
ts = sys.argv[4]
key_prefix = sys.argv[2]
data[f"{key_prefix}_by"] = actor
data[f"{key_prefix}_at"] = ts
p.write_text(json.dumps(data, indent=2))
PY
    mkdir -p "$target_dir"
    mv "$json_file" "$target_dir/"
    [ -f "$args_file" ] && mv "$args_file" "$target_dir/" || true
    tool_audit "$status" "request_id=${req_id} actor=${actor}"
    append_event "$status" "$req_id" "actor=${actor}"
    log_ok "${status^}: ${req_id} (by ${actor})"
}

veil_approve() { _move_request "$1" "$APPROVED_DIR" approved; }
veil_deny()    { _move_request "$1" "$DENIED_DIR" denied; }

execute_request() {
    local req_id="$1"
    validate_name "$req_id" "request-id" || return 1
    local json_file args_file
    json_file="${APPROVED_DIR}/${req_id}.json"
    args_file="${APPROVED_DIR}/${req_id}.args"
    [[ -f "$json_file" ]] || die "Approved request not found: ${req_id}"

    local session_id tool verb
    session_id="$(python3 - "$json_file" <<'PY'
import json, pathlib, sys
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
print(data.get("session_id",""))
PY
)"
    tool="$(python3 - "$json_file" <<'PY'
import json, pathlib, sys
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
print(data["tool"])
PY
)"
    verb="$(python3 - "$json_file" <<'PY'
import json, pathlib, sys
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
print(data["verb"])
PY
)"

    if [[ -n "$session_id" && "$session_id" != "none" ]]; then
        if ! bash "${SCRIPT_DIR}/$(_resolve_tool b2e0)" validate "$session_id" >/dev/null 2>&1; then
            append_event "execute_blocked" "$req_id" "invalid_or_expired_session"
            die "Session invalid or expired for request ${req_id}"
        fi
    fi

    local tmp_out exit_code
    tmp_out="$(mktemp)"
    set +e
    python3 - "$args_file" <<'PY' > "${tmp_out}.argv"
import pathlib, sys
p = pathlib.Path(sys.argv[1])
data = p.read_bytes().split(b"\x00")
for item in data:
    if item:
        print(item.decode("utf-8", "replace"))
PY
    mapfile -t _argv < "${tmp_out}.argv"
    bash "${SCRIPT_DIR}/${tool}.sh" "$verb" "${_argv[@]}" >"$tmp_out" 2>&1
    exit_code=$?
    set -e
    rm -f "${tmp_out}.argv"

    python3 - "$json_file" "$req_id" "$exit_code" "$tmp_out" "$(whoami)" "$(ts)" <<'PY'
import json, pathlib, sys
p = pathlib.Path(sys.argv[1])
data = json.loads(p.read_text())
data["status"] = "executed" if sys.argv[3] == "0" else "failed"
data["executed_request_id"] = sys.argv[2]
data["exit_code"] = int(sys.argv[3])
data["executed_by"] = sys.argv[5]
data["executed_at"] = sys.argv[6]
data["output_tail"] = pathlib.Path(sys.argv[4]).read_text(errors="replace")[-4000:]
p.write_text(json.dumps(data, indent=2))
PY

    if [[ "$exit_code" -eq 0 ]]; then
        mv "$json_file" "$EXECUTED_DIR/"
        append_event "executed" "$req_id" "exit_code=0"
        log_ok "Executed: ${req_id}"
    else
        mv "$json_file" "$FAILED_DIR/"
        append_event "failed" "$req_id" "exit_code=${exit_code}"
        add_strike "execution failed: ${req_id}"
        log_error "Execution failed: ${req_id} (exit=${exit_code})"
        cat "$tmp_out" >&2 || true
        rm -f "$tmp_out"
        return "$exit_code"
    fi

    [[ -f "$args_file" ]] && mv "$args_file" "$EXECUTED_DIR/" || true
    rm -f "$tmp_out"
}

veil_status() {
    echo "node=${NODE_ID}"
    echo "pending=$(find "$PENDING_DIR" -maxdepth 1 -name '*.json' | wc -l)"
    echo "approved=$(find "$APPROVED_DIR" -maxdepth 1 -name '*.json' 2>/dev/null | wc -l)"
    echo "denied=$(find "$DENIED_DIR" -maxdepth 1 -name '*.json' 2>/dev/null | wc -l)"
    echo "executed=$(find "$EXECUTED_DIR" -maxdepth 1 -name '*.json' 2>/dev/null | wc -l)"
    echo "failed=$(find "$FAILED_DIR" -maxdepth 1 -name '*.json' 2>/dev/null | wc -l)"
    echo "strikes=$(get_strike_count)"
    [ -f "$MANUAL_REVIEW_FLAG" ] && echo "manual_review=active" || echo "manual_review=clear"
}

veil_pending() {
    local f
    for f in "$PENDING_DIR"/*.json; do
        [ -f "$f" ] || continue
        cat "$f"
        echo "---"
    done
}

veil_strikes() {
    [ -f "$STRIKE_FILE" ] && cat "$STRIKE_FILE" || echo "No strikes"
}

veil_clear_review() {
    rm -f "$MANUAL_REVIEW_FLAG"
    tool_audit "clear_review" "manual_review=false"
    append_event "clear_review" "none" "manual_review=false"
    log_ok "Manual review flag cleared"
}

tool_help() {
    cat <<'EOF'
veil status
veil pending
veil approve <request-id>
veil deny <request-id>
veil execute <request-id>
veil strikes
veil clear-review

Implicit queue mode:
veil <tool> <verb> [args...]
EOF
}

main() {
    tool_init
    case "${1:-}" in
        __strike) shift; add_strike "${*:-unspecified}" ;;
        status|"") veil_status ;;
        pending) veil_pending ;;
        approve) shift; veil_approve "${1:-}" ;;
        deny) shift; veil_deny "${1:-}" ;;
        execute) shift; execute_request "${1:-}" ;;
        strikes) veil_strikes ;;
        clear-review) veil_clear_review ;;
        help|--help|-h) tool_help ;;
        *)
            local tool="$1" verb="${2:-help}"
            shift 2 || true
            queue_request "$tool" "$verb" "$@"
            ;;
    esac
}

main "$@"
