#!/bin/bash
# admin - CLI dashboard for MeekServer admin scripts and services
# Usage:
#   admin              Interactive menu (fzf)
#   admin status       Unified dashboard: scripts + timer services (grouped)
#   admin timers       Timer schedule view (next/last run times)
#   admin log [name]   Show recent log for a script (category picker if no name)
#   admin run [name]   Run a script (category picker if no name)
#   admin edit [name]  Edit a script (category picker if no name)

set -euo pipefail

LOG_DIR="/var/log/server-admin"
SCRIPT_DIRS=(/usr/sbin /usr/bin)
CACHE_FILE="/tmp/.server-admin-cache"
CACHE_MAX_AGE=300

# Timer name patterns for admin services
TIMER_PATTERN='(update-|backup-|clean-|run-|kometa|kavita|dailies-|cloudflare-|haos-|unifi-|maintain-|vaultwarden-|nextcloud-|compress-|amp|certbot)'

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
DIM='\033[2m'
BOLD='\033[1m'
RESET='\033[0m'

# Category definitions
CATEGORY_ORDER=(update backup run maintenance sync dailies network notify other)
declare -A CATEGORY_LABELS=(
    [update]="Updates"
    [backup]="Backups"
    [run]="Run Jobs"
    [maintenance]="Maintenance"
    [sync]="Sync"
    [dailies]="Dailies"
    [network]="Network"
    [notify]="Notifications"
    [other]="Other"
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

# Get all admin scripts (cached)
get_scripts() {
    if [[ -f "$CACHE_FILE" ]]; then
        local age
        age=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE") ))
        if [[ $age -lt $CACHE_MAX_AGE ]]; then
            cat "$CACHE_FILE"
            return
        fi
    fi

    local scripts=()

    while IFS= read -r f; do
        scripts+=("$(basename "$f")")
    done < <(sudo grep -rl "admin-common.sh" "${SCRIPT_DIRS[@]}" 2>/dev/null)

    for log in "$LOG_DIR"/*.log; do
        [[ -f "$log" ]] || continue
        local name
        name=$(basename "$log" .log)
        local found=false
        for s in "${scripts[@]}"; do
            [[ "$s" == "$name" ]] && found=true && break
        done
        if ! $found; then
            for dir in "${SCRIPT_DIRS[@]}"; do
                if [[ -f "$dir/$name" ]]; then
                    scripts+=("$name")
                    break
                fi
            done
        fi
    done

    printf '%s\n' "${scripts[@]}" | grep -v '^admin$' | sort -u | tee "$CACHE_FILE"
}

# Get timer-only services (not already covered by scripts)
# Returns: timer_name|service_name lines
get_timer_services() {
    local scripts
    scripts=$(get_scripts)

    systemctl list-timers --all --no-pager 2>/dev/null | grep -E "$TIMER_PATTERN" | \
    while read -r line; do
        local timer svc
        timer=$(echo "$line" | awk '{for(i=1;i<=NF;i++) if($i ~ /\.timer$/) print $i}')
        svc=$(echo "$line" | awk '{for(i=1;i<=NF;i++) if($i ~ /\.service$/) print $i}')
        [[ -z "$timer" || -z "$svc" ]] && continue

        local timer_name="${timer%.timer}"
        local svc_name="${svc%.service}"

        # Skip if we already track this as a script
        local found=false
        while IFS= read -r s; do
            [[ "$s" == "$timer_name" || "$s" == "$svc_name" ]] && found=true && break
        done <<< "$scripts"

        # Also check if the service ExecStart points to a script we track
        if ! $found; then
            local exec_path
            exec_path=$(systemctl show "$svc" --property=ExecStart --no-pager 2>/dev/null | grep -oP 'path=\K[^ ;]+')
            if [[ -n "$exec_path" ]]; then
                local exec_name
                exec_name=$(basename "$exec_path")
                while IFS= read -r s; do
                    [[ "$s" == "$exec_name" ]] && found=true && break
                done <<< "$scripts"
            fi
        fi

        $found || echo "${timer_name}|${svc_name}"
    done
}

# Categorize a name
get_category() {
    local name="$1"
    case "$name" in
        update-*)      echo "update" ;;
        backup-*)      echo "backup" ;;
        run-*)         echo "run" ;;
        clean-*)       echo "maintenance" ;;
        certbot-*)     echo "maintenance" ;;
        sync-*)        echo "sync" ;;
        dailies-*)     echo "dailies" ;;
        maintain-*)    echo "dailies" ;;
        cloudflare-*)  echo "network" ;;
        clamav-*|nut-*|systemd-failure-*) echo "notify" ;;
        *)             echo "other" ;;
    esac
}

# Parse last run from log file (single pass)
parse_last_run() {
    local log_file="$1"

    # If current log is missing or empty, try the most recent rotated log
    if [[ ! -s "$log_file" ]]; then
        local rotated
        rotated=$(ls -t "${log_file}"-* 2>/dev/null | grep -v '\.gz$' | head -1)
        if [[ -n "$rotated" ]]; then
            log_file="$rotated"
        fi
    fi
    if [[ ! -s "$log_file" ]]; then
        echo "never||"
        return
    fi

    local last_end="" last_error="" last_ok="" last_start=""
    local line

    while IFS= read -r line; do
        case "$line" in
            *'[START] =='*) last_start="$line" ;;
            *'[END]'*'completed in'*) last_end="$line" ;;
            *'[ERROR]'*) last_error="$line" ;;
            *'[OK]'*) last_ok="$line" ;;
        esac
    done < <(sudo cat "$log_file" 2>/dev/null)

    if [[ -z "$last_start" ]]; then
        echo "never||"
        return
    fi

    local timestamp
    timestamp=$(echo "$last_start" | grep -oP '^\[\K[0-9-]+ [0-9:]+')

    if [[ -n "$last_end" ]]; then
        local end_ts duration
        end_ts=$(echo "$last_end" | grep -oP '^\[\K[0-9-]+ [0-9:]+')
        duration=$(echo "$last_end" | grep -oP 'completed in \K.*')

        if [[ -n "$last_error" ]]; then
            local error_ts
            error_ts=$(echo "$last_error" | grep -oP '^\[\K[0-9-]+ [0-9:]+')
            if [[ "$error_ts" > "$end_ts" ]]; then
                echo "${timestamp}|error|"
                return
            fi
        fi

        if [[ -n "$last_ok" ]] && echo "$last_ok" | grep -qi "up to date"; then
            local ok_ts
            ok_ts=$(echo "$last_ok" | grep -oP '^\[\K[0-9-]+ [0-9:]+')
            if [[ "$ok_ts" == "$end_ts" ]] || [[ "$ok_ts" > "$timestamp" ]]; then
                echo "${timestamp}|current|${duration}"
                return
            fi
        fi

        echo "${timestamp}|ok|${duration}"
    elif [[ -n "$last_error" ]]; then
        echo "${timestamp}|error|"
    else
        echo "${timestamp}|running|"
    fi
}

# Get status for a timer-triggered service from systemd
parse_service_status() {
    local svc_name="$1"
    local svc="${svc_name}.service"

    local result last_trigger
    result=$(systemctl show "$svc" --property=Result --no-pager 2>/dev/null | cut -d= -f2-)
    last_trigger=$(systemctl show "${svc_name}.timer" --property=LastTriggerUSec --no-pager 2>/dev/null | cut -d= -f2-)

    local ts="never"
    if [[ -n "$last_trigger" && "$last_trigger" != "n/a" ]]; then
        ts=$(date -d "$last_trigger" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "never")
    fi

    local status
    local active
    active=$(systemctl is-active "$svc" 2>/dev/null || true)

    if [[ "$active" == "active" || "$active" == "activating" ]]; then
        status="running"
    elif [[ "$ts" == "never" ]]; then
        status=""
    elif [[ "$result" == "success" ]]; then
        status="ok"
    elif [[ "$result" == "exit-code" || "$result" == "signal" || "$result" == "timeout" ]]; then
        status="error"
    else
        status="ok"
    fi

    echo "${ts}|${status}|"
}

relative_time() {
    local ts="$1"
    [[ "$ts" == "never" ]] && echo "never" && return

    local then_epoch now_epoch diff
    then_epoch=$(date -d "$ts" +%s 2>/dev/null) || { echo "$ts"; return; }
    now_epoch=$(date +%s)
    diff=$((now_epoch - then_epoch))

    if [[ $diff -lt 60 ]]; then
        echo "${diff}s ago"
    elif [[ $diff -lt 3600 ]]; then
        echo "$((diff / 60))m ago"
    elif [[ $diff -lt 86400 ]]; then
        echo "$((diff / 3600))h ago"
    elif [[ $diff -lt 604800 ]]; then
        echo "$((diff / 86400))d ago"
    else
        echo "$((diff / 604800))w ago"
    fi
}

status_icon() {
    case "$1" in
        ok)      echo -e "${GREEN}✓${RESET}" ;;
        current) echo -e "${BLUE}✓${RESET}" ;;
        error)   echo -e "${RED}✗${RESET}" ;;
        running) echo -e "${YELLOW}●${RESET}" ;;
        *)       echo -e "${DIM}—${RESET}" ;;
    esac
}

status_label() {
    case "$1" in
        ok)      echo -e "${GREEN}ok${RESET}" ;;
        current) echo -e "${BLUE}up to date${RESET}" ;;
        error)   echo -e "${RED}error${RESET}" ;;
        running) echo -e "${YELLOW}running${RESET}" ;;
        *)       echo -e "${DIM}never${RESET}" ;;
    esac
}

print_row() {
    local name="$1" status="$2" rel="$3" duration="$4"
    printf "  %b  %-35s %-20b %-12s %s\n" \
        "$(status_icon "$status")" "$name" "$(status_label "$status")" "$rel" "${duration:--}"
}

scripts_in_category() {
    local cat="$1"
    get_scripts | while IFS= read -r name; do
        [[ "$(get_category "$name")" == "$cat" ]] && echo "$name"
    done
}

active_categories() {
    local scripts
    scripts=$(get_scripts)
    local seen=()
    while IFS= read -r name; do
        local cat
        cat=$(get_category "$name")
        local found=false
        for s in "${seen[@]+"${seen[@]}"}"; do
            [[ "$s" == "$cat" ]] && found=true && break
        done
        if ! $found; then
            seen+=("$cat")
        fi
    done <<< "$scripts"

    for cat in "${CATEGORY_ORDER[@]}"; do
        for s in "${seen[@]}"; do
            [[ "$s" == "$cat" ]] && echo "$cat" && break
        done
    done
}

pick_script() {
    local prompt="${1:-Select}"

    local cat_choices=""
    for cat in $(active_categories); do
        cat_choices+="${CATEGORY_LABELS[$cat]}"$'\n'
    done
    [[ -z "$cat_choices" ]] && return 1

    local chosen_label
    chosen_label=$(echo "$cat_choices" | fzf --prompt="$prompt category > " --height=15 --reverse) || return 1

    local chosen_cat=""
    for cat in "${CATEGORY_ORDER[@]}"; do
        [[ "${CATEGORY_LABELS[$cat]}" == "$chosen_label" ]] && chosen_cat="$cat" && break
    done
    [[ -z "$chosen_cat" ]] && return 1

    local script_list
    script_list=$(scripts_in_category "$chosen_cat")
    [[ -z "$script_list" ]] && return 1

    echo "$script_list" | fzf --prompt="$prompt ${chosen_label,,} > " --height=15 --reverse
}

resolve_script() {
    local name="$1"
    for dir in "${SCRIPT_DIRS[@]}"; do
        if [[ -f "$dir/$name" ]]; then
            echo "$dir/$name"
            return
        fi
    done
    echo ""
}

# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------

cmd_status() {
    echo -e "${BOLD}  Admin Status${RESET}"
    echo -e "${DIM}  $(date '+%Y-%m-%d %H:%M:%S')${RESET}"
    echo ""

    # Collect all entries: name|status|rel|duration
    declare -A category_entries

    # 1. Scripts (status from logs)
    local scripts
    scripts=$(get_scripts)
    while IFS= read -r name; do
        local log_file="$LOG_DIR/${name}.log"
        local info ts status duration rel cat

        info=$(parse_last_run "$log_file")
        ts=$(echo "$info" | cut -d'|' -f1)
        status=$(echo "$info" | cut -d'|' -f2)
        duration=$(echo "$info" | cut -d'|' -f3)
        rel=$(relative_time "$ts")
        cat=$(get_category "$name")

        category_entries[$cat]+="${name}|${status}|${rel}|${duration}"$'\n'
    done <<< "$scripts"

    # 2. Timer-only services (status from systemd)
    while IFS= read -r entry; do
        [[ -z "$entry" ]] && continue
        local timer_name svc_name
        timer_name=$(echo "$entry" | cut -d'|' -f1)
        svc_name=$(echo "$entry" | cut -d'|' -f2)

        local info ts status duration rel cat
        info=$(parse_service_status "$svc_name")
        ts=$(echo "$info" | cut -d'|' -f1)
        status=$(echo "$info" | cut -d'|' -f2)
        duration=$(echo "$info" | cut -d'|' -f3)
        rel=$(relative_time "$ts")
        cat=$(get_category "$timer_name")

        category_entries[$cat]+="${timer_name}|${status}|${rel}|${duration}"$'\n'
    done < <(get_timer_services)

    # Print grouped
    for cat in "${CATEGORY_ORDER[@]}"; do
        local entries="${category_entries[$cat]:-}"
        [[ -z "$entries" ]] && continue

        echo -e "  ${BOLD}${CATEGORY_LABELS[$cat]}${RESET}"
        printf "  ${DIM}%s${RESET}\n" "$(printf '%.0s─' {1..75})"

        while IFS= read -r entry; do
            [[ -z "$entry" ]] && continue
            local name status rel duration
            name=$(echo "$entry" | cut -d'|' -f1)
            status=$(echo "$entry" | cut -d'|' -f2)
            rel=$(echo "$entry" | cut -d'|' -f3)
            duration=$(echo "$entry" | cut -d'|' -f4)
            print_row "$name" "$status" "$rel" "$duration"
        done <<< "$entries"

        echo ""
    done
}

cmd_timers() {
    echo -e "${BOLD}  Timer Schedule${RESET}"
    echo ""

    local custom_timers
    custom_timers=$(systemctl list-timers --all --no-pager 2>/dev/null | \
        grep -E "$TIMER_PATTERN" || true)

    if [[ -z "$custom_timers" ]]; then
        echo "  No admin timers found."
        return
    fi

    printf "  ${DIM}%-3s %-30s %-22s %-22s${RESET}\n" "" "TIMER" "NEXT RUN" "LAST RUN"
    printf "  ${DIM}%s${RESET}\n" "$(printf '%.0s─' {1..80})"

    while IFS= read -r line; do
        local unit next last
        unit=$(echo "$line" | awk '{for(i=1;i<=NF;i++) if($i ~ /\.timer$/) print $i}')
        [[ -z "$unit" ]] && continue

        local timer_info
        timer_info=$(systemctl show "$unit" --property=NextElapseUSecRealtime,LastTriggerUSec --no-pager 2>/dev/null)
        next=$(echo "$timer_info" | grep NextElapse | cut -d= -f2-)
        last=$(echo "$timer_info" | grep LastTrigger | cut -d= -f2-)

        if [[ -n "$next" && "$next" != "n/a" ]]; then
            next=$(date -d "$next" '+%m-%d %H:%M' 2>/dev/null || echo "$next")
        else
            next="—"
        fi
        if [[ -n "$last" && "$last" != "n/a" ]]; then
            last=$(date -d "$last" '+%m-%d %H:%M' 2>/dev/null || echo "$last")
        else
            last="—"
        fi

        local active icon
        active=$(systemctl is-active "$unit" 2>/dev/null || true)
        if [[ "$active" == "active" ]]; then
            icon="${GREEN}●${RESET}"
        else
            icon="${DIM}○${RESET}"
        fi

        printf "  %b  %-30s %-22s %-22s\n" "$icon" "${unit%.timer}" "$next" "$last"
    done <<< "$custom_timers"

    echo ""
}

cmd_log() {
    local name="$1"

    if [[ -z "$name" ]]; then
        name=$(pick_script "View log") || return
    fi

    local log_file="$LOG_DIR/${name}.log"
    if [[ ! -f "$log_file" ]]; then
        # Try systemd journal for timer-only services
        echo -e "${BOLD}  Journal: ${name}${RESET}"
        echo ""
        sudo journalctl -u "${name}.service" --no-pager -n 30 2>/dev/null | while IFS= read -r line; do
            echo "  $line"
        done
        echo ""
        return
    fi

    echo -e "${BOLD}  Log: ${name}${RESET} ${DIM}($log_file)${RESET}"
    echo ""
    sudo tail -50 "$log_file" | while IFS= read -r line; do
        case "$line" in
            *\[ERROR\]*) echo -e "  ${RED}${line}${RESET}" ;;
            *\[WARN\]*)  echo -e "  ${YELLOW}${line}${RESET}" ;;
            *\[OK\]*)    echo -e "  ${GREEN}${line}${RESET}" ;;
            *\[START\]*) echo -e "  ${CYAN}${line}${RESET}" ;;
            *\[END\]*)   echo -e "  ${DIM}${line}${RESET}" ;;
            *)           echo "  $line" ;;
        esac
    done
    echo ""
}

cmd_run() {
    local name="$1"

    if [[ -z "$name" ]]; then
        name=$(pick_script "Run") || return
    fi

    local script_path
    script_path=$(resolve_script "$name")
    if [[ -z "$script_path" ]]; then
        echo -e "  ${RED}Script not found:${RESET} $name"
        return 1
    fi

    echo -e "${BOLD}  Running: ${name}${RESET}"
    echo ""
    sudo "$script_path"
}

cmd_edit() {
    local name="$1"

    if [[ -z "$name" ]]; then
        name=$(pick_script "Edit") || return
    fi

    local script_path
    script_path=$(resolve_script "$name")
    if [[ -z "$script_path" ]]; then
        echo -e "  ${RED}Script not found:${RESET} $name"
        return 1
    fi

    sudo "${EDITOR:-vim}" "$script_path"
}

cmd_interactive() {
    local action
    action=$(printf "status\ntimers\nlog\nedit" | \
        fzf --prompt="admin > " --height=10 --reverse)

    case "$action" in
        status) cmd_status ;;
        timers) cmd_timers ;;
        log)    cmd_log "" ;;
        edit)   cmd_edit "" ;;
        *)      echo "Cancelled." ;;
    esac
}

# Join args with dashes: "update oxyromon" → "update-oxyromon"
join_args() { local IFS=-; echo "$*"; }

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

cmd="${1:-}"
shift 2>/dev/null || true

case "$cmd" in
    status|s)  cmd_status ;;
    timers|t)  cmd_timers ;;
    log|l)     cmd_log "$(join_args "$@")" ;;
    edit|e)    cmd_edit "$(join_args "$@")" ;;
    script)    cmd_run "$1" ;;
    help|-h|--help)
        echo "Usage: admin [command] [name...]"
        echo ""
        echo "Commands:"
        echo "  status, s       Unified dashboard"
        echo "  timers, t       Timer schedule view"
        echo "  log, l [name]   View log (picker if no name)"
        echo "  edit, e [name]  Edit script (picker if no name)"
        echo "  script <name>   Run script by exact name"
        echo "  help            Show this help"
        echo ""
        echo "Anything else runs the script directly:"
        echo "  admin update oxyromon   →  runs update-oxyromon"
        echo "  admin run kometa        →  runs run-kometa"
        echo "  admin backup dailies    →  runs backup-dailies"
        echo "  admin script log-cleanup → runs log-cleanup (exact name)"
        echo ""
        echo "With no args, opens interactive menu."
        ;;
    "")        cmd_interactive ;;
    *)
        # Join all args with dashes as a script name
        cmd_run "$(join_args "$cmd" "$@")"
        ;;
esac
