#!/usr/bin/env bash

mkdir -p dotfiles/links dotfiles/missing-links dotfiles/copydir
echo "linked" >dotfiles/linked
echo "copied" >dotfiles/copied
echo "one" >dotfiles/links/one
echo "stale" >dotfiles/links/stale
echo "excluded" >dotfiles/links/excluded
echo "missing source" >dotfiles/missing-links/file
echo "managed" >dotfiles/copydir/managed
cat <<'EOF' >dotfiles/rendered.tmpl
{{ exec(command="touch $HOME/unapply-rendered") }}rendered
EOF
cat <<'EOF' >dotfiles/mutating.tmpl
{{ exec(command="printf tampered > $HOME/.managed-copy; if [ -e $HOME/mutate-edit ]; then rm -f $HOME/.managed-rc; ln -s $HOME/edit-victim $HOME/.managed-rc; fi") }}mutating
EOF

cat <<EOF >mise.toml
[dotfiles]
"~/.managed-link" = "dotfiles/linked"
"~/.managed-copy" = { source = "dotfiles/copied", mode = "copy" }
"~/.managed-rendered" = { source = "dotfiles/rendered.tmpl", mode = "template" }
"~/.managed-mutating" = { source = "dotfiles/mutating.tmpl", mode = "template" }
"~/.managed-links" = { source = "dotfiles/links", mode = "symlink-each", exclude = ["excluded"] }
"~/.managed-missing-links" = { source = "dotfiles/missing-links", mode = "symlink-each" }
"~/.managed-copydir" = { source = "dotfiles/copydir", mode = "copy" }
"~/.managed-rc/block" = { block = "managed block" }
"~/.managed-lines/line" = { line = "possibly preexisting" }
EOF

# A line edit cannot prove it created an already-present line.
echo "possibly preexisting" >~/.managed-lines
printf 'before\n\n' >~/.managed-rc
assert_succeed "mise bootstrap dotfiles apply --yes"
assert_succeed "test -L $HOME/.managed-link"
assert_contains "cat ~/.managed-rc" "managed block"

# Template dry-runs stay inert even though verifying the rendered content
# during a real unapply can execute template functions.
rm ~/unapply-rendered
if [[ $(uname) == "Darwin" ]]; then
  assert_contains "script -q /dev/null env -u MISE_YES __USAGE=1 mise bootstrap dotfiles unapply ~/.managed-rendered" "dotfiles: skipped"
else
  assert_contains "script -qec 'env -u MISE_YES __USAGE=1 mise bootstrap dotfiles unapply ~/.managed-rendered' /dev/null" "dotfiles: skipped"
fi
assert_fail "test -e $HOME/unapply-rendered"
assert_succeed "test -e $HOME/.managed-rendered"
assert_contains "mise bootstrap dotfiles unapply ~/.managed-rendered --dry-run" "if unchanged"
# The hidden top-level command remains a compatibility path while it is deprecated.
assert_contains "mise dotfiles unapply ~/.managed-rendered --dry-run" "if unchanged"
assert_fail "test -e $HOME/unapply-rendered"
assert_succeed "test -e $HOME/.managed-rendered"

# Default unapply refuses ambiguous line edits before changing any edit.
assert_fail "mise bootstrap dotfiles unapply --yes" "line edits have no ownership marker"
assert_contains "cat ~/.managed-rc" "managed block"
assert_succeed "test -L $HOME/.managed-link"

# Template functions can change another selected target. Revalidate every file
# after all templates render instead of deleting from a stale plan.
cp dotfiles/copied ~/.managed-copy
assert_fail "mise bootstrap dotfiles unapply ~/.managed-copy ~/.managed-mutating --yes" "use --force"
assert "cat ~/.managed-copy" "tampered"
assert_succeed "test -e $HOME/.managed-mutating"

# The same post-render validation protects edits, including against a template
# replacing an edit target with a symlink to another file.
cp ~/.managed-rc ~/.managed-rc.saved
echo "do not edit" >~/edit-victim
touch ~/mutate-edit
assert_fail "mise bootstrap dotfiles unapply ~/.managed-rc/block ~/.managed-mutating --yes" "target is a symlink"
assert "cat ~/edit-victim" "do not edit"
rm ~/mutate-edit ~/.managed-rc
mv ~/.managed-rc.saved ~/.managed-rc

# File and edit entries are all validated before either kind is removed.
echo "locally modified" >~/.managed-copy
assert_fail "mise bootstrap dotfiles unapply ~/.managed-rc/block ~/.managed-copy --yes" "use --force"
assert_contains "cat ~/.managed-rc" "managed block"

# Exact symlinks and marker-owned blocks can be removed safely.
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-link --yes"
assert_fail "test -e $HOME/.managed-link"

# Removing a block preserves byte-for-byte CRLF, blank lines, and a missing
# final newline in all surrounding content.
perl -0pi -e 's/\n/\r\n/g' ~/.managed-rc
printf 'after-no-newline' >>~/.managed-rc
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-rc/block --yes"
assert_not_contains "cat ~/.managed-rc" "mise:block"
assert "od -An -tx1 ~/.managed-rc | tr -d ' \n'" "6265666f72650d0a0d0a61667465722d6e6f2d6e65776c696e65"

# symlink-each removes path-equivalent and stale exact mappings, but preserves
# unrelated and excluded user-created links and their containing directory.
# A manifest remains sufficient ownership evidence when the complete source
# directory has been deleted.
rm -r dotfiles/missing-links
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-missing-links --yes"
assert_fail "test -L $HOME/.managed-missing-links/file"
mkdir ~/.managed-links/private
chmod 000 ~/.managed-links/private
assert_not_contains "MISE_DEBUG=1 mise bootstrap dotfiles unapply ~/.managed-links --dry-run 2>&1" "$HOME/.managed-links/private"
chmod 700 ~/.managed-links/private
ln -s "$PWD/dotfiles/links/one" ~/.managed-links/alias
ln -s "$PWD/dotfiles/links/excluded" ~/.managed-links/excluded
rm ~/.managed-links/one
relative_one="$(realpath --relative-to="$HOME/.managed-links" "$PWD/dotfiles/links/one")"
ln -s "$relative_one" ~/.managed-links/one
rm dotfiles/links/stale
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-links --yes"
assert_fail "test -e $HOME/.managed-links/one"
assert_fail "test -e $HOME/.managed-links/stale"
assert "readlink ~/.managed-links/alias" "$PWD/dotfiles/links/one"
assert "readlink ~/.managed-links/excluded" "$PWD/dotfiles/links/excluded"
assert "find '$MISE_STATE_DIR/dotfiles' -type f | wc -l | tr -d ' '" "0"

# Modified copies are refused unless forced.
assert_fail "mise bootstrap dotfiles unapply ~/.managed-copy --yes" "use --force"
assert "cat ~/.managed-copy" "locally modified"
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-copy --force --yes"
assert_fail "test -e $HOME/.managed-copy"

# Directory copies are removed file-by-file. Modified managed files need
# --force, and unmanaged neighbors always survive.
echo "locally modified" >~/.managed-copydir/managed
echo "keep me" >~/.managed-copydir/unmanaged
assert_fail "mise bootstrap dotfiles unapply ~/.managed-copydir --yes" "use --force"
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-copydir --force --yes"
assert_fail "test -e $HOME/.managed-copydir/managed"
assert "cat ~/.managed-copydir/unmanaged" "keep me"

# A real template unapply verifies content, then removes it.
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-rendered --yes"
assert_succeed "test -e $HOME/unapply-rendered"
assert_fail "test -e $HOME/.managed-rendered"

# Plain lines require explicit force because they carry no marker.
assert_fail "mise bootstrap dotfiles unapply ~/.managed-lines/line --yes"
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-lines/line --force --yes"
assert_not_contains "cat ~/.managed-lines" "possibly preexisting"

# A repointed declared symlink is left alone unless forced.
ln -s /somewhere-else ~/.managed-link
assert_fail "mise bootstrap dotfiles unapply ~/.managed-link --yes" "use --force"
assert "readlink ~/.managed-link" "/somewhere-else"
assert_succeed "mise bootstrap dotfiles unapply ~/.managed-link --force --yes"
assert_fail "test -L $HOME/.managed-link"

assert_fail "mise bootstrap dotfiles unapply ~/.not-managed --yes" "no dotfiles matched"
