#!/usr/bin/env bash

#########################################################################################
#### sysinfo.sh — One-shot system information dump for Linux servers                 ####
#### Shows OS, CPU, memory, disk, network, services, load, and uptime at a glance    ####
#### No dependencies beyond coreutils and standard Linux tools                       ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.01                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./sysinfo.sh                                                                  ####
####   ./sysinfo.sh --no-color                                                       ####
####   ./sysinfo.sh --section disk,network                                           ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################

set -euo pipefail

# ── Defaults ──────────────────────────────────────────────────────────
SECTIONS="${SECTIONS:-all}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"

# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME

# ── Colors ────────────────────────────────────────────────────────────
setup_colors() {
    if [[ "$COLOR" == "never" ]]; then
        RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
        return
    fi
    if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
        RED='\033[0;31m'
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        BLUE='\033[0;34m'
        CYAN='\033[0;36m'
        BOLD='\033[1m'
        DIM='\033[2m'
        RESET='\033[0m'
    else
        RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
    fi
}

# ── Logging ───────────────────────────────────────────────────────────
log()     { echo -e "${BLUE}[INFO]${RESET} $*"; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }

# ── Helpers ───────────────────────────────────────────────────────────
section_header() {
    echo ""
    echo -e "  ${BOLD}${CYAN}── $1 ──${RESET}"
    echo ""
}

field() {
    printf "  ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
}

field_color() {
    printf "  ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
}

human_bytes() {
    local bytes="$1"
    if [[ "$bytes" -ge 1073741824 ]]; then
        awk "BEGIN { printf \"%.1f GiB\", $bytes / 1073741824 }"
    elif [[ "$bytes" -ge 1048576 ]]; then
        awk "BEGIN { printf \"%.1f MiB\", $bytes / 1048576 }"
    elif [[ "$bytes" -ge 1024 ]]; then
        awk "BEGIN { printf \"%.1f KiB\", $bytes / 1024 }"
    else
        echo "${bytes} B"
    fi
}

should_show() {
    [[ "$SECTIONS" == "all" ]] || [[ ",$SECTIONS," == *",$1,"* ]]
}

# ══════════════════════════════════════════════════════════════════════
#                       OS INFORMATION
# ══════════════════════════════════════════════════════════════════════

show_os() {
    section_header "Operating System"

    local hostname_val
    hostname_val=$(hostname -f 2>/dev/null || hostname)
    field "Hostname:" "$hostname_val"

    if [[ -f /etc/os-release ]]; then
        local pretty_name version_id
        # shellcheck disable=SC1091
        pretty_name=$(. /etc/os-release && echo "${PRETTY_NAME:-Unknown}")
        # shellcheck disable=SC1091
        version_id=$(. /etc/os-release && echo "${VERSION_ID:-}")
        field "Distribution:" "$pretty_name"
        if [[ -n "$version_id" ]]; then
            field "Version:" "$version_id"
        fi
    elif [[ -f /etc/redhat-release ]]; then
        field "Distribution:" "$(cat /etc/redhat-release)"
    fi

    field "Kernel:" "$(uname -r)"
    field "Architecture:" "$(uname -m)"
    field "Uptime:" "$(uptime -p 2>/dev/null || uptime | sed 's/.*up //' | sed 's/,  [0-9]* user.*//')"
    field "Boot time:" "$(who -b 2>/dev/null | awk '{print $3, $4}' || echo "N/A")"

    local tz
    tz=$(timedatectl 2>/dev/null | grep "Time zone" | awk '{print $3}' || echo "${TZ:-$(cat /etc/timezone 2>/dev/null || echo 'Unknown')}")
    field "Timezone:" "$tz"
    field "Current time:" "$(date '+%Y-%m-%d %H:%M:%S %Z')"

    if command -v sestatus &>/dev/null; then
        local selinux_status
        selinux_status=$(sestatus 2>/dev/null | grep "SELinux status" | awk '{print $3}' || echo "N/A")
        field "SELinux:" "$selinux_status"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       CPU INFORMATION
# ══════════════════════════════════════════════════════════════════════

show_cpu() {
    section_header "CPU"

    if [[ -f /proc/cpuinfo ]]; then
        local model cores sockets
        model=$(grep -m1 "model name" /proc/cpuinfo | cut -d: -f2 | sed 's/^ //')
        cores=$(grep -c "^processor" /proc/cpuinfo || true)
        sockets=$(grep "physical id" /proc/cpuinfo | sort -u | wc -l)

        field "Model:" "${model:-Unknown}"
        field "Logical CPUs:" "$cores"
        if [[ "$sockets" -gt 0 ]]; then
            field "Sockets:" "$sockets"
        fi
    else
        field "CPUs:" "$(nproc 2>/dev/null || echo 'Unknown')"
    fi

    # Load averages
    if [[ -f /proc/loadavg ]]; then
        local load
        load=$(cut -d' ' -f1-3 /proc/loadavg)
        field "Load average:" "$load"
    fi

    # CPU usage snapshot
    if command -v mpstat &>/dev/null; then
        local idle
        idle=$(mpstat 1 1 2>/dev/null | tail -1 | awk '{print $NF}')
        if [[ -n "$idle" ]]; then
            local used
            used=$(awk "BEGIN { printf \"%.1f\", 100 - $idle }")
            field "CPU usage:" "${used}%"
        fi
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       MEMORY INFORMATION
# ══════════════════════════════════════════════════════════════════════

show_memory() {
    section_header "Memory"

    if [[ -f /proc/meminfo ]]; then
        local total_kb free_kb avail_kb buffers_kb cached_kb swap_total_kb swap_free_kb
        total_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
        free_kb=$(awk '/^MemFree:/ {print $2}' /proc/meminfo)
        avail_kb=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo)
        buffers_kb=$(awk '/^Buffers:/ {print $2}' /proc/meminfo)
        cached_kb=$(awk '/^Cached:/ {print $2}' /proc/meminfo)
        swap_total_kb=$(awk '/^SwapTotal:/ {print $2}' /proc/meminfo)
        swap_free_kb=$(awk '/^SwapFree:/ {print $2}' /proc/meminfo)

        local total_b used_b avail_b
        total_b=$((total_kb * 1024))
        avail_b=$((avail_kb * 1024))
        used_b=$(( (total_kb - free_kb - buffers_kb - cached_kb) * 1024 ))

        local pct_used
        pct_used=$(awk "BEGIN { printf \"%.0f\", ($total_kb - $avail_kb) * 100 / $total_kb }")

        local color="$GREEN"
        if [[ "$pct_used" -ge 90 ]]; then
            color="$RED"
        elif [[ "$pct_used" -ge 75 ]]; then
            color="$YELLOW"
        fi

        field "Total:" "$(human_bytes "$total_b")"
        field_color "Used:" "${color}$(human_bytes "$used_b") (${pct_used}%)${RESET}"
        field "Available:" "$(human_bytes "$avail_b")"

        if [[ "$swap_total_kb" -gt 0 ]]; then
            local swap_used_kb swap_pct
            swap_used_kb=$((swap_total_kb - swap_free_kb))
            swap_pct=$(awk "BEGIN { printf \"%.0f\", $swap_used_kb * 100 / $swap_total_kb }")
            field "Swap:" "$(human_bytes $((swap_total_kb * 1024))) total, $(human_bytes $((swap_used_kb * 1024))) used (${swap_pct}%)"
        else
            field "Swap:" "None"
        fi
    else
        free -h 2>/dev/null || echo "  Memory info unavailable"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       DISK INFORMATION
# ══════════════════════════════════════════════════════════════════════

show_disk() {
    section_header "Disk"

    printf "  ${BOLD}%-20s %8s %8s %8s %6s${RESET}\n" "FILESYSTEM" "SIZE" "USED" "AVAIL" "USE%"
    printf "  %s\n" "$(printf '%.0s─' {1..58})"

    df -h --output=target,size,used,avail,pcent -x tmpfs -x devtmpfs -x overlay 2>/dev/null | tail -n +2 | sort | while IFS= read -r line; do
        local mount size used avail pct
        mount=$(echo "$line" | awk '{print $1}')
        size=$(echo "$line" | awk '{print $2}')
        used=$(echo "$line" | awk '{print $3}')
        avail=$(echo "$line" | awk '{print $4}')
        pct=$(echo "$line" | awk '{print $5}' | tr -d '%')

        local color="$GREEN"
        if [[ -n "$pct" ]]; then
            if [[ "$pct" -ge 90 ]]; then
                color="$RED"
            elif [[ "$pct" -ge 75 ]]; then
                color="$YELLOW"
            fi
        fi

        printf "  %-20s %8s %8s %8s %b%5s%%%b\n" "$mount" "$size" "$used" "$avail" "$color" "${pct:-?}" "$RESET"
    done

    # Inode usage for root
    echo ""
    local inode_pct
    inode_pct=$(df -i / 2>/dev/null | tail -1 | awk '{print $5}' | tr -d '%')
    if [[ -n "$inode_pct" ]]; then
        local icolor="$GREEN"
        if [[ "$inode_pct" -ge 90 ]]; then icolor="$RED"
        elif [[ "$inode_pct" -ge 75 ]]; then icolor="$YELLOW"; fi
        field_color "Inode usage (/):" "${icolor}${inode_pct}%${RESET}"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       NETWORK INFORMATION
# ══════════════════════════════════════════════════════════════════════

show_network() {
    section_header "Network"

    # Interfaces with IPs
    printf "  ${BOLD}%-16s %-18s %-16s %s${RESET}\n" "INTERFACE" "IPv4" "STATE" "MAC"
    printf "  %s\n" "$(printf '%.0s─' {1..65})"

    if command -v ip &>/dev/null; then
        ip -o addr show 2>/dev/null | grep "inet " | while IFS= read -r line; do
            local iface addr state mac
            iface=$(echo "$line" | awk '{print $2}')
            addr=$(echo "$line" | awk '{print $4}')

            [[ "$iface" == "lo" ]] && continue

            state=$(ip link show "$iface" 2>/dev/null | grep -o "state [A-Z]*" | awk '{print $2}')
            mac=$(ip link show "$iface" 2>/dev/null | grep "link/ether" | awk '{print $2}')

            printf "  %-16s %-18s %-16s %s\n" "$iface" "$addr" "${state:-UNKNOWN}" "${mac:--}"
        done
    fi

    # Default gateway
    local gw
    gw=$(ip route show default 2>/dev/null | awk '{print $3, "via", $5}' | head -1)
    if [[ -n "$gw" ]]; then
        echo ""
        field "Default gateway:" "$gw"
    fi

    # DNS servers
    local dns
    dns=$(grep "^nameserver" /etc/resolv.conf 2>/dev/null | awk '{print $2}' | tr '\n' ' ')
    if [[ -n "$dns" ]]; then
        field "DNS servers:" "$dns"
    fi

    # Listening ports (top 15)
    if command -v ss &>/dev/null; then
        echo ""
        printf "  ${BOLD}%-8s %-24s %s${RESET}\n" "PROTO" "LISTEN" "PROCESS"
        printf "  %s\n" "$(printf '%.0s─' {1..55})"

        ss -tlnp 2>/dev/null | tail -n +2 | head -15 | while IFS= read -r line; do
            local proto addr proc_info
            proto="tcp"
            addr=$(echo "$line" | awk '{print $4}')
            proc_info=$(echo "$line" | grep -oP 'users:\(\("\K[^"]+' || echo "-")

            printf "  %-8s %-24s %s\n" "$proto" "$addr" "$proc_info"
        done

        local total_listeners
        total_listeners=$(ss -tlnp 2>/dev/null | tail -n +2 | wc -l)
        if [[ "$total_listeners" -gt 15 ]]; then
            echo -e "  ${DIM}... and $((total_listeners - 15)) more${RESET}"
        fi
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       SERVICES
# ══════════════════════════════════════════════════════════════════════

show_services() {
    section_header "Services"

    if command -v systemctl &>/dev/null; then
        # Failed services
        local failed_count
        failed_count=$(systemctl --no-legend --state=failed 2>/dev/null | wc -l)

        if [[ "$failed_count" -gt 0 ]]; then
            field_color "Failed services:" "${RED}${failed_count}${RESET}"
            echo ""
            systemctl --no-legend --state=failed 2>/dev/null | while IFS= read -r line; do
                local unit
                unit=$(echo "$line" | awk '{print $2}')
                printf "    ${RED}✗${RESET} %s\n" "$unit"
            done
            echo ""
        else
            field_color "Failed services:" "${GREEN}0${RESET}"
        fi

        # Running service count
        local running_count
        running_count=$(systemctl --no-legend --type=service --state=running 2>/dev/null | wc -l)
        field "Running services:" "$running_count"

        # Enabled timers
        local timer_count
        timer_count=$(systemctl --no-legend --type=timer --state=active 2>/dev/null | wc -l)
        field "Active timers:" "$timer_count"
    else
        log "systemd not available"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       SECURITY
# ══════════════════════════════════════════════════════════════════════

show_security() {
    section_header "Security"

    # Pending updates
    if command -v apt-get &>/dev/null; then
        local upgradable
        local apt_output
        apt_output=$(apt list --upgradable 2>/dev/null || true)
        upgradable=$(echo "$apt_output" | grep -c "upgradable" || true)
        field "Pending updates:" "$upgradable"

        local security_updates
        security_updates=$(echo "$apt_output" | grep -c "\-security" || true)
        if [[ "$security_updates" -gt 0 ]]; then
            field_color "Security updates:" "${RED}${security_updates}${RESET}"
        fi
    elif command -v yum &>/dev/null; then
        local yum_updates
        yum_updates=$(yum check-update --quiet 2>/dev/null | grep -cE "^\S" || true)
        field "Pending updates:" "$yum_updates"
    elif command -v dnf &>/dev/null; then
        local dnf_updates
        dnf_updates=$(dnf check-update --quiet 2>/dev/null | grep -cE "^\S" || true)
        field "Pending updates:" "$dnf_updates"
    fi

    # Logged in users
    local user_count
    user_count=$(who 2>/dev/null | wc -l)
    field "Logged in users:" "$user_count"

    if [[ "$user_count" -gt 0 ]]; then
        who 2>/dev/null | awk '{printf "    %s from %s since %s %s\n", $1, $5, $3, $4}'
    fi

    # Reboot required
    if [[ -f /var/run/reboot-required ]]; then
        echo ""
        field_color "Reboot required:" "${YELLOW}YES${RESET}"
    fi

    # Last 5 failed login attempts
    if command -v lastb &>/dev/null && [[ -r /var/log/btmp ]]; then
        local failed_logins
        failed_logins=$(lastb -n 5 2>/dev/null | head -5 | grep -c "" || true)
        if [[ "$failed_logins" -gt 0 ]]; then
            echo ""
            field "Recent failed logins:" ""
            lastb -n 5 2>/dev/null | head -5 | while IFS= read -r line; do
                [[ -z "$line" ]] && continue
                printf "    %s\n" "$line"
            done
        fi
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       DOCKER / CONTAINERS
# ══════════════════════════════════════════════════════════════════════

show_containers() {
    if ! command -v docker &>/dev/null && ! command -v podman &>/dev/null; then
        return
    fi

    section_header "Containers"

    local runtime="docker"
    if ! command -v docker &>/dev/null; then
        runtime="podman"
    fi

    if ! "$runtime" info &>/dev/null 2>&1; then
        field "Status:" "${runtime} not accessible (permission denied or not running)"
        return
    fi

    local running stopped total
    running=$("$runtime" ps -q 2>/dev/null | wc -l)
    total=$("$runtime" ps -aq 2>/dev/null | wc -l)
    stopped=$((total - running))

    field "Runtime:" "$runtime"
    field "Running:" "$running"
    field "Stopped:" "$stopped"

    # Disk usage
    if [[ "$runtime" == "docker" ]]; then
        local docker_disk
        docker_disk=$(docker system df --format "{{.Size}}" 2>/dev/null | head -1)
        if [[ -n "$docker_disk" ]]; then
            field "Image disk:" "$docker_disk"
        fi
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       USAGE
# ══════════════════════════════════════════════════════════════════════

usage() {
    cat <<EOF
${SCRIPT_NAME} — One-shot system information dump

USAGE:
  ${SCRIPT_NAME} [OPTIONS]

OPTIONS:
  --section SECTIONS    Comma-separated sections to show (default: all)
                        Available: os, cpu, memory, disk, network, services,
                        security, containers
  --verbose             Enable debug output
  --no-color            Disable colored output
  --help                Show this help

ENVIRONMENT VARIABLES:
  SECTIONS              Sections to display (default: all)
  COLOR                 Color mode: auto, always, never (default: auto)

EXAMPLES:
  # Full system info
  ./sysinfo.sh

  # Disk and network only
  ./sysinfo.sh --section disk,network

  # Pipe-friendly output
  ./sysinfo.sh --no-color | tee /tmp/sysinfo-\$(hostname).txt
EOF
}

# ══════════════════════════════════════════════════════════════════════
#                       ARGUMENT PARSING
# ══════════════════════════════════════════════════════════════════════

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --section)
                SECTIONS="$2"; shift 2 ;;
            --verbose)
                VERBOSE="true"; shift ;;
            --no-color)
                COLOR="never"; shift ;;
            --help|-h)
                setup_colors
                usage
                exit 0 ;;
            *)
                echo "Unknown option: $1" >&2
                echo "Run ${SCRIPT_NAME} --help for usage" >&2
                exit 1 ;;
        esac
    done
}

# ══════════════════════════════════════════════════════════════════════
#                       MAIN
# ══════════════════════════════════════════════════════════════════════

main() {
    parse_args "$@"
    setup_colors

    echo ""
    echo -e "${BOLD}System Information — $(hostname -f 2>/dev/null || hostname)${RESET}"
    echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}"

    should_show "os"         && show_os
    should_show "cpu"        && show_cpu
    should_show "memory"     && show_memory
    should_show "disk"       && show_disk
    should_show "network"    && show_network
    should_show "services"   && show_services
    should_show "security"   && show_security
    should_show "containers" && show_containers

    echo ""
}

main "$@"
