#!/usr/bin/env bash

# OS Checker CLI - https://oschecker.com
# Copyright (c) Insytec
#
# Provided for use with an OS Checker account; do not redistribute or reuse
# outside that context. Provided "as is", without warranty of any kind; use
# is at your own risk and the copyright holder is not liable for any damages
# arising from its use.

set -euo pipefail

# API configuration
base_url="https://oschecker.com"
if [[ -n "${OSCHECKER_BASE_URL:-}" ]]; then
    base_url="$OSCHECKER_BASE_URL"
fi
checkin_url="$base_url/api/machine/checkin"
machines_url="$base_url/api/machines"
machine_url="$base_url/api/machine"
cli_url="https://cli.oschecker.com"
if [[ -n "${OSCHECKER_CLI_URL:-}" ]]; then
    cli_url="$OSCHECKER_CLI_URL"
fi

# Local configuration
bin_dir="/usr/local/bin"
config_dir="/etc/oschecker"
units_dir="/etc/systemd/system"
id_file="$config_dir/id"
if [[ -n "${OSCHECKER_ID_FILE:-}" ]]; then
    id_file="$OSCHECKER_ID_FILE"
fi
machine_id="${OSCHECKER_MACHINE_ID:-}"
if [[ -z "$machine_id" && -f "$id_file" ]]; then
    machine_id="$(cat "$id_file")"
fi
plugins_dir="$config_dir/plugins.d/"
if [[ -n "${OSCHECKER_PLUGINS_DIR:-}" ]]; then
    plugins_dir="$OSCHECKER_PLUGINS_DIR"
fi

# Script configuration
prog="oschecker"
client="$prog/0.1"
service_unit="oschecker.service"
timer_unit="oschecker.timer"
timer_schedule="daily"

# Colors (not when piped/redirected)
color_gray=""
color_red=""
color_reset=""
if [[ -t 1 ]]; then
    color_gray=$'\033[90m'
    color_red=$'\033[31m'
    color_reset=$'\033[0m'
fi

# JSON helpers
json_escape() {
    local s="$1"
    s=${s//\\/\\\\}
    s=${s//\"/\\\"}
    s=${s//$'\n'/\\n}
    s=${s//$'\r'/\\r}
    s=${s//$'\t'/\\t}
    printf '%s' "$s"
}
build_json_map() {
    local input="$1"
    awk '
        function esc(s) {
            gsub(/\\/, "\\\\", s)
            gsub(/"/, "\\\"", s)
            gsub(/\r/, "\\r", s)
            gsub(/\t/, "\\t", s)
            return s
        }
        BEGIN { printf "{" }
        {
            line = $0
            if (line == "") next
            idx = index(line, " ")
            if (idx == 0) {
                key = line
                value = ""
            } else {
                key = substr(line, 1, idx - 1)
                value = substr(line, idx + 1)
            }
            if (n++ > 0) printf ","
            printf "\"%s\":\"%s\"", esc(key), esc(value)
        }
        END { printf "}" }
    ' <<< "$input"
}

# Guards
require_curl() {
    if ! command -v curl &>/dev/null; then
        echo "Error: Required command 'curl' not found." >&2
        exit 1
    fi
}
require_machine_id() {
    if [[ -z "$machine_id" ]]; then
        echo "Error: No machine ID found at $id_file. Run '$prog install <machine-id>' first, or set OSCHECKER_MACHINE_ID." >&2
        exit 1
    fi
}
require_bin_writable() {
    mkdir -p "$bin_dir" 2>/dev/null || true
    if [[ ! -w "$bin_dir" ]]; then
        echo "Error: No permission to write to $bin_dir. Try running with sudo." >&2
        exit 1
    fi
}
require_systemctl() {
    if ! command -v systemctl &>/dev/null; then
        echo "Error: 'systemctl' not found. oschecker requires systemd for scheduled checkins." >&2
        exit 1
    fi
}
require_units_writable() {
    mkdir -p "$units_dir" 2>/dev/null || true
    if [[ ! -w "$units_dir" ]]; then
        echo "Error: No permission to write to $units_dir. Try running with sudo." >&2
        exit 1
    fi
}

# Machine ID file
write_machine_id() {
    mkdir -p "$(dirname "$id_file")"
    printf '%s' "$1" > "$id_file"
    chmod 644 "$id_file"
    machine_id="$1"
}

# Collect OS info
read_os_release() {
    if [[ ! -f /etc/os-release ]]; then
        echo "Error: Required file '/etc/os-release' not found." >&2
        exit 1
    fi
    os_id="$(grep -oP '^ID="?\K[^"\s]+' /etc/os-release)"
    os_release="$(grep -oP '^VERSION_ID="?\K[^"\s]+' /etc/os-release || true)"
    os_architecture="$(uname -m)"
}

# Collect installed packages
collect_packages() {
    if command -v dpkg-query &>/dev/null; then
        field_packages="$(dpkg-query -W -f='${Package} ${Architecture} ${Version} ${Status}\n' | awk -v arch="$(dpkg --print-architecture)" '($2 == arch || $2 == "all") && $NF == "installed" {print $1, $3}')"
    elif command -v pacman &>/dev/null; then
        field_packages="$(pacman -Q)"
    elif command -v rpm &>/dev/null; then
        field_packages="$(rpm -qa --qf '%{NAME} %{VERSION}-%{RELEASE}\n' | sort)"
    else
        echo "Error: No supported package manager found." >&2
        exit 1
    fi
}

# Run all executable files in the plugins-directory, collecting names/outputs
collect_plugins() {
    plugin_names=()
    plugin_outputs=()
    if [[ -d "$plugins_dir" ]]; then
        for script in "$plugins_dir"/*; do
            if [[ -x "$script" && -f "$script" ]]; then
                plugin_names+=("$(basename "${script%.*}")")
                plugin_outputs+=("$("$script")")
            fi
        done
    fi
}

# Convert plugin names and outputs into a JSON object
plugins_json_from_kv() {
    local result="" i
    for (( i = 0; i < ${#plugin_names[@]}; i++ )); do
        if [[ -n "$result" ]]; then
            result+=","
        fi
        result+="\"$(json_escape "${plugin_names[i]}")\":\"$(json_escape "${plugin_outputs[i]}")\""
    done
    printf '{%s}' "$result"
}

# Print aligned "name: value" lines
print_plugins_kv() {
    [[ ${#plugin_names[@]} -eq 0 ]] && return
    local width="${1:-}" i label indent line first
    if [[ -z "$width" ]]; then
        width=0
        for i in "${plugin_names[@]}"; do
            (( ${#i} + 1 > width )) && width=$(( ${#i} + 1 ))
        done
    fi
    indent="$(printf '%*s' "$(( width + 2 ))" "")"
    for (( i = 0; i < ${#plugin_names[@]}; i++ )); do
        label="$(printf '%-*s' "$width" "${plugin_names[i]}:")"
        first=1
        while IFS= read -r line; do
            if [[ $first -eq 1 ]]; then
                printf '%s%s%s  %s\n' "$color_gray" "$label" "$color_reset" "$line"
                first=0
            else
                printf '%s%s\n' "$indent" "$line"
            fi
        done <<< "${plugin_outputs[i]}"
    done
}

# Download the latest oschecker
download_binary() {
    require_curl
    require_bin_writable
    curl -fsSL "$cli_url" -o "$bin_dir/oschecker"
    chmod +x "$bin_dir/oschecker"
}

# Create and enable the systemd timer that drives scheduled checkins
install_schedule() {
    require_systemctl
    require_units_writable
    cat > "$units_dir/$service_unit" <<EOF
[Unit]
Description=OS Checker checkin

[Service]
Type=oneshot
ExecStart=$bin_dir/oschecker checkin
EOF
    cat > "$units_dir/$timer_unit" <<EOF
[Unit]
Description=OS Checker scheduled checkin

[Timer]
OnCalendar=$timer_schedule
Persistent=true

[Install]
WantedBy=timers.target
EOF
    systemctl daemon-reload
    systemctl enable --now "$timer_unit"
}

# Disable and remove the systemd timer
uninstall_schedule() {
    require_systemctl
    require_units_writable
    systemctl disable --now "$timer_unit" &>/dev/null || true
    rm -f "$units_dir/$service_unit" "$units_dir/$timer_unit"
    systemctl daemon-reload
}

# HTTP request and print response
request() {
    local headers_file body status content_type
    headers_file="$(mktemp)"
    body="$(curl -sS -D "$headers_file" "$@")" || { rm -f "$headers_file"; return 1; }
    status="$(grep -Eo '^HTTP/[0-9.]+ [0-9]+' "$headers_file" | tail -1 | awk '{print $2}')"
    content_type="$(grep -i '^content-type:' "$headers_file" | tail -1 | cut -d: -f2- | tr -d '\r' | xargs)"
    rm -f "$headers_file"
    if [[ "$content_type" == text/plain* ]]; then
        printf '%s\n' "$body"
    else
        printf '%sRequest error: HTTP status %s.%s\n' "$color_red" "${status:-unknown}" "$color_reset"
    fi
}

# Commands
cmd_install() {
    if [[ $# -lt 1 ]]; then
        echo "Usage: $prog install <machine-id>" >&2
        exit 1
    fi
    if [[ -f "$id_file" ]]; then
        echo "Error: oschecker is already installed (machine ID found at $id_file). Run '$prog update' instead, or remove $id_file first." >&2
        exit 1
    fi
    download_binary
    write_machine_id "$1"
    mkdir -p "$plugins_dir"
    install_schedule
    echo "Installed oschecker to $bin_dir/oschecker"
    echo "Scheduled checkin every day via $timer_unit"
    cmd_checkin
}

cmd_update() {
    download_binary
    echo "Updated oschecker"
    cmd_checkin
}

cmd_uninstall() {
    if [[ ! -f "$id_file" ]]; then
        echo "Error: oschecker is not installed (no machine ID found at $id_file)." >&2
        exit 1
    fi
    require_bin_writable
    uninstall_schedule
    rm -f "$bin_dir/oschecker" "$id_file"
    echo "Uninstalled oschecker"
    echo "This machine is still registered on your account - delete it from your Machines page on the website if you want it gone for good"
    echo "Left $plugins_dir in place - remove it by hand if you want"
}

cmd_checkin() {
    local dry_run=0
    if [[ $# -gt 0 ]]; then
        case "$1" in
            --dry-run)
                dry_run=1
                ;;
            *)
                echo "Usage: $prog checkin [--dry-run]" >&2
                echo "Set OSCHECKER_MACHINE_ID to check in without installing." >&2
                exit 1
                ;;
        esac
    fi

    read_os_release
    collect_packages

    if [[ $dry_run -eq 1 ]]; then
        collect_plugins
        local count width name
        count="$(printf '%s\n' "$field_packages" | grep -c . || true)"
        width=9  # len("packages:")
        for name in "${plugin_names[@]}"; do
            (( ${#name} + 1 > width )) && width=$(( ${#name} + 1 ))
        done
        printf '%s%-*s%s  %s\n' "$color_gray" "$width" "os:" "$color_reset" "$os_id $os_release"
        printf '%s%-*s%s  %s\n' "$color_gray" "$width" "packages:" "$color_reset" "$count installed"
        print_plugins_kv "$width"
        return
    fi

    require_machine_id

    collect_plugins
    local payload
    payload="$(printf '{"os":"%s","release":"%s","architecture":"%s","packages":%s,"plugins":%s}' \
        "$(json_escape "$os_id")" \
        "$(json_escape "$os_release")" \
        "$(json_escape "$os_architecture")" \
        "$(build_json_map "$field_packages")" \
        "$(plugins_json_from_kv)")"

    require_curl
    request -X POST "$checkin_url" \
        -H 'Content-Type: application/json' \
        -H 'Accept: text/plain' \
        -H "User-Agent: $client" \
        -H "Authorization: Bearer $machine_id" \
        --data-binary "$payload"
}

cmd_machines() {
    require_machine_id
    require_curl
    request -X GET "$machines_url" \
        -H 'Accept: text/plain' \
        -H "User-Agent: $client" \
        -H "Authorization: Bearer $machine_id"
}

cmd_machine() {
    local target_name="" detailed=0
    for arg in "$@"; do
        case "$arg" in
            --detailed)
                detailed=1
                ;;
            *)
                target_name="$arg"
                ;;
        esac
    done
    local url="$machine_url"
    if [[ -n "$target_name" ]]; then
        url+="/$target_name"
    fi
    if [[ $detailed -eq 1 ]]; then
        url+="?detailed=1"
    fi
    require_machine_id
    require_curl
    request -X GET "$url" \
        -H 'Accept: text/plain' \
        -H "User-Agent: $client" \
        -H "Authorization: Bearer $machine_id"
}

usage() {
    echo "Usage: $prog <install|update|uninstall|checkin|machines|machine> ..." >&2
    exit 1
}

# Main entry point
if [[ $# -lt 1 ]]; then
    usage
fi
cmd="$1"
shift
case "$cmd" in
    install)
        cmd_install "$@"
        ;;
    update)
        cmd_update "$@"
        ;;
    uninstall)
        cmd_uninstall "$@"
        ;;
    checkin)
        cmd_checkin "$@"
        ;;
    machines)
        cmd_machines "$@"
        ;;
    machine)
        cmd_machine "$@"
        ;;
    *)
        usage
        ;;
esac
