#!/usr/bin/env bash
# gniza4cp/lib/schedule.sh — Cron management for decoupled schedules
#
# Schedules are defined in /etc/gniza4cp/schedules.d/<name>.conf:
#   SCHEDULE="hourly|daily|weekly|monthly|custom"
#   SCHEDULE_TIME="HH:MM"
#   SCHEDULE_DAY=""        # dow (0-6) for weekly, dom (1-28) for monthly
#   SCHEDULE_CRON=""       # full 5-field cron expr for custom
#   REMOTES=""             # comma-separated remote names (empty = all)
#
# Cron lines are tagged with "# gniza4cp:<name>" for clean install/remove.

readonly GNIZA4CP_CRON_TAG="# gniza4cp:"
readonly SCHEDULES_DIR="/etc/gniza4cp/schedules.d"

# ── Discovery ─────────────────────────────────────────────────

# List schedule names (filenames without .conf) sorted alphabetically.
list_schedules() {
    if [[ ! -d "$SCHEDULES_DIR" ]]; then
        return 0
    fi
    local f
    for f in "$SCHEDULES_DIR"/*.conf; do
        [[ -f "$f" ]] || continue
        basename "$f" .conf
    done
}

# Return 0 if at least one schedule config exists.
has_schedules() {
    local schedules
    schedules=$(list_schedules)
    [[ -n "$schedules" ]]
}

# ── Loading ───────────────────────────────────────────────────

# Source a schedule config and set SCHEDULE/REMOTES globals.
# Usage: load_schedule <name>
load_schedule() {
    local name="$1"
    local conf="$SCHEDULES_DIR/${name}.conf"

    if [[ ! -f "$conf" ]]; then
        log_error "Schedule config not found: $conf"
        return 1
    fi

    # Reset schedule globals before sourcing
    SCHEDULE=""
    SCHEDULE_TIME=""
    SCHEDULE_DAY=""
    SCHEDULE_CRON=""
    SCHEDULE_REMOTES=""
    SCHEDULE_SYSBACKUP=""
    SCHEDULE_SKIP_SUSPENDED=""

    _safe_source_config "$conf" || {
        log_error "Failed to parse schedule config: $conf"
        return 1
    }

    # Map REMOTES to SCHEDULE_REMOTES to avoid conflicts
    SCHEDULE_REMOTES="${REMOTES:-}"
    SCHEDULE_SYSBACKUP="${SYSBACKUP:-}"
    SCHEDULE_SKIP_SUSPENDED="${SKIP_SUSPENDED:-}"

    log_debug "Loaded schedule '$name': ${SCHEDULE} at ${SCHEDULE_TIME:-02:00}, remotes=${SCHEDULE_REMOTES:-all}"
}

# ── Cron Generation ───────────────────────────────────────────

# Convert schedule vars to a 5-field cron expression.
# Must be called after load_schedule() sets SCHEDULE/SCHEDULE_TIME/etc.
schedule_to_cron() {
    local name="$1"
    local schedule="${SCHEDULE:-}"
    local stime="${SCHEDULE_TIME:-02:00}"
    local sday="${SCHEDULE_DAY:-}"
    local scron="${SCHEDULE_CRON:-}"

    if [[ -z "$schedule" ]]; then
        return 1  # no schedule configured
    fi

    local hour minute
    hour="${stime%%:*}"
    minute="${stime##*:}"
    # Strip leading zeros for cron
    hour=$((10#$hour))
    minute=$((10#$minute))

    case "$schedule" in
        hourly)
            if [[ -n "$sday" && "$sday" -gt 1 ]] 2>/dev/null; then
                echo "$minute */$sday * * *"
            else
                echo "$minute * * * *"
            fi
            ;;
        daily)
            echo "$minute $hour * * *"
            ;;
        weekly)
            if [[ -z "$sday" ]]; then
                log_error "Schedule '$name': SCHEDULE_DAY required for weekly schedule"
                return 1
            fi
            echo "$minute $hour * * $sday"
            ;;
        monthly)
            if [[ -z "$sday" ]]; then
                log_error "Schedule '$name': SCHEDULE_DAY required for monthly schedule"
                return 1
            fi
            echo "$minute $hour $sday * *"
            ;;
        custom)
            if [[ -z "$scron" ]]; then
                log_error "Schedule '$name': SCHEDULE_CRON required for custom schedule"
                return 1
            fi
            echo "$scron"
            ;;
        *)
            log_error "Schedule '$name': unknown SCHEDULE value: $schedule"
            return 1
            ;;
    esac
}

# Build the full cron line for a schedule.
# Uses SCHEDULE_REMOTES if set, otherwise targets all remotes.
build_cron_line() {
    local name="$1"
    local cron_expr
    cron_expr=$(schedule_to_cron "$name") || return 1

    local extra_flags=""
    if [[ -n "$SCHEDULE_REMOTES" ]]; then
        extra_flags+=" --remote=$SCHEDULE_REMOTES"
    fi
    if [[ "${SCHEDULE_SYSBACKUP:-}" == "yes" ]]; then
        extra_flags+=" --sysbackup"
    fi
    if [[ "${SCHEDULE_SKIP_SUSPENDED:-}" == "yes" ]]; then
        extra_flags+=" --skip-suspended"
    fi

    echo "$cron_expr /usr/local/bin/gniza4cp backup${extra_flags} >/dev/null 2>&1"
}

# ── Crontab Management ────────────────────────────────────────

# Install cron entries for all schedules in schedules.d/.
# Strips any existing gniza4cp entries first, then appends new ones.
install_schedules() {
    if ! has_schedules; then
        log_error "No schedules configured in $SCHEDULES_DIR"
        return 1
    fi

    # Collect new cron lines
    local new_lines=""
    local count=0
    local schedules; schedules=$(list_schedules)

    while IFS= read -r sname; do
        [[ -z "$sname" ]] && continue
        load_schedule "$sname" || { log_error "Skipping schedule '$sname': failed to load"; continue; }

        if [[ -z "${SCHEDULE:-}" ]]; then
            log_debug "Schedule '$sname' has no SCHEDULE type, skipping"
            continue
        fi

        local cron_line
        cron_line=$(build_cron_line "$sname") || { log_error "Skipping schedule '$sname': invalid schedule"; continue; }

        new_lines+="${GNIZA4CP_CRON_TAG}${sname}"$'\n'
        new_lines+="${cron_line}"$'\n'
        ((count++)) || true
    done <<< "$schedules"

    if (( count == 0 )); then
        log_warn "No valid schedules found"
        return 1
    fi

    # Get current crontab, strip old gniza4cp lines
    local current_crontab=""
    current_crontab=$(crontab -l 2>/dev/null) || true

    local filtered=""
    local skip_next=false
    while IFS= read -r line; do
        if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
            skip_next=true
            continue
        fi
        if [[ "$skip_next" == "true" ]]; then
            skip_next=false
            continue
        fi
        filtered+="$line"$'\n'
    done <<< "$current_crontab"

    # Append daily stats collection (runs at 05:00 UTC)
    new_lines+="${GNIZA4CP_CRON_TAG}_stats"$'\n'
    new_lines+="0 5 * * * /usr/local/bin/gniza4cp stats >> /var/log/gniza4cp/cron-stats.log 2>&1"$'\n'

    # Append new lines
    local final="${filtered}${new_lines}"

    # Install
    echo "$final" | crontab - || {
        log_error "Failed to install crontab"
        return 1
    }

    echo "Installed $count schedule(s):"
    echo ""

    # Show what was installed
    while IFS= read -r sname; do
        [[ -z "$sname" ]] && continue
        load_schedule "$sname" 2>/dev/null || continue
        [[ -z "${SCHEDULE:-}" ]] && continue
        local cron_line; cron_line=$(build_cron_line "$sname" 2>/dev/null) || continue
        echo "  [$sname] $cron_line"
    done <<< "$schedules"
}

# Display current gniza4cp cron entries.
show_schedules() {
    local current_crontab=""
    current_crontab=$(crontab -l 2>/dev/null) || true

    if [[ -z "$current_crontab" ]]; then
        echo "No crontab entries found."
        return 0
    fi

    local found=false
    local next_is_command=false
    local current_tag=""
    while IFS= read -r line; do
        if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
            current_tag="${line#"$GNIZA4CP_CRON_TAG"}"
            next_is_command=true
            continue
        fi
        if [[ "$next_is_command" == "true" ]]; then
            next_is_command=false
            if [[ "$found" == "false" ]]; then
                echo "Current gniza4cp schedules:"
                echo ""
                found=true
            fi
            echo "  [$current_tag] $line"
        fi
    done <<< "$current_crontab"

    if [[ "$found" == "false" ]]; then
        echo "No gniza4cp schedule entries in crontab."
    fi
}

# Remove all gniza4cp cron entries.
remove_schedules() {
    local current_crontab=""
    current_crontab=$(crontab -l 2>/dev/null) || true

    if [[ -z "$current_crontab" ]]; then
        echo "No crontab entries to remove."
        return 0
    fi

    local filtered=""
    local skip_next=false
    local removed=0
    while IFS= read -r line; do
        if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
            skip_next=true
            ((removed++)) || true
            continue
        fi
        if [[ "$skip_next" == "true" ]]; then
            skip_next=false
            continue
        fi
        filtered+="$line"$'\n'
    done <<< "$current_crontab"

    if (( removed == 0 )); then
        echo "No gniza4cp schedule entries found in crontab."
        return 0
    fi

    echo "$filtered" | crontab - || {
        log_error "Failed to update crontab"
        return 1
    }

    echo "Removed $removed gniza4cp schedule(s) from crontab."
}
