#!/usr/bin/env bash
# mn - My Notes
# A zero-dependency CLI tool to sync markdown notes via a WebDAV endpoint.
# Author: Barney Matthews (Modified for generic WebDAV support). License: MIT

NOTES_DIR="$HOME/mn"
CONFIG_FILE="$NOTES_DIR/mn.conf"

mkdir -p "$NOTES_DIR"

for cmd in curl grep sed awk find tar; do
    command -v "$cmd" >/dev/null 2>&1 || { echo "Error: '$cmd' is required but not installed." >&2; exit 1; }
done

# --- Config ---
if [ -f "$CONFIG_FILE" ]; then
    chmod 600 "$CONFIG_FILE"
    while IFS='=' read -r key value; do
        key=$(echo "$key" | tr -d ' ')
        [ -z "$key" ] && continue
        case "$key" in \#*) continue ;; esac

        value=$(echo "$value" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/[[:space:]]*#.*//')
        case "$value" in \'*\'|\"*\" ) value=$(echo "$value" | sed 's/^.\(.*\).$/\1/') ;; esac

        case "$key" in
            MN_USER) MN_USER="$value" ;;
            MN_PASS) MN_PASS="$value" ;;
            MN_PATH) MN_PATH="$value" ;;
            MN_URL)  MN_URL="$value" ;;
        esac
    done < "$CONFIG_FILE"
fi

# --- First-run / reconfigure setup ---
if [ -z "$MN_USER" ] || [ -z "$MN_PASS" ] || [ -z "$MN_URL" ]; then
    echo "No config found at $CONFIG_FILE - let's set one up."
    echo "Select your provider:"
    echo "1) Koofr"
    echo "2) Custom WebDAV Server"
    printf "Choice [1-2]: "
    read -r provider_choice

    case "$provider_choice" in
        2)
            # Custom WebDAV Setup
            printf "WebDAV Server URL (e.g., https://example.com/remote.php/dav/files/user/): "
            read -r MN_URL
            printf "Username: "
            read -r MN_USER
            printf "App Password (input hidden): "
            stty -echo; read -r MN_PASS; stty echo; echo
            printf "Remote notes folder inside WebDAV [/notes]: "
            read -r MN_PATH
            ;;
        *)
            # Default Koofr Setup
            MN_URL="https://app.koofr.net/dav/Koofr"
            printf "Koofr email/username: "
            read -r MN_USER
            printf "Koofr app password (input hidden): "
            stty -echo; read -r MN_PASS; stty echo; echo
            printf "Remote notes folder [/notes]: "
            read -r MN_PATH
            ;;
    esac

    MN_PATH="${MN_PATH:-/notes}"

    printf "Save this config for future runs? [Y/n] "
    read -r save
    case "$save" in
        [Nn]|[Nn][Oo]) echo "Using credentials for this session only." ;;
        *)
            umask 077
            cat > "$CONFIG_FILE" <<EOF
MN_URL=$MN_URL
MN_USER=$MN_USER
MN_PASS=$MN_PASS
MN_PATH=$MN_PATH
EOF
            chmod 600 "$CONFIG_FILE"
            echo "Saved to $CONFIG_FILE"
            ;;
    esac
fi

MN_URL="${MN_URL%/}" # Strip trailing slash if present
MN_PATH="${MN_PATH:-/notes}"
case "$MN_PATH" in /*) ;; *) MN_PATH="/$MN_PATH" ;; esac
[ "$MN_PATH" = "//" ] && MN_PATH="/"

EDITOR="${EDITOR:-nano}"

# --- Helpers ---
show_help() {
    cat <<EOF
Usage: mn [options] [note]

  -h          Show this help
  -l          List local notes
  -g PATTERN  Search notes (grep)
  -t          Open today's note (YYYY-MM-DD)
  -d NOTE     Delete a note (local + remote)
  -r OLD NEW  Rename a note (local + remote)
  -s          Sync (pull) all remote notes down to local directory
  -b          Backup all local notes to ~/mn-YYYY-MM-DD.tar
  -c          Clear saved credentials and reconfigure

Remote: ${MN_URL}${MN_PATH}
EOF
    exit 0
}

api_curl() {
    local nf rc host
    host=$(echo "$MN_URL" | awk -F/ '{print $3}')
    nf=$(mktemp); chmod 600 "$nf"
    printf 'machine %s\nlogin %s\npassword %s\n' "$host" "$MN_USER" "$MN_PASS" > "$nf"
    curl -s --netrc-file "$nf" "$@"; rc=$?
    rm -f "$nf"; return $rc
}

urlenc() {
    local s="$1" out="" c i
    for (( i=0; i<${#s}; i++ )); do
        c="${s:$i:1}"
        case "$c" in
            [a-zA-Z0-9./_-]) out+="$c" ;;
            *) out+=$(printf '%%%02X' "'$c") ;;
        esac
    done
    echo "$out"
}

urldec() {
    echo "$1" | awk '{
        gsub(/\+/, " ");
        while (match($0, /%[0-9a-fA-F]{2}/)) {
            hex = substr($0, RSTART+1, 2);
            dec = 0;
            for (i=1; i<=2; i++) {
                c = substr(hex, i, 1);
                if (c ~ /[A-F]/) { dec = dec * 16 + (index("ABCDEF", c) + 9) }
                else if (c ~ /[a-f]/) { dec = dec * 16 + (index("abcdef", c) + 9) }
                else { dec = dec * 16 + c }
            }
            printf "%s%c", substr($0, 1, RSTART-1), dec;
            $0 = substr($0, RSTART+RLENGTH);
        }
        print $0;
    }'
}

get_file_hash() {
    if command -v md5sum >/dev/null 2>&1; then md5sum "$1" 2>/dev/null | awk '{print $1}'
    elif command -v shasum >/dev/null 2>&1; then shasum "$1" 2>/dev/null | awk '{print $1}'
    elif command -v md5 >/dev/null 2>&1; then md5 -q "$1" 2>/dev/null || md5 "$1" 2>/dev/null | awk '{print $1}'
    else ls -ln "$1" 2>/dev/null | awk '{print $5,$6,$7,$8}'
    fi
}

remote_url() { echo "${MN_URL}$(urlenc "${MN_PATH}/$1")"; }

remote_mkdir() {
    local dir="$1" path="" part parts target
    if [ -z "$dir" ] || [ "$dir" = "." ]; then target="$MN_PATH"; else target="$MN_PATH/$dir"; fi
    IFS='/' read -ra parts <<< "$target"
    for part in "${parts[@]}"; do
        [ -z "$part" ] && continue
        path="$path/$part"
        api_curl -X MKCOL "${MN_URL}$(urlenc "$path")" -o /dev/null
    done
}

pull_note() {
    local file="$1" url http_code
    url=$(remote_url "$file")
    http_code=$(api_curl -w "%{http_code}" -o "$file" "$url")
    case "$http_code" in
        200) ;;
        404) rm -f "$file" ;;
        *) echo "Error: Pull failed (HTTP $http_code)." >&2; exit 1 ;;
    esac
}

push_note() {
    local file="$1" url http_code dir
    dir=$(dirname "$file")
    remote_mkdir "$dir"
    url=$(remote_url "$file")
    http_code=$(api_curl -w "%{http_code}" -o /dev/null -X PUT --data-binary "@$file" "$url")
    case "$http_code" in
        200|201|204) ;;
        *) echo "Error: Push failed (HTTP $http_code)." >&2; exit 1 ;;
    esac
}

remote_delete() {
    local file="$1" url http_code
    url=$(remote_url "$file")
    http_code=$(api_curl -w "%{http_code}" -o /dev/null -X DELETE "$url")
    case "$http_code" in 200|204|404) ;; *) echo "Warning: Remote delete failed (HTTP $http_code)." >&2 ;; esac
}

remote_rename() {
    local old="$1" new="$2" src dst http_code dir
    dir=$(dirname "$new")
    remote_mkdir "$dir"
    src=$(remote_url "$old")
    dst=$(remote_url "$new")
    http_code=$(api_curl -w "%{http_code}" -o /dev/null -X MOVE -H "Destination: $dst" -H "Overwrite: F" "$src")
    case "$http_code" in 201|204|412|404) ;; *) echo "Warning: Remote rename failed (HTTP $http_code)." >&2 ;; esac
}

list_notes() {
    echo "=== $NOTES_DIR ==="
    if [ -d "$NOTES_DIR" ]; then
        (cd "$NOTES_DIR" && find . -type f ! -name "mn.conf" ! -name "mn.sh" ! -path '*/.*' | sed "s|^\./||" | sort)
    fi
    exit 0
}

search_notes() {
    echo "=== Searching for: '$1' ==="
    grep -Rin "$1" "$NOTES_DIR" --exclude-dir=".git" --exclude="mn.conf" --exclude="mn.sh"
    exit 0
}

delete_note() {
    local file="$1"
    case "$file" in *.md) ;; *) file="${file}.md" ;; esac
    [ -f "$NOTES_DIR/$file" ] || { echo "Error: '$file' not found."; exit 1; }
    printf "Delete '%s'? This cannot be undone. [y/N] " "$file"
    read -r confirm
    case "$confirm" in [Yy]|[Yy][Ee][Ss]) ;; *) echo "Aborted."; exit 0 ;; esac
    remote_delete "$file"
    rm "$NOTES_DIR/$file"
    echo "Deleted '$file'."
    exit 0
}

rename_note() {
    local old="$1" new="$2"
    case "$old" in *.md) ;; *) old="${old}.md" ;; esac
    case "$new" in *.md) ;; *) new="${new}.md" ;; esac
    [ -f "$NOTES_DIR/$old" ] || { echo "Error: '$old' not found."; exit 1; }
    [ -f "$NOTES_DIR/$new" ] && { echo "Error: '$new' already exists."; exit 1; }
    remote_rename "$old" "$new"
    [ "$(dirname "$new")" != "." ] && mkdir -p "$NOTES_DIR/$(dirname "$new")"
    mv "$NOTES_DIR/$old" "$NOTES_DIR/$new"
    echo "Renamed '$old' to '$new'."
    exit 0
}

sync_all_remote() {
    echo "Fetching remote file list..."
    local base_encoded_path xml_response raw_paths path dec_path rel_path dir

    base_encoded_path=$(urlenc "$MN_PATH")
    xml_response=$(api_curl -X PROPFIND -H "Depth: 1" -H "Content-Type: text/xml" "${MN_URL}${base_encoded_path}")

    if [ -z "$xml_response" ]; then
        echo "Error: Could not retrieve remote file list." >&2
        exit 1
    fi

    # Namespace-agnostic regex to split XML href tags smoothly on varying WebDAV backends
    raw_paths=$(echo "$xml_response" | tr -d '\n\r' | sed -E 's/<\/[^>]*href>//g' | sed -E 's/<[^>]*href>/\n/g' | grep -v '^[[:space:]]*$')

    echo "$raw_paths" | while IFS= read -r path; do
        [ -z "$path" ] && continue
        dec_path=$(urldec "$path")

        case "$dec_path" in
            *"$MN_PATH"*.*.md)
                rel_path=$(echo "$dec_path" | sed "s|.*$MN_PATH||" | sed 's|^/||')
                echo "Syncing: $rel_path"
                dir=$(dirname "$rel_path")
                [ "$dir" != "." ] && mkdir -p "$NOTES_DIR/$dir"
                (cd "$NOTES_DIR" && pull_note "$rel_path")
                ;;
        esac
    done
    echo "Sync complete."
    exit 0
}

backup_local_notes() {
    local backup_file
    backup_file="$HOME/mn-$(date '+%Y-%m-%d').tar"
    echo "Creating backup at $backup_file..."

    if (cd "$NOTES_DIR" && find . -type f ! -name "mn.conf" ! -name "mn.sh" ! -path '*/.*' | tar -cf "$backup_file" -T -); then
        echo "Backup created successfully."
    else
        echo "Error: Backup failed." >&2
        exit 1
    fi
    exit 0
}

reconfigure() {
    rm -f "$CONFIG_FILE"
    echo "Saved config cleared. Run mn again to set up new credentials."
    exit 0
}

# --- Entry Point ---
if [ "$1" = "-r" ]; then
    [ -z "$2" ] || [ -z "$3" ] && { echo "Usage: mn -r OLD NEW"; exit 1; }
    rename_note "$2" "$3"
fi

ACTION_RUN=0
while getopts "hlg:tcd:sb" opt; do
    case $opt in
        h) show_help ;;
        l) ACTION_RUN=1; list_notes ;;
        g) ACTION_RUN=1; search_notes "$OPTARG" ;;
        t) NOTE_NAME=$(date '+%Y-%m-%d') ;;
        c) ACTION_RUN=1; reconfigure ;;
        d) ACTION_RUN=1; delete_note "$OPTARG" ;;
        s) ACTION_RUN=1; sync_all_remote ;;
        b) ACTION_RUN=1; backup_local_notes ;;
        *) show_help ;;
    esac
done
[ "$ACTION_RUN" -eq 1 ] && exit 0
shift $((OPTIND - 1))

[ -z "$NOTE_NAME" ] && NOTE_NAME="${1:-note}"
if [ "$NOTE_NAME" = "mn.conf" ] || [ "$NOTE_NAME" = "mn.sh" ]; then
    echo "Error: Cannot open runtime files via mn."
    exit 1
fi

case "$NOTE_NAME" in *.md) ;; *) NOTE_NAME="${NOTE_NAME}.md" ;; esac

cd "$NOTES_DIR" || { echo "Error: Cannot access $NOTES_DIR"; exit 1; }
[ "$(dirname "$NOTE_NAME")" != "." ] && mkdir -p "$(dirname "$NOTE_NAME")"

echo "Fetching... [WebDAV]"
pull_note "$NOTE_NAME"

PRE_SHA=$(get_file_hash "$NOTE_NAME")
$EDITOR "$NOTE_NAME"

[ -f "$NOTE_NAME" ] || { echo "Note not saved. Cancelled."; exit 0; }

POST_SHA=$(get_file_hash "$NOTE_NAME")
if [ "$PRE_SHA" = "$POST_SHA" ]; then
    echo "No changes. Sync skipped."
else
    echo "Pushing... [WebDAV]"
    push_note "$NOTE_NAME"
    echo "Done."
fi
