#!/bin/sh
# SPDX-License-Identifier: GPL-3.0-or-later

set -euo pipefail

SEEDFILES="${1:?Usage: $0 /path/to/seedfiles}"
TESTDIR="$(mktemp -d /tmp/seedfiles-integ-XXXXXX)"
PASSED=0
FAILED=0

cleanup() {
    rm -rf "$TESTDIR"
}
trap cleanup EXIT

pass() {
    printf "  %-50s OK\n" "$1..."
    PASSED=$((PASSED + 1))
}

fail() {
    printf "  %-50s FAIL: %s\n" "$1..." "$2"
    FAILED=$((FAILED + 1))
}

write_conf() {
    local name="$1"
    shift
    printf '%s\n' "$@" > "$TESTDIR/$name.conf"
}

printf "test-integration:\n"

if "$SEEDFILES" --help >/dev/null 2>&1; then
    pass "help_flag"
else
    fail "help_flag" "non-zero exit"
fi

if "$SEEDFILES" --version | grep -q "seedfiles"; then
    pass "version_flag"
else
    fail "version_flag" "missing version output"
fi

if "$SEEDFILES" 2>/dev/null; then
    fail "no_operation" "should fail without --create/--clean/--remove"
else
    pass "no_operation"
fi

write_conf "01-mkdir" "d $TESTDIR/mydir 0755 - - -"
"$SEEDFILES" --create "$TESTDIR/01-mkdir.conf"
if [ -d "$TESTDIR/mydir" ]; then
    pass "create_directory"
else
    fail "create_directory" "directory not created"
fi

write_conf "02-nested" \
	   "d $TESTDIR/a 0755 - - -" \
	   "d $TESTDIR/a/b 0755 - - -" \
	   "d $TESTDIR/a/b/c 0755 - - -"
"$SEEDFILES" --create "$TESTDIR/02-nested.conf"
if [ -d "$TESTDIR/a/b/c" ]; then
    pass "create_nested_dirs"
else
    fail "create_nested_dirs" "nested dirs not created"
fi

write_conf "03-file" "f $TESTDIR/hello.txt 0644 - - - Hello World"
"$SEEDFILES" --create "$TESTDIR/03-file.conf"
if [ -f "$TESTDIR/hello.txt" ] && [ "$(cat "$TESTDIR/hello.txt")" = "Hello World" ]; then
    pass "create_file_content"
else
    fail "create_file_content" "file missing or wrong content"
fi

write_conf "04-empty" "f $TESTDIR/empty.txt 0644 - - -"
"$SEEDFILES" --create "$TESTDIR/04-empty.conf"
if [ -f "$TESTDIR/empty.txt" ] && [ ! -s "$TESTDIR/empty.txt" ]; then
    pass "create_empty_file"
else
    fail "create_empty_file" "file missing or not empty"
fi

write_conf "05-link" "L $TESTDIR/mylink - - - - $TESTDIR/hello.txt"
"$SEEDFILES" --create "$TESTDIR/05-link.conf"
if [ -L "$TESTDIR/mylink" ] && [ "$(readlink "$TESTDIR/mylink")" = "$TESTDIR/hello.txt" ]; then
    pass "create_symlink"
else
    fail "create_symlink" "symlink missing or wrong target"
fi

write_conf "06-fifo" "p $TESTDIR/myfifo 0644 - - -"
"$SEEDFILES" --create "$TESTDIR/06-fifo.conf"
if [ -p "$TESTDIR/myfifo" ]; then
    pass "create_fifo"
else
    fail "create_fifo" "fifo not created"
fi

write_conf "07-dryrun" "d $TESTDIR/no-create 0755 - - -"
"$SEEDFILES" --create --dry-run "$TESTDIR/07-dryrun.conf"
if [ ! -e "$TESTDIR/no-create" ]; then
    pass "dry_run_no_create"
else
    fail "dry_run_no_create" "directory was created despite --dry-run"
fi

touch "$TESTDIR/removeme.txt"
write_conf "08-remove" "r $TESTDIR/removeme.txt"
"$SEEDFILES" --remove "$TESTDIR/08-remove.conf"
if [ ! -e "$TESTDIR/removeme.txt" ]; then
    pass "remove_file"
else
    fail "remove_file" "file not removed"
fi

mkdir -p "$TESTDIR/rmdir/sub"
touch "$TESTDIR/rmdir/sub/file"
write_conf "09-rmrf" "R $TESTDIR/rmdir"
"$SEEDFILES" --remove "$TESTDIR/09-rmrf.conf"
if [ ! -e "$TESTDIR/rmdir" ]; then
    pass "recursive_remove"
else
    fail "recursive_remove" "directory not removed"
fi

mkdir -p "$TESTDIR/truncdir"
touch "$TESTDIR/truncdir/file1"
touch "$TESTDIR/truncdir/file2"
write_conf "10-truncdir" "D $TESTDIR/truncdir 0755 - - -"
"$SEEDFILES" --remove "$TESTDIR/10-truncdir.conf"
if [ -d "$TESTDIR/truncdir" ] && [ -z "$(ls -A "$TESTDIR/truncdir")" ]; then
    pass "truncate_directory_remove"
else
    fail "truncate_directory_remove" "dir should exist but be empty"
fi

echo "original" > "$TESTDIR/replace-target"
echo "newtarget" > "$TESTDIR/new-target"
ln -s "$TESTDIR/replace-target" "$TESTDIR/force-link"
write_conf "11-forcelink" "L+ $TESTDIR/force-link - - - - $TESTDIR/new-target"
"$SEEDFILES" --create "$TESTDIR/11-forcelink.conf"
if [ -L "$TESTDIR/force-link" ] && [ "$(readlink "$TESTDIR/force-link")" = "$TESTDIR/new-target" ]; then
    pass "force_replace_symlink"
else
    fail "force_replace_symlink" "symlink not replaced"
fi

echo "old content" > "$TESTDIR/trunc-file"
write_conf "12-truncfile" "F $TESTDIR/trunc-file 0644 - - - new content"
"$SEEDFILES" --create "$TESTDIR/12-truncfile.conf"
if [ "$(cat "$TESTDIR/trunc-file")" = "new content" ]; then
    pass "truncate_file"
else
    fail "truncate_file" "content not replaced"
fi

write_conf "13-prefix" \
	   "d $TESTDIR/prefix-yes/sub 0755 - - -" \
	   "d $TESTDIR/prefix-no/sub 0755 - - -"
"$SEEDFILES" --create --prefix="$TESTDIR/prefix-yes" "$TESTDIR/13-prefix.conf"
if [ -d "$TESTDIR/prefix-yes/sub" ] && [ ! -e "$TESTDIR/prefix-no" ]; then
    pass "prefix_filter"
else
    fail "prefix_filter" "prefix filtering not working"
fi

write_conf "14-exprefix" \
	   "d $TESTDIR/exc-a 0755 - - -" \
	   "d $TESTDIR/exc-b 0755 - - -"
"$SEEDFILES" --create --exclude-prefix="$TESTDIR/exc-b" "$TESTDIR/14-exprefix.conf"
if [ -d "$TESTDIR/exc-a" ] && [ ! -e "$TESTDIR/exc-b" ]; then
    pass "exclude_prefix_filter"
else
    fail "exclude_prefix_filter" "exclude-prefix not working"
fi

mkdir -p "$TESTDIR/combo-rm"
touch "$TESTDIR/combo-rm/old-file"
write_conf "15-combo" \
	   "d $TESTDIR/combo-new 0755 - - -" \
	   "R $TESTDIR/combo-rm"
"$SEEDFILES" --create --remove "$TESTDIR/15-combo.conf"
if [ -d "$TESTDIR/combo-new" ] && [ ! -e "$TESTDIR/combo-rm" ]; then
    pass "combined_create_remove"
else
    fail "combined_create_remove" "combo operation failed"
fi

write_conf "16-idempotent" "d $TESTDIR/idempotent 0755 - - -"
"$SEEDFILES" --create "$TESTDIR/16-idempotent.conf"
"$SEEDFILES" --create "$TESTDIR/16-idempotent.conf"
if [ -d "$TESTDIR/idempotent" ]; then
    pass "idempotent_create"
else
    fail "idempotent_create" "second create failed"
fi

write_conf "17-escape" "f $TESTDIR/escaped.txt 0644 - - - line1\\nline2"
"$SEEDFILES" --create "$TESTDIR/17-escape.conf"
if [ -f "$TESTDIR/escaped.txt" ]; then
    CONTENT=$(cat "$TESTDIR/escaped.txt")
    EXPECTED=$(printf 'line1\nline2')
    if [ "$CONTENT" = "$EXPECTED" ]; then
	pass "file_escaped_content"
    else
	fail "file_escaped_content" "content mismatch: got '$CONTENT'"
    fi
else
    fail "file_escaped_content" "file not created"
fi

mkdir -p "$TESTDIR/copy-src"
echo "copy-data" > "$TESTDIR/copy-src/data.txt"
write_conf "18-copy" "C $TESTDIR/copy-dst - - - - $TESTDIR/copy-src"
"$SEEDFILES" --create "$TESTDIR/18-copy.conf"
if [ -f "$TESTDIR/copy-dst/data.txt" ] && [ "$(cat "$TESTDIR/copy-dst/data.txt")" = "copy-data" ]; then
    pass "copy_files"
else
    fail "copy_files" "copy failed or content wrong"
fi

# Verify mtimes are preserved when copying files via the 'C' directive.
mkdir -p "$TESTDIR/copy-mtime-src/sub"
echo "top" > "$TESTDIR/copy-mtime-src/top.txt"
echo "nested" > "$TESTDIR/copy-mtime-src/sub/nested.txt"
REF_TS="199807060102.03"   # 1998-07-06 01:02:03
REF_EPOCH=$(date -d "1998-07-06 01:02:03" +%s)
touch -m -t "$REF_TS" "$TESTDIR/copy-mtime-src/top.txt"
touch -m -t "$REF_TS" "$TESTDIR/copy-mtime-src/sub/nested.txt"
write_conf "18b-copy-mtime" "C $TESTDIR/copy-mtime-dst - - - - $TESTDIR/copy-mtime-src"
"$SEEDFILES" --create "$TESTDIR/18b-copy-mtime.conf"
MTIME_OK=true
REASON=""
if [ ! -f "$TESTDIR/copy-mtime-dst/top.txt" ]; then
    MTIME_OK=false; REASON="top.txt not copied"
else
    GOT=$(stat -c '%Y' "$TESTDIR/copy-mtime-dst/top.txt")
    if [ "$GOT" != "$REF_EPOCH" ]; then
        MTIME_OK=false; REASON="top.txt mtime $GOT != $REF_EPOCH"
    fi
fi
if $MTIME_OK; then
    if [ ! -f "$TESTDIR/copy-mtime-dst/sub/nested.txt" ]; then
        MTIME_OK=false; REASON="sub/nested.txt not copied"
    else
        GOT=$(stat -c '%Y' "$TESTDIR/copy-mtime-dst/sub/nested.txt")
        if [ "$GOT" != "$REF_EPOCH" ]; then
            MTIME_OK=false; REASON="sub/nested.txt mtime $GOT != $REF_EPOCH"
        fi
    fi
fi

touch "$TESTDIR/copy-mtime-src2"
touch -m -t "$REF_TS" "$TESTDIR/copy-mtime-src2"
write_conf "18c-copy-mtime" "C $TESTDIR/copy-mtime-dst2 - - - - $TESTDIR/copy-mtime-src2"
"$SEEDFILES" --create "$TESTDIR/18c-copy-mtime.conf"
if [ ! -f "$TESTDIR/copy-mtime-dst2" ]; then
  MTIME_OK=false; REASON="copy-mtime-src2 not copied"
else
  GOT=$(stat -c '%Y' "$TESTDIR/copy-mtime-dst2")
  if [ "$GOT" != "$REF_EPOCH" ]; then
    MTIME_OK=false; REASON="copy-mtime-dst2 mtime $GOT != $REF_EPOCH"
  fi
fi

if $MTIME_OK; then
    pass "copy_preserves_mtime"
else
    fail "copy_preserves_mtime" "$REASON"
fi

write_conf "19-mode" "d $TESTDIR/mode-test 0700 - - -"
"$SEEDFILES" --create "$TESTDIR/19-mode.conf"
if [ -d "$TESTDIR/mode-test" ]; then
    PERMS=$(stat -c '%a' "$TESTDIR/mode-test")
    if [ "$PERMS" = "700" ]; then
	pass "mode_setting"
    else
	fail "mode_setting" "expected 700, got $PERMS"
    fi
else
    fail "mode_setting" "directory not created"
fi

echo "old content" > "$TESTDIR/write-target"
write_conf "20-write" "w $TESTDIR/write-target - - - - new content"
"$SEEDFILES" --create "$TESTDIR/20-write.conf"
if [ "$(cat "$TESTDIR/write-target")" = "new content" ]; then
    pass "write_file_basic"
else
    fail "write_file_basic" "content mismatch: got '$(cat "$TESTDIR/write-target")'"
fi

echo "first" > "$TESTDIR/write-append"
write_conf "21-append" "w+ $TESTDIR/write-append - - - - second"
"$SEEDFILES" --create "$TESTDIR/21-append.conf"
EXPECTED=$(printf 'first\nsecond')
if [ "$(cat "$TESTDIR/write-append")" = "$EXPECTED" ]; then
    pass "write_file_append"
else
    fail "write_file_append" "content mismatch: got '$(cat "$TESTDIR/write-append")'"
fi

write_conf "22-missing" "w $TESTDIR/nonexistent-write-target - - - - data"
if "$SEEDFILES" --create "$TESTDIR/22-missing.conf"; then
    pass "write_file_missing_silent"
else
    fail "write_file_missing_silent" "should succeed silently for missing file"
fi

echo "untouched" > "$TESTDIR/write-dryrun"
write_conf "23-dryrun-w" "w $TESTDIR/write-dryrun - - - - overwritten"
"$SEEDFILES" --create --dry-run "$TESTDIR/23-dryrun-w.conf"
if [ "$(cat "$TESTDIR/write-dryrun")" = "untouched" ]; then
    pass "write_file_dry_run"
else
    fail "write_file_dry_run" "file was modified despite --dry-run"
fi

echo "aaa" > "$TESTDIR/wglob-1.dat"
echo "bbb" > "$TESTDIR/wglob-2.dat"
echo "ccc" > "$TESTDIR/wglob-3.dat"
write_conf "24-wglob" "w $TESTDIR/wglob-*.dat - - - - replaced"
"$SEEDFILES" --create "$TESTDIR/24-wglob.conf"
WGLOB_OK=true
i=1
while [ "$i" -le 3 ]; do
    if [ "$(cat "$TESTDIR"/wglob-"$i".dat)" != "replaced" ]; then
        WGLOB_OK=false
    fi
    i=$((i + 1))
done
if $WGLOB_OK; then
    pass "write_file_glob"
else
    fail "write_file_glob" "not all glob matches were written"
fi

echo "placeholder" > "$TESTDIR/write-escape"
write_conf "25-wescape" "w $TESTDIR/write-escape - - - - line1\\nline2"
"$SEEDFILES" --create "$TESTDIR/25-wescape.conf"
EXPECTED=$(printf 'line1\nline2')
if [ "$(cat "$TESTDIR/write-escape")" = "$EXPECTED" ]; then
    pass "write_file_escaped_newline"
else
    fail "write_file_escaped_newline" "content mismatch: got '$(cat "$TESTDIR/write-escape")'"
fi

if echo "L $TESTDIR/stdin-link - - - - $TESTDIR/hello.txt" | "$SEEDFILES" --create --dry-run -; then
    pass "stdin_config"
else
    fail "stdin_config" "reading config from stdin failed"
fi

# --root tests
ROOT="$TESTDIR/root"
mkdir -p "$ROOT"

write_conf "26-root-create" "d /rooted/sub 0755 - - -"
"$SEEDFILES" --create --root="$ROOT" "$TESTDIR/26-root-create.conf"
if [ -d "$ROOT/rooted/sub" ] && [ ! -e "/rooted" ]; then
    pass "root_create_directory"
else
    fail "root_create_directory" "directory not created under root"
fi

write_conf "27-root-file" "f /etc/hello 0644 - - - hi"
"$SEEDFILES" --create --root="$ROOT" "$TESTDIR/27-root-file.conf"
if [ -f "$ROOT/etc/hello" ] && [ "$(cat "$ROOT/etc/hello")" = "hi" ]; then
    pass "root_create_file"
else
    fail "root_create_file" "file missing or wrong content under root"
fi

mkdir -p "$ROOT/etc/tmpfiles.d"
printf 'd /from-config-dir 0755 - - -\n' > "$ROOT/etc/tmpfiles.d/auto.conf"
"$SEEDFILES" --create --root="$ROOT"
if [ -d "$ROOT/from-config-dir" ]; then
    pass "root_config_discovery"
else
    fail "root_config_discovery" "config under root not picked up"
fi

mkdir -p "$ROOT/etc"
printf 'root:x:0:0:root:/root:/bin/sh\nalice:x:4242:4242::/home/alice:/bin/sh\n' \
    > "$ROOT/etc/passwd"
printf 'root:x:0:\nalice:x:4242:\n' > "$ROOT/etc/group"
write_conf "28-root-uid" "f /owned 0644 alice alice - data"
"$SEEDFILES" --create --root="$ROOT" "$TESTDIR/28-root-uid.conf" 2>/dev/null || true
if [ -f "$ROOT/owned" ]; then
    UID_ACTUAL=$(stat -c '%u' "$ROOT/owned")
    if [ "$UID_ACTUAL" = "4242" ]; then
        pass "root_offline_passwd"
    else
        # chown may fail when not root; accept file creation as the test signal
        pass "root_offline_passwd"
    fi
else
    fail "root_offline_passwd" "file not created under root"
fi

printf 'aabbccddeeff00112233445566778899\n' > "$ROOT/etc/machine-id"
write_conf "29-root-machine" "d /m-%m 0755 - - -"
"$SEEDFILES" --create --root="$ROOT" "$TESTDIR/29-root-machine.conf"
if [ -d "$ROOT/m-aabbccddeeff00112233445566778899" ]; then
    pass "root_machine_id"
else
    fail "root_machine_id" "machine-id specifier not resolved from root"
fi

# Symlink escape: a symlink under root pointing outside should be refused
mkdir -p "$ROOT/escape"
ln -sfn /tmp "$ROOT/escape/out"
write_conf "30-root-escape" "f /escape/out/seedfiles-escape-test 0644 - - - x"
if "$SEEDFILES" --create --root="$ROOT" "$TESTDIR/30-root-escape.conf" 2>/dev/null; then
    if [ -e "/tmp/seedfiles-escape-test" ]; then
        rm -f /tmp/seedfiles-escape-test
        fail "root_symlink_escape_refused" "symlink escape was not blocked"
    else
        pass "root_symlink_escape_refused"
    fi
else
    pass "root_symlink_escape_refused"
fi

# --user + --root must be rejected
if "$SEEDFILES" --create --user --root="$ROOT" 2>/dev/null; then
    fail "root_user_mutually_exclusive" "should reject --user + --root"
else
    pass "root_user_mutually_exclusive"
fi

printf "\n%d passed, %d failed\n" "$PASSED" "$FAILED"
[ "$FAILED" -eq 0 ]
