#!/bin/bash
# =============================================================================
# kitestacks-watcher.sh
# Runs as a systemd service. Watches configured directories, then on change:
#   1. Pulls latest from Forgejo (avoid conflicts)
#   2. Copies changed files into the correct apps/ or clusters/ folder
#   3. Bumps the version in README.md and the docs/ changelog file
#   4. Commits with a descriptive message
#   5. Pushes to active repo (test first, then main after promote.sh)
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config/settings.conf"
STATE_FILE="/opt/kitestacks-autosync/.active_target"
WORK_DIR="/opt/kitestacks-autosync"

log()  { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') INFO  $*"; }
warn() { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') WARN  $*"; }
err()  { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') ERROR $*" >&2; }

# ── Load config ──────────────────────────────────────────────────────────────
[[ ! -f "$CONFIG_FILE" ]] && { err "Config not found: $CONFIG_FILE"; exit 1; }
source "$CONFIG_FILE"

# ── Helpers ───────────────────────────────────────────────────────────────────
active_target() { cat "$STATE_FILE" 2>/dev/null || echo "test"; }

active_repo() {
    [[ "$(active_target)" == "main" ]] && echo "$MAIN_REPO" || echo "$TEST_REPO"
}

repo_dir() { echo "$WORK_DIR/$(active_repo)"; }

# ── Version management ────────────────────────────────────────────────────────
read_version() {
    local readme="$(repo_dir)/README.md"
    # Look for a line like: <!-- version: 1.3.2 -->  or  **Version:** 1.3.2
    local ver
    ver=$(grep -oP '(?<=version:\s)[\d.]+' "$readme" 2>/dev/null | head -1)
    [[ -z "$ver" ]] && ver=$(grep -oP '\d+\.\d+\.\d+' "$readme" 2>/dev/null | head -1)
    echo "${ver:-$VERSION_SEED}"
}

bump_version() {
    local ver="$1"
    IFS='.' read -ra p <<< "$ver"
    local maj="${p[0]:-1}" min="${p[1]:-3}" pat="${p[2]:-0}"
    case "${VERSION_BUMP:-patch}" in
        major) echo "$((maj+1)).0.0" ;;
        minor) echo "${maj}.$((min+1)).0" ;;
        *)     echo "${maj}.${min}.$((pat+1))" ;;
    esac
}

# ── README.md update ──────────────────────────────────────────────────────────
# Maintains the KiteStacks README.md format with version tracking.
update_readme() {
    local dir="$1" new_ver="$2" old_ver="$3"
    local readme="$dir/README.md"
    local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')"

    # If version comment tag exists, update it; otherwise append one after title
    if grep -q '<!-- version:' "$readme" 2>/dev/null; then
        sed -i "s|<!-- version:.*-->|<!-- version: $new_ver -->|" "$readme"
    else
        # Inject version tag after the first H1 line
        sed -i "0,/^# /{s|^# \(.*\)|# \1\n\n<!-- version: $new_ver -->|}" "$readme"
    fi

    log "README.md version tag: $old_ver → $new_ver"
}

# ── Docs changelog update ─────────────────────────────────────────────────────
# Updates docs/KiteStacks-Homelab-Documentation-v<version>.md
# Creates a new versioned doc file and a CHANGELOG.md entry.
update_docs() {
    local dir="$1" new_ver="$2" changed_files="$3"
    local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')"
    local docs_dir="$dir/docs"
    mkdir -p "$docs_dir"

    # ── New versioned doc file ────────────────────────────────────────────────
    local doc_file="$docs_dir/KiteStacks-Homelab-Documentation-v${new_ver}.md"
    cat > "$doc_file" <<EOF
# KiteStacks Homelab Documentation v${new_ver}

**Version:** ${new_ver}
**Updated:** ${ts}
**Previous:** [v${2%.*}.$(( ${new_ver##*.} - 1 )) docs](KiteStacks-Homelab-Documentation-v${2%.*}.$(( ${new_ver##*.} - 1 )).md)

---

## Change Summary

$(echo "$changed_files" | sed 's/^/- /')

---

## Cluster

| Component | Status |
|-----------|--------|
| K3s | Active |
| FluxCD | Planned |
| Longhorn | Planned |

## Applications

| App | Path |
|-----|------|
| Homepage | apps/homepage/ |
| Kavita | apps/kavita-docker-automation/ |
| Linkding | apps/linkding/ |
| Forgejo | apps/forgejo/ |
| Grafana | apps/grafana/ |
| Prometheus | apps/prometheus/ |
| Authentik | apps/authentik/ |
| Open WebUI | apps/open-webui/ |
| LiteLLM | apps/litellm/ |

## All Documentation Versions

$(ls "$docs_dir"/KiteStacks-Homelab-Documentation-v*.md 2>/dev/null \
    | sort -V \
    | while read f; do
        v=$(basename "$f" .md | grep -oP '[\d.]+$')
        echo "- [v${v}]($(basename "$f"))"
      done)
EOF

    log "Created doc file: $(basename "$doc_file")"

    # ── CHANGELOG.md ─────────────────────────────────────────────────────────
    local changelog="$dir/CHANGELOG.md"
    if [[ ! -f "$changelog" ]]; then
        echo -e "# Changelog\n\nAll notable changes to KiteStacks Homelab are documented here.\n" > "$changelog"
    fi

    # Prepend new entry after the header (after line 3)
    local entry
    entry=$(cat <<EOF

## [v${new_ver}] — ${ts}

### Changed
$(echo "$changed_files" | sed 's/^/- /')

EOF
)
    # Insert after line 3 of the changelog
    local tmp; tmp=$(mktemp)
    head -3 "$changelog" > "$tmp"
    echo "$entry" >> "$tmp"
    tail -n +4 "$changelog" >> "$tmp"
    mv "$tmp" "$changelog"

    log "CHANGELOG.md updated with v${new_ver}"
}

# ── Map a filesystem path to a repo subfolder ─────────────────────────────────
# Files from ~/docker/homepage → apps/homepage/
# Files from ~/docker/        → apps/<dirname>/
# Files from /etc/kitestacks/ → clusters/assassin/
map_to_repo_path() {
    local src="$1"
    local rel=""

    for watch in $WATCH_DIRS; do
        if [[ "$src" == "$watch"* ]]; then
            rel="${src#$watch/}"
            # Top-level dir under watch becomes the app folder
            local top; top=$(echo "$rel" | cut -d'/' -f1)
            if [[ "$watch" == *docker* ]]; then
                echo "apps/${rel}"
            else
                echo "clusters/assassin/${rel}"
            fi
            return
        fi
    done
    # Fallback
    echo "server-files/${src#/}"
}

# ── Sync changed files into the workspace ─────────────────────────────────────
sync_files() {
    local repo="$1"; shift
    local files=("$@")
    local synced=()

    for src in "${files[@]}"; do
        local dest_rel; dest_rel="$(map_to_repo_path "$src")"
        local dest="$repo/$dest_rel"
        mkdir -p "$(dirname "$dest")"

        if [[ -f "$src" ]]; then
            cp -p "$src" "$dest"
            synced+=("$dest_rel")
            log "Synced: $dest_rel"
        else
            rm -f "$dest"
            synced+=("DELETED: $dest_rel")
            log "Removed: $dest_rel"
        fi
    done

    printf '%s\n' "${synced[@]}"
}

# ── Commit and push ───────────────────────────────────────────────────────────
commit_and_push() {
    local dir="$1" version="$2" file_count="$3"
    cd "$dir"

    git add -A

    if git diff --cached --quiet; then
        log "Nothing to commit."
        return 0
    fi

    local target; target="$(active_target)"
    local msg="Automated update: $(date '+%Y-%m-%d %H:%M:%S')"
    # Match the commit style already in your repo: "Automated update: YYYY-MM-DD HH:MM:SS"

    git commit -m "$msg"

    log "Pushing to ${target} repo ($(active_repo))..."
    if git push origin HEAD 2>&1; then
        log "✓ Push OK — v${version}"
    else
        err "Push failed. Will retry on next change."
        return 1
    fi
}

# ── Should this path be skipped? ─────────────────────────────────────────────
is_excluded() {
    local path="$1"
    for pat in $EXCLUDE_PATTERNS; do
        case "$path" in $pat) return 0 ;; esac
    done
    return 1
}

# ════════════════════════════════════════════════════════════════════════════
# MAIN
# ════════════════════════════════════════════════════════════════════════════
mkdir -p "$WORK_DIR"
log "KiteStacks AutoSync starting up"
log "Active target : $(active_target) → $(active_repo)"
log "Watching      : $WATCH_DIRS"
log "Debounce      : ${DEBOUNCE_SECONDS}s"

# Pull latest before we start watching
RDIR="$(repo_dir)"
if [[ -d "$RDIR/.git" ]]; then
    cd "$RDIR"
    git pull --rebase origin HEAD 2>/dev/null && log "Pulled latest from remote." || warn "Pull failed — continuing anyway."
fi

declare -A PENDING
LAST_EVENT=0

# Build the inotifywait argument list from WATCH_DIRS
IFS=' ' read -ra WATCH_ARRAY <<< "$WATCH_DIRS"

inotifywait -m -r \
    -e modify,create,delete,moved_to,moved_from \
    --format '%w%f' \
    --exclude '\.git' \
    "${WATCH_ARRAY[@]}" 2>/dev/null |

while IFS= read -r changed; do

    is_excluded "$changed" && continue
    PENDING["$changed"]=1
    LAST_EVENT=$(date +%s)

    # ── Debounce loop ─────────────────────────────────────────────────────────
    while true; do
        sleep 1
        ELAPSED=$(( $(date +%s) - LAST_EVENT ))
        [[ $ELAPSED -ge ${DEBOUNCE_SECONDS:-15} ]] && break

        # Drain any additional events that arrived during sleep
        while IFS= read -r -t 0.1 extra; do
            is_excluded "$extra" && continue
            PENDING["$extra"]=1
            LAST_EVENT=$(date +%s)
        done
    done

    [[ ${#PENDING[@]} -eq 0 ]] && continue

    log "━━ Batch of ${#PENDING[@]} change(s) detected ━━"

    RDIR="$(repo_dir)"
    OLD_VER="$(read_version)"
    NEW_VER="$(bump_version "$OLD_VER")"

    # Pull before committing
    cd "$RDIR"
    git pull --rebase origin HEAD 2>/dev/null || warn "Pre-commit pull failed."

    # Sync files and capture the list of repo-relative paths
    CHANGED_LIST="$(sync_files "$RDIR" "${!PENDING[@]}")"

    update_readme "$RDIR" "$NEW_VER" "$OLD_VER"
    update_docs   "$RDIR" "$NEW_VER" "$CHANGED_LIST"
    commit_and_push "$RDIR" "$NEW_VER" "${#PENDING[@]}"

    # Clear batch
    unset PENDING
    declare -A PENDING

done
