#!/bin/bash
# docker-update-fzf  —  fzf TUI helper for docker-update
#
# Called by docker-update for interactive log browsing and project management.
# Do not invoke directly unless you know what you're doing.
#
# Usage:
#   docker-update-fzf log   <log_dir>
#   docker-update-fzf debug <json_file>

set -uo pipefail

# ── global temp dir (cleaned up on exit) ───────────────────────────────────
_DU_TMPDIR=""

_setup_tmpdir() {
    _DU_TMPDIR=$(mktemp -d /tmp/du-fzf-XXXXXX)
    trap '_cleanup' EXIT INT TERM
}

_cleanup() {
    [[ -n "$_DU_TMPDIR" && -d "$_DU_TMPDIR" ]] && rm -rf "$_DU_TMPDIR"
}

die() {
    printf 'docker-update-fzf: %s\n' "$*" >&2
    exit 1
}

# ── project status (by name, no workdir needed) ────────────────────────────

# Returns "running" or "stopped" for a compose project by name.
# Uses docker ps --filter so it works from any directory.
project_status_by_name() {
    local name="$1"
    local running
    running=$(docker ps -q \
        --filter "label=com.docker.compose.project=${name}" \
        2>/dev/null | wc -l)
    [[ "$running" -gt 0 ]] && echo "running" || echo "stopped"
}

# ── build_log_list  ────────────────────────────────────────────────────────
# Outputs tab-delimited lines:
#   DISPLAY_TEXT \t FULL_LOG_PATH
# for every .log file in log_dir (newest first).
build_log_list() {
    local log_dir="$1"
    python3 - "$log_dir" << 'PYEOF'
import json, os, sys
from pathlib import Path

log_dir = Path(sys.argv[1])
logs = sorted(log_dir.glob("*.log"), key=lambda p: p.stat().st_mtime, reverse=True)

for log in logs:
    # Parse run_id from filename: docker-update-YYYY-MM-DD_HH-MM-SS.log
    stem = log.stem  # docker-update-YYYY-MM-DD_HH-MM-SS
    ts_raw = stem.replace("docker-update-", "", 1)
    # YYYY-MM-DD_HH-MM-SS -> "YYYY-MM-DD  HH:MM:SS"
    if "_" in ts_raw:
        date_part, time_part = ts_raw.split("_", 1)
        time_fmt = time_part.replace("-", ":")
        ts_display = f"{date_part}  {time_fmt}"
    else:
        ts_display = ts_raw

    # Count projects from JSON sidecar if available
    json_path = log.with_suffix(".json")
    count = ""
    if json_path.exists():
        try:
            data = json.loads(json_path.read_text())
            n = len(data.get("projects", []))
            count = f"  ({n} projects)"
        except Exception:
            pass

    display = f"  {ts_display}{count}"
    print(f"{display}\t{log}")
PYEOF
}

# ── build_project_list  ────────────────────────────────────────────────────
# Outputs tab-delimited lines:
#   DISPLAY_TEXT \t NAME \t WORKDIR \t OUTCOME \t IS_GIT
# Section-header lines use NAME=HEADER and empty WORKDIR/OUTCOME/IS_GIT.
build_project_list() {
    local json_file="$1"
    python3 - "$json_file" << 'PYEOF'
import json, subprocess, sys

json_file = sys.argv[1]
with open(json_file) as f:
    data = json.load(f)

# Get currently running projects in one fast call
running_names: set[str] = set()
try:
    r = subprocess.run(
        ["docker", "compose", "ls", "--format", "json"],
        capture_output=True, text=True, timeout=6
    )
    for p in json.loads(r.stdout or "[]"):
        if "running" in p.get("Status", "").lower():
            running_names.add(p.get("Name", ""))
except Exception:
    pass

OUTCOMES: dict[str, tuple[str, str, str]] = {
    "succeeded":      ("✓", "\033[0;32m", "Succeeded"),
    "failed_pull":    ("⚠", "\033[0;33m", "Failed to pull (restarted)"),
    "failed_stop":    ("✗", "\033[0;31m", "Failed to stop"),
    "failed_restart": ("✗", "\033[0;31m", "Failed to restart"),
}
ORDER    = ["succeeded", "failed_pull", "failed_stop", "failed_restart"]
RESET    = "\033[0m"
DIM      = "\033[2m"
BOLD     = "\033[1m"

# Group projects by outcome
groups: dict[str, list] = {o: [] for o in ORDER}
for p in data.get("projects", []):
    outcome = p.get("outcome", "succeeded")
    if outcome in groups:
        groups[outcome].append(p)

for outcome in ORDER:
    items = groups[outcome]
    if not items:
        continue

    icon, color, label = OUTCOMES[outcome]

    # Section header (not selectable — bash loop detects NAME==HEADER)
    bar     = "─" * max(0, 44 - len(label))
    header  = f"  {DIM}── {label} {bar}{RESET}"
    print(f"{header}\tHEADER\t\t\t")

    for p in items:
        name    = p["name"]
        workdir = p.get("workdir", "")
        is_git  = "true" if p.get("is_git", False) else "false"
        dot     = "●" if name in running_names else "○"
        dot_c   = f"{DIM}{dot}{RESET}"
        name_c  = f"{color}{name}{RESET}"
        icon_c  = f"{color}{icon}{RESET}"
        display = f"  {icon_c}  {dot_c}  {name_c}"
        print(f"{display}\t{name}\t{workdir}\t{outcome}\t{is_git}")
PYEOF
}

# ── write_preview_script  ──────────────────────────────────────────────────
# Writes a bash script to $_DU_TMPDIR/preview.sh that fzf calls on hover.
# Uses docker ps --filter label=... so it works from any directory.
write_preview_script() {
    local script="$_DU_TMPDIR/preview.sh"
    cat > "$script" << 'PREVIEW_EOF'
#!/bin/bash
line="$1"
name=$(printf '%s' "$line" | cut -f2)

if [[ "$name" == "HEADER" || -z "$name" ]]; then
    printf '\n  (hover over a project to see its live container logs)\n'
    exit 0
fi

printf '\033[1m── containers: %s ──\033[0m\n' "$name"
docker ps \
    --filter "label=com.docker.compose.project=${name}" \
    --format '  {{.Names}}   {{.Status}}' 2>/dev/null
printf '\n\033[1m── logs (last 40 lines) ──\033[0m\n'

# Grab up to 3 container IDs for this project and tail their logs
containers=$(docker ps -q \
    --filter "label=com.docker.compose.project=${name}" \
    2>/dev/null | head -3)

if [[ -z "$containers" ]]; then
    # Also check stopped containers
    containers=$(docker ps -aq \
        --filter "label=com.docker.compose.project=${name}" \
        2>/dev/null | head -3)
    [[ -z "$containers" ]] && { printf '  (no containers found)\n'; exit 0; }
fi

printf '%s\n' "$containers" | while IFS= read -r cid; do
    cname=$(docker inspect --format '{{.Name}}' "$cid" 2>/dev/null | sed 's|^/||')
    printf '\n\033[2m--- %s ---\033[0m\n' "$cname"
    docker logs --tail=40 --timestamps "$cid" 2>&1
done
PREVIEW_EOF
    chmod +x "$script"
    echo "$script"
}

# ── write_log_preview_script  ──────────────────────────────────────────────
# Receives tab-delimited log-list line ($1) and cats the log file.
write_log_preview_script() {
    local script="$_DU_TMPDIR/log_preview.sh"
    cat > "$script" << 'LOGPREVIEW_EOF'
#!/bin/bash
path=$(printf '%s' "$1" | cut -f2)
if [[ -z "$path" || ! -f "$path" ]]; then
    printf '(no log file found)\n'
    exit 1
fi
cat "$path"
LOGPREVIEW_EOF
    chmod +x "$script"
    echo "$script"
}

# ── show_action_menu  ──────────────────────────────────────────────────────
# Args: name workdir is_git is_debug
# Echoes the chosen action line; returns 1 on Esc.
show_action_menu() {
    local name="$1" workdir="$2" is_git="$3" is_debug="$4"

    local status dot
    status=$(project_status_by_name "$name")
    [[ "$status" == "running" ]] && dot="●" || dot="○"

    local actions=""
    [[ "$status" != "running" ]] && actions+="  ▶  Start\n"
    actions+="  ↺  Update\n"
    [[ "$status" == "running" ]] && actions+="  ■  Stop\n"

    local debug_note=""
    [[ "$is_debug" == "true" ]] && debug_note="  \033[33m[DEBUG — no real changes]\033[0m"

    local selected
    selected=$(printf "%b" "$actions" | \
        fzf \
            --reverse \
            --no-info \
            --ansi \
            --prompt="  Action > " \
            --header="$(printf "  %s  %s%s" "$dot" "$name" "$debug_note")" \
            --bind="esc:abort" \
            < /dev/tty) || return 1

    echo "$selected"
}

# ── execute_action  ────────────────────────────────────────────────────────
# Args: action_line name workdir is_git is_debug
execute_action() {
    local action_line="$1" name="$2" workdir="$3" is_git="$4" is_debug="$5"

    # Extract verb from display line "  ▶  Start" → "Start"
    local verb
    verb=$(echo "$action_line" | awk '{print $NF}')

    printf '\n\033[2m%s\033[0m\n' "$(printf '─%.0s' {1..60})"

    if [[ "$is_debug" == "true" ]]; then
        printf '  \033[33m[DEBUG]\033[0m Would run: \033[1m%s\033[0m on \033[1m%s\033[0m\n\n' \
            "$verb" "$name"
        case "$verb" in
            Start)
                printf '  \033[2mcd %s && docker compose up -d\033[0m\n' "$workdir"
                ;;
            Stop)
                printf '  \033[2mcd %s && docker compose down\033[0m\n' "$workdir"
                ;;
            Update)
                if [[ "$is_git" == "true" ]]; then
                    printf '  \033[2mcd %s && git pull && docker compose up -d --build\033[0m\n' "$workdir"
                else
                    printf '  \033[2mcd %s && docker compose down && docker compose pull && docker compose up -d\033[0m\n' "$workdir"
                fi
                ;;
        esac
    else
        printf '  \033[1m%s\033[0m  →  \033[1m%s\033[0m\n\n' "$name" "$verb"
        case "$verb" in
            Start)
                (cd "$workdir" && docker compose up -d 2>&1) || true
                ;;
            Stop)
                (cd "$workdir" && docker compose down 2>&1) || true
                ;;
            Update)
                if [[ "$is_git" == "true" ]]; then
                    printf '  \033[2m→ git pull\033[0m\n'
                    (cd "$workdir" && git pull 2>&1) || true
                    printf '\n  \033[2m→ docker compose up -d --build\033[0m\n'
                    (cd "$workdir" && docker compose up -d --build 2>&1) || true
                else
                    printf '  \033[2m→ docker compose down\033[0m\n'
                    (cd "$workdir" && docker compose down 2>&1) || true
                    printf '\n  \033[2m→ docker compose pull\033[0m\n'
                    (cd "$workdir" && docker compose pull 2>&1) || true
                    printf '\n  \033[2m→ docker compose up -d\033[0m\n'
                    (cd "$workdir" && docker compose up -d 2>&1) || true
                fi
                ;;
        esac
    fi

    printf '\n\033[2m%s\033[0m\n' "$(printf '─%.0s' {1..60})"
    printf '  Press any key to return…'
    read -r -s -n1 < /dev/tty
    printf '\n'
}

# ── show_project_list  ─────────────────────────────────────────────────────
# Args: json_file is_debug
# Runs the project selection + action loop. Returns when user presses Esc.
show_project_list() {
    local json_file="$1" is_debug="$2"
    local preview_script
    preview_script=$(write_preview_script)

    while true; do
        local list
        list=$(build_project_list "$json_file") || {
            printf 'Failed to build project list\n' >&2
            return 1
        }

        if [[ -z "$list" ]]; then
            printf '\n  No projects found in log.\n'
            printf '  Press any key…'
            read -r -s -n1 < /dev/tty
            return 0
        fi

        local selected
        selected=$(printf '%s\n' "$list" | \
            fzf \
                --reverse \
                --ansi \
                --no-info \
                --delimiter=$'\t' \
                --with-nth=1 \
                --prompt="  Project > " \
                --header=$'  \033[2mEnter: manage   Esc: back\033[0m' \
                --preview="$preview_script {}" \
                --preview-window="right:50%:wrap" \
                --bind="esc:abort" \
                < /dev/tty) || return 0    # Esc → back

        local item_type
        item_type=$(printf '%s' "$selected" | cut -f2)
        # Skip section-header lines
        [[ "$item_type" == "HEADER" ]] && continue

        local name workdir outcome is_git
        name=$(printf    '%s' "$selected" | cut -f2)
        workdir=$(printf '%s' "$selected" | cut -f3)
        outcome=$(printf '%s' "$selected" | cut -f4)
        is_git=$(printf  '%s' "$selected" | cut -f5)

        [[ -z "$name" || -z "$workdir" ]] && continue

        local action
        action=$(show_action_menu "$name" "$workdir" "$is_git" "$is_debug") || continue

        execute_action "$action" "$name" "$workdir" "$is_git" "$is_debug"
        # Loop: refresh list and show again
    done
}

# ── tui_log  ───────────────────────────────────────────────────────────────
tui_log() {
    local log_dir="${1:-/var/log/docker-update}"

    [[ -d "$log_dir" ]] || die "Log directory not found: $log_dir"

    local log_preview_script
    log_preview_script=$(write_log_preview_script)

    while true; do
        local log_list
        log_list=$(build_log_list "$log_dir") || break

        if [[ -z "$log_list" ]]; then
            printf '\n  No log files found in %s\n' "$log_dir"
            break
        fi

        local selected_log_line
        selected_log_line=$(printf '%s\n' "$log_list" | \
            fzf \
                --reverse \
                --ansi \
                --no-info \
                --delimiter=$'\t' \
                --with-nth=1 \
                --prompt="  Log file > " \
                --header=$'  \033[2mEnter: browse projects   Esc: exit\033[0m' \
                --preview="$log_preview_script {}" \
                --preview-window="right:60%:wrap" \
                --bind="esc:abort" \
                < /dev/tty) || break    # Esc → exit

        local log_path
        log_path=$(printf '%s' "$selected_log_line" | cut -f2)
        local json_path="${log_path%.log}.json"

        if [[ ! -f "$json_path" ]]; then
            printf '\n  \033[33mNote:\033[0m No JSON sidecar found for %s\n' \
                "$(basename "$log_path")"
            printf '  (Log was created before JSON sidecars were supported.)\n'
            printf '  Press any key…'
            read -r -s -n1 < /dev/tty
            printf '\n'
            continue
        fi

        # Level 2+: browse projects from this log
        show_project_list "$json_path" "false"
    done

    printf '\n'
}

# ── tui_debug  ─────────────────────────────────────────────────────────────
tui_debug() {
    local json_file="$1"
    [[ -f "$json_file" ]] || die "JSON file not found: $json_file"

    printf '\n'
    printf '  \033[33m┌──────────────────────────────────────────────────┐\033[0m\n'
    printf '  \033[33m│  DEBUG MODE — no real actions will be performed  │\033[0m\n'
    printf '  \033[33m└──────────────────────────────────────────────────┘\033[0m\n'
    printf '\n'
    sleep 1

    show_project_list "$json_file" "true"

    # Clean up temp JSON written by docker-update debug
    rm -f "$json_file"
}

# ── main  ──────────────────────────────────────────────────────────────────
main() {
    local mode="${1:-}"
    shift || true

    _setup_tmpdir

    case "$mode" in
        log)   tui_log   "$@" ;;
        debug) tui_debug "$@" ;;
        *)
            printf 'Usage: docker-update-fzf {log|debug} [args]\n' >&2
            exit 1
            ;;
    esac
}

main "$@"
