#!/usr/bin/env bash

#########################################################################################
#### process-top.sh — Show top CPU/memory consumers, zombies, and long-running procs ####
#### Provides a quick snapshot of process health and resource usage                  ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.00                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./process-top.sh                                                              ####
####   ./process-top.sh --top 20 --section cpu,zombies                               ####
####   ./process-top.sh --min-runtime 48                                             ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################

set -euo pipefail

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

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

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

# ── Logging ───────────────────────────────────────────────────────────
log()     { echo -e "${CYAN}[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"
}

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

value_color() {
    local value="$1"
    local int_val
    int_val=$(printf "%.0f" "$value" 2>/dev/null || echo "0")
    if [[ "$int_val" -ge 80 ]]; then
        echo "$YELLOW"
    else
        echo "$GREEN"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       TOP CPU
# ══════════════════════════════════════════════════════════════════════

show_cpu() {
    section_header "Top ${TOP_N} by CPU Usage"

    printf "  ${BOLD}%-8s %-12s %6s %6s %10s  %s${RESET}\n" "PID" "USER" "CPU%" "MEM%" "TIME" "COMMAND"
    printf "  %s\n" "$(printf '%.0s─' {1..72})"

    ps -eo pid,user,pcpu,pmem,etime,comm --sort=-pcpu --no-headers 2>/dev/null | head -n "$TOP_N" | while IFS= read -r line; do
        local pid user cpu mem etime comm
        pid=$(echo "$line" | awk '{print $1}')
        user=$(echo "$line" | awk '{print $2}')
        cpu=$(echo "$line" | awk '{print $3}')
        mem=$(echo "$line" | awk '{print $4}')
        etime=$(echo "$line" | awk '{print $5}')
        comm=$(echo "$line" | awk '{print $6}')

        local cc mc
        cc=$(value_color "$cpu")
        mc=$(value_color "$mem")

        printf "  %-8s %-12s %b%5s%%%b %b%5s%%%b %10s  %s\n" \
            "$pid" "$user" "$cc" "$cpu" "$RESET" "$mc" "$mem" "$RESET" "$etime" "$comm"
    done
}

# ══════════════════════════════════════════════════════════════════════
#                       TOP MEMORY
# ══════════════════════════════════════════════════════════════════════

show_memory() {
    section_header "Top ${TOP_N} by Memory Usage"

    printf "  ${BOLD}%-8s %-12s %6s %6s %10s  %s${RESET}\n" "PID" "USER" "CPU%" "MEM%" "TIME" "COMMAND"
    printf "  %s\n" "$(printf '%.0s─' {1..72})"

    ps -eo pid,user,pcpu,pmem,etime,comm --sort=-pmem --no-headers 2>/dev/null | head -n "$TOP_N" | while IFS= read -r line; do
        local pid user cpu mem etime comm
        pid=$(echo "$line" | awk '{print $1}')
        user=$(echo "$line" | awk '{print $2}')
        cpu=$(echo "$line" | awk '{print $3}')
        mem=$(echo "$line" | awk '{print $4}')
        etime=$(echo "$line" | awk '{print $5}')
        comm=$(echo "$line" | awk '{print $6}')

        local cc mc
        cc=$(value_color "$cpu")
        mc=$(value_color "$mem")

        printf "  %-8s %-12s %b%5s%%%b %b%5s%%%b %10s  %s\n" \
            "$pid" "$user" "$cc" "$cpu" "$RESET" "$mc" "$mem" "$RESET" "$etime" "$comm"
    done
}

# ══════════════════════════════════════════════════════════════════════
#                       ZOMBIES
# ══════════════════════════════════════════════════════════════════════

show_zombies() {
    section_header "Zombie Processes"

    local zombie_list
    zombie_list=$(ps -eo pid,ppid,user,stat,comm --no-headers 2>/dev/null | awk '$4 ~ /^Z/')

    if [[ -z "$zombie_list" ]]; then
        echo -e "  ${GREEN}No zombie processes found${RESET}"
        return
    fi

    printf "  ${BOLD}%-8s %-8s %-12s %-6s %s${RESET}\n" "PID" "PPID" "USER" "STAT" "COMMAND"
    printf "  %s\n" "$(printf '%.0s─' {1..55})"

    echo "$zombie_list" | while IFS= read -r line; do
        local pid ppid user stat comm
        pid=$(echo "$line" | awk '{print $1}')
        ppid=$(echo "$line" | awk '{print $2}')
        user=$(echo "$line" | awk '{print $3}')
        stat=$(echo "$line" | awk '{print $4}')
        comm=$(echo "$line" | awk '{print $5}')

        printf "  ${RED}%-8s${RESET} %-8s %-12s ${RED}%-6s${RESET} %s\n" \
            "$pid" "$ppid" "$user" "$stat" "$comm"
    done
}

# ══════════════════════════════════════════════════════════════════════
#                       LONG-RUNNING
# ══════════════════════════════════════════════════════════════════════

etime_to_hours() {
    local etime="$1"
    local days=0 hours=0 mins=0

    if [[ "$etime" == *-* ]]; then
        days=${etime%%-*}
        etime=${etime#*-}
    fi

    local parts
    IFS=: read -ra parts <<< "$etime"
    local num_parts=${#parts[@]}

    if [[ "$num_parts" -eq 3 ]]; then
        hours=${parts[0]}
        mins=${parts[1]}
    elif [[ "$num_parts" -eq 2 ]]; then
        hours=${parts[0]}
        mins=${parts[1]}
    elif [[ "$num_parts" -eq 1 ]]; then
        mins=${parts[0]}
    fi

    # Remove leading zeros
    days=$((10#$days))
    hours=$((10#$hours))
    mins=$((10#$mins))

    echo $(( days * 24 + hours + (mins > 30 ? 1 : 0) ))
}

show_long_running() {
    section_header "Long-Running Processes (> ${MIN_RUNTIME}h)"

    printf "  ${BOLD}%-8s %-12s %10s %6s  %s${RESET}\n" "PID" "USER" "ELAPSED" "MEM%" "COMMAND"
    printf "  %s\n" "$(printf '%.0s─' {1..62})"

    local found=0
    ps -eo pid,user,etime,pmem,comm --no-headers 2>/dev/null | while IFS= read -r line; do
        local pid user etime mem comm
        pid=$(echo "$line" | awk '{print $1}')
        user=$(echo "$line" | awk '{print $2}')
        etime=$(echo "$line" | awk '{print $3}')
        mem=$(echo "$line" | awk '{print $4}')
        comm=$(echo "$line" | awk '{print $5}')

        local run_hours
        run_hours=$(etime_to_hours "$etime")

        if [[ "$run_hours" -ge "$MIN_RUNTIME" ]]; then
            printf "  %-8s %-12s %10s %5s%%  %s\n" "$pid" "$user" "$etime" "$mem" "$comm"
            found=1
        fi
    done

    if [[ "$found" -eq 0 ]]; then
        echo -e "  ${GREEN}No processes running longer than ${MIN_RUNTIME} hours${RESET}"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       SUMMARY
# ══════════════════════════════════════════════════════════════════════

print_summary() {
    echo ""
    echo -e "  ${BOLD}══════════════════════════════════════════${RESET}"
    echo -e "  ${BOLD}Process Summary${RESET}"
    echo -e "  ${BOLD}══════════════════════════════════════════${RESET}"

    local total_procs zombie_count long_count

    total_procs=$(ps -e --no-headers 2>/dev/null | wc -l)
    zombie_count=$(ps -eo stat --no-headers 2>/dev/null | awk '/^Z/{c++} END{print c+0}')
    long_count=$(ps -eo etime --no-headers 2>/dev/null | while IFS= read -r etime; do
        etime=$(echo "$etime" | xargs)
        local h
        h=$(etime_to_hours "$etime")
        if [[ "$h" -ge "$MIN_RUNTIME" ]]; then
            echo "1"
        fi
    done | wc -l)

    field "Total processes:" "$total_procs"

    if [[ "$zombie_count" -gt 0 ]]; then
        field_color "Zombie processes:" "${RED}${zombie_count}${RESET}"
    else
        field_color "Zombie processes:" "${GREEN}0${RESET}"
    fi

    field "Long-running (>${MIN_RUNTIME}h):" "$long_count"

    echo ""
}

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

usage() {
    cat <<EOF
${SCRIPT_NAME} — Show top CPU/memory consumers, zombies, and long-running processes

USAGE:
  ${SCRIPT_NAME} [OPTIONS]

OPTIONS:
  --top N               Number of processes to show (default: ${TOP_N})
  --min-runtime HOURS   Long-running threshold in hours (default: ${MIN_RUNTIME})
  --section SECTIONS    Comma-separated sections to show (default: all)
                        Available: cpu, memory, zombies, long-running
  --verbose             Enable debug output
  --no-color            Disable colored output
  --help                Show this help

ENVIRONMENT VARIABLES:
  TOP_N                 Number of top processes (default: 10)
  MIN_RUNTIME           Long-running threshold in hours (default: 24)
  SECTIONS              Sections to display (default: all)
  COLOR                 Color mode: auto, always, never (default: auto)

EXAMPLES:
  # Full process overview
  ./process-top.sh

  # Top 20 CPU consumers
  ./process-top.sh --top 20 --section cpu

  # Find zombies and long-running processes
  ./process-top.sh --section zombies,long-running

  # Processes running longer than 48 hours
  ./process-top.sh --min-runtime 48 --section long-running
EOF
}

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

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --top)
                TOP_N="$2"; shift 2 ;;
            --min-runtime)
                MIN_RUNTIME="$2"; shift 2 ;;
            --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}Process Monitor — $(hostname -f 2>/dev/null || hostname)${RESET}"
    echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}"

    should_show "cpu"          && show_cpu
    should_show "memory"       && show_memory
    should_show "zombies"      && show_zombies
    should_show "long-running" && show_long_running

    print_summary
}

main "$@"
