#!/usr/bin/env bash

source_repo="$PWD/bootstrap-source"
checkout="$PWD/bootstrap-checkout"
dry_checkout="$PWD/bootstrap-dry-checkout"
global_source_repo="$PWD/bootstrap-global-source"
relative_global_source_repo="$PWD/bootstrap-relative-global-source"
bare_global_checkout="$PWD/bootstrap-bare-global-checkout"

# The removed --from-git spelling is gone from help, completions, and parsing.
for command in "bootstrap" "bootstrap remote"; do
  assert_contains "mise $command --help" "--adopt"
  assert_not_contains "mise $command --help" "--from-git"
  assert_contains "mise __complete_word__ --shell fish --line 'mise $command --'" "--adopt"
  assert_not_contains "mise __complete_word__ --shell fish --line 'mise $command --'" "--from-git"
done
assert_fail "mise bootstrap --from-git old.git --dry-run" "unexpected argument '--from-git' found"
assert_fail "mise bootstrap remote --host devbox --from-git old.git --dry-run" "unexpected argument '--from-git' found"

mkdir -p "$source_repo"
git -C "$source_repo" init -q -b main
git -C "$source_repo" config user.email test@example.com
git -C "$source_repo" config user.name "mise test"

cat <<'EOF' >"$source_repo/mise.work.toml"
[dotfiles]
"~/.bootstrap-from-profile" = "profile"

[tasks.bootstrap]
run = 'printf work-v1 > "$HOME/bootstrap-from-task"'

[bootstrap.hooks.final]
run = 'touch "$HOME/bootstrap-from-hook"'
EOF
echo work-v1 >"$source_repo/profile"
git -C "$source_repo" add .
git -C "$source_repo" commit -qm initial

mkdir -p "$global_source_repo"
git -C "$global_source_repo" init -q -b main
git -C "$global_source_repo" config user.email test@example.com
git -C "$global_source_repo" config user.name "mise test"
cat <<'EOF' >"$global_source_repo/config.work.toml"
[dotfiles]
"~/.bootstrap-from-global" = "global-profile"

[tasks.bootstrap]
run = 'printf global > "$HOME/bootstrap-from-global-task"'
EOF
echo global >"$global_source_repo/global-profile"
git -C "$global_source_repo" add .
git -C "$global_source_repo" commit -qm initial

# --adopt checks out a global config repository directly into the mise
# config directory, so its config participates in the first bootstrap and
# remains the global config for later invocations.
assert_succeed "mise -E work bootstrap --adopt '$global_source_repo' --yes --only dotfiles,task"
assert "git -C '$MISE_CONFIG_DIR' remote get-url origin" "$global_source_repo"
assert "cat ~/.bootstrap-from-global" "global"
assert "cat ~/bootstrap-from-global-task" "global"
assert_contains "mise -E work config ls" "~/.config/mise/config.work.toml"

assert_not_contains "mise -E work bootstrap --adopt='$global_source_repo' --yes --only dotfiles 2>&1" 'deprecated'

mkdir -p "$relative_global_source_repo"
git -C "$relative_global_source_repo" init -q -b main
git -C "$relative_global_source_repo" config user.email test@example.com
git -C "$relative_global_source_repo" config user.name "mise test"
cat <<'EOF' >"$relative_global_source_repo/config.toml"
[tasks.bootstrap]
run = 'printf relative-global > "$HOME/bootstrap-from-relative-global-task"'
EOF
git -C "$relative_global_source_repo" add .
git -C "$relative_global_source_repo" commit -qm initial

# A relative MISE_GLOBAL_CONFIG_FILE is resolved before the child changes into
# the checkout and remains the selected global config during re-execution.
rm -f "$HOME/bootstrap-from-relative-global-task"
assert_succeed "MISE_GLOBAL_CONFIG_FILE=relative-global/config.toml mise bootstrap --adopt '$relative_global_source_repo' --yes --only task"
assert "cat ~/bootstrap-from-relative-global-task" "relative-global"

# A relative MISE_CONFIG_DIR is resolved before the child changes into the
# checkout and remains the config directory during re-execution.
relative_config_checkout="bootstrap-relative-config-checkout"
rm -f "$HOME/bootstrap-from-relative-global-task"
assert_succeed "MISE_CONFIG_DIR='$relative_config_checkout' mise bootstrap --adopt '$relative_global_source_repo' --yes --only task"
assert "git -C '$relative_config_checkout' remote get-url origin" "$relative_global_source_repo"
assert "cat ~/bootstrap-from-relative-global-task" "relative-global"

# A bare relative MISE_GLOBAL_CONFIG_FILE checks out into the current directory
# rather than silently falling back to the default global config directory.
mkdir "$bare_global_checkout"
rm -f "$HOME/bootstrap-from-relative-global-task"
assert_succeed "cd '$bare_global_checkout' && MISE_GLOBAL_CONFIG_FILE=config.toml mise bootstrap --adopt '$relative_global_source_repo' --yes --only task"
assert "git -C '$bare_global_checkout' remote get-url origin" "$relative_global_source_repo"
assert "cat ~/bootstrap-from-relative-global-task" "relative-global"

# The default checkout does not collide with the installer's bootstrap staging
# directory, which may already contain versioned mise binaries.
mkdir -p "$MISE_DATA_DIR/bootstrap"
echo occupied >"$MISE_DATA_DIR/bootstrap/mise-test"
assert_succeed "GIT_DIR=/nonexistent GIT_WORK_TREE=/nonexistent mise -E work bootstrap --from '$source_repo' --yes --only dotfiles"
assert "git -C '$MISE_DATA_DIR/bootstrap-repo' remote get-url origin" "$source_repo"

# Global controls are preserved when mise re-executes from the checkout.
assert_succeed "mise --no-hooks -E work bootstrap --from '$source_repo' --from-dir '$checkout' --yes --only final-hook"
assert_fail "test -e ~/bootstrap-from-hook"

# A missing checkout is cloned, selected profile config is loaded, and its
# regular declarative resources and bootstrap task run from the checkout.
assert_succeed "mise -E work bootstrap --from '$source_repo' --from-dir '$checkout' --yes --only dotfiles,task"
assert "cat ~/.bootstrap-from-profile" "work-v1"
assert "cat ~/bootstrap-from-task" "work-v1"
assert "git -C '$checkout' remote get-url origin" "$source_repo"
assert_succeed "mise --cd '$checkout' -E work bootstrap dotfiles track ~/.bootstrap-from-profile --yes"

# An existing empty destination and a single-component relative destination
# are both valid clone targets.
empty_checkout="$PWD/bootstrap-empty-checkout"
mkdir "$empty_checkout"
assert_succeed "mise -E work bootstrap --from '$source_repo' --from-dir '$empty_checkout' --yes --only dotfiles"
assert "git -C '$empty_checkout' remote get-url origin" "$source_repo"
relative_checkout="bootstrap-relative-checkout"
assert_succeed "mise -E work bootstrap --from '$source_repo' --from-dir '$relative_checkout' --yes --only dotfiles"
assert "git -C '$relative_checkout' remote get-url origin" "$source_repo"

# A reused checkout without --update changes nothing and records no
# "bootstrap --from" generation of its own; a clone or pull does. Its checkout
# summary distinguishes the parent operation from the child bootstrap.
count_from_runs="mise bootstrap dotfiles history --json | jq '[.[] | select((.description // \"\") | contains(\"checkout of $source_repo\"))] | length'"
before="$(eval "$count_from_runs")"
assert_succeed "mise -E work bootstrap --from '$source_repo' --from-dir '$checkout' --yes --only dotfiles"
assert "$count_from_runs" "$before"

# Existing checkouts are reused, while --update fast-forwards before bootstrap.
echo work-v2 >"$source_repo/profile"
perl -pi -e 's/work-v1/work-v2/' "$source_repo/mise.work.toml"
git -C "$source_repo" add .
git -C "$source_repo" commit -qm update
assert_succeed "mise -E work bootstrap --from '$source_repo' --from-dir '$checkout' --update --yes --only dotfiles,task"
assert "cat ~/.bootstrap-from-profile" "work-v2"
assert "cat ~/bootstrap-from-task" "work-v2"
assert "$count_from_runs" "$((before + 1))"

# --dry-run also leaves an existing checkout untouched when --update is set.
echo work-v3 >"$source_repo/profile"
git -C "$source_repo" add .
git -C "$source_repo" commit -qm dry-update
assert_contains "mise -E work bootstrap --from '$source_repo' --from-dir '$checkout' --update --dry-run --only dotfiles" "Would run: git -C"
assert "cat '$checkout/profile'" "work-v2"

# Origin validation compares the raw configured URL, not an insteadOf-rewritten
# transport URL.
git config --global url.file:///rewritten/.insteadOf "$source_repo"
assert_succeed "mise -E work bootstrap --from '$source_repo' --from-dir '$checkout' --yes --only dotfiles"
git config --global --unset-all url.file:///rewritten/.insteadOf

# Dry-run reports a clone without creating its destination.
assert_contains "mise -E work bootstrap --from '$source_repo' --from-dir '$dry_checkout' --dry-run" "Would run: git clone"
assert_directory_not_exists "$dry_checkout"

# Reusing a directory from a different remote fails rather than bootstrapping
# an unexpected repository.
git -C "$checkout" remote set-url origin unexpected
assert_fail "mise -E work bootstrap --from '$source_repo' --from-dir '$checkout' --yes"

# A go-getter style ?ref= selects a branch, tag or commit instead of the
# default branch; --update follows a branch and switches to a moved ref.
ref_source="$PWD/bootstrap-ref-source"
mkdir -p "$ref_source"
git -C "$ref_source" init -q -b main
git -C "$ref_source" config user.email test@example.com
git -C "$ref_source" config user.name "mise test"
cat <<'EOF' >"$ref_source/mise.toml"
[tasks.bootstrap]
run = 'cp "$PWD/marker" "$HOME/bootstrap-ref-marker"'
EOF
echo main >"$ref_source/marker"
git -C "$ref_source" add .
git -C "$ref_source" commit -qm main
git -C "$ref_source" tag v1
git -C "$ref_source" checkout -q -b feature/x
echo feature >"$ref_source/marker"
git -C "$ref_source" commit -qam feature
git -C "$ref_source" checkout -q main
echo main-2 >"$ref_source/marker"
git -C "$ref_source" commit -qam main-2

assert_succeed "mise bootstrap --from '$ref_source?ref=feature/x' --from-dir '$PWD/ref-branch' --yes --only task"
assert "cat ~/bootstrap-ref-marker" "feature"
assert "git -C '$PWD/ref-branch' remote get-url origin" "$ref_source"

assert_succeed "mise bootstrap --from 'git::$ref_source?ref=v1' --from-dir '$PWD/ref-tag' --yes --only task"
assert "cat ~/bootstrap-ref-marker" "main"

# --update on a branch ref fast-forwards it
git -C "$ref_source" checkout -q feature/x
echo feature-2 >"$ref_source/marker"
git -C "$ref_source" commit -qam feature-2
git -C "$ref_source" checkout -q main
assert_succeed "mise bootstrap --from '$ref_source?ref=feature/x' --from-dir '$PWD/ref-branch' --update --yes --only task"
assert "cat ~/bootstrap-ref-marker" "feature-2"

# --update on a tag ref (detached HEAD) does not try to pull
assert_succeed "mise bootstrap --from '$ref_source?ref=v1' --from-dir '$PWD/ref-tag' --update --yes --only task"
assert "cat ~/bootstrap-ref-marker" "main"

# --update follows a tag that moved on the remote
git -C "$ref_source" tag -f v1 main >/dev/null
assert_succeed "mise bootstrap --from '$ref_source?ref=v1' --from-dir '$PWD/ref-tag' --update --yes --only task"
assert "cat ~/bootstrap-ref-marker" "main-2"

assert_contains "mise bootstrap --from '$ref_source?ref=v1' --from-dir '$PWD/ref-dry' --dry-run" "checkout v1 --"
assert_contains "mise bootstrap --from '$ref_source?ref=feature/x' --from-dir '$PWD/ref-branch' --update --dry-run" "merge --ff-only origin/feature/x (if it is a branch)"

# An unknown ref fails and leaves no checkout that a later run would reuse
assert_fail "mise bootstrap --from '$ref_source?ref=nope' --from-dir '$PWD/ref-missing' --yes --only task"
assert_directory_not_exists "$PWD/ref-missing"

# --update rejects a tag that was deleted upstream instead of using the stale
# local copy, and keeps the other tags in the checkout
git -C "$PWD/ref-tag" tag local-only
git -C "$ref_source" tag -d v1 >/dev/null
assert_fail "mise bootstrap --from '$ref_source?ref=v1' --from-dir '$PWD/ref-tag' --update --yes --only task"
assert_contains "git -C '$PWD/ref-tag' tag" "local-only"

# A local tag that shares its name with a branch on origin does not block
# updating that branch
git -C "$PWD/ref-branch" tag main
assert_succeed "mise bootstrap --from '$ref_source?ref=main' --from-dir '$PWD/ref-branch' --update --yes --only task"

# A local tag that shadows a branch of the same name on origin does not stop
# --update from switching to the branch and following it
git -C "$ref_source" checkout -q -b rel main
echo rel >"$ref_source/marker"
git -C "$ref_source" commit -qam rel
git -C "$ref_source" checkout -q main
git -C "$PWD/ref-branch" tag rel
assert_succeed "mise bootstrap --from '$ref_source?ref=rel' --from-dir '$PWD/ref-branch' --update --yes --only task"
assert "cat ~/bootstrap-ref-marker" "rel"
assert "git -C '$PWD/ref-branch' symbolic-ref HEAD" "refs/heads/rel"

# A branch wins over a tag of the same name, on a fresh clone as on update
git -C "$ref_source" checkout -q -b both main
echo both-branch >"$ref_source/marker"
git -C "$ref_source" commit -qam both
git -C "$ref_source" tag both main
git -C "$ref_source" checkout -q main
assert_succeed "mise bootstrap --from '$ref_source?ref=both' --from-dir '$PWD/ref-both' --yes --only task"
assert "cat ~/bootstrap-ref-marker" "both-branch"
assert "git -C '$PWD/ref-both' symbolic-ref HEAD" "refs/heads/both"

# --update rejects a branch deleted upstream instead of using the local copy
git -C "$ref_source" branch -q gone main
assert_succeed "mise bootstrap --from '$ref_source?ref=gone' --from-dir '$PWD/ref-gone' --yes --only task"
git -C "$ref_source" branch -q -D gone
assert_fail "mise bootstrap --from '$ref_source?ref=gone' --from-dir '$PWD/ref-gone' --update --yes --only task"

# --update follows origin even when the local branch tracks another remote
git clone -q "$ref_source" "$PWD/ref-other"
git -C "$PWD/ref-other" config user.email test@example.com
git -C "$PWD/ref-other" config user.name "mise test"
git -C "$PWD/ref-other" checkout -q -B both origin/both
echo other-remote >"$PWD/ref-other/marker"
git -C "$PWD/ref-other" commit -qam other
git -C "$PWD/ref-both" remote add other "$PWD/ref-other"
git -C "$PWD/ref-both" fetch other
git -C "$PWD/ref-both" branch -q --set-upstream-to=other/both both
git -C "$ref_source" checkout -q -B both refs/heads/both
echo origin-newer >"$ref_source/marker"
git -C "$ref_source" commit -qam origin-newer
git -C "$ref_source" checkout -q main
assert_succeed "mise bootstrap --from '$ref_source?ref=both' --from-dir '$PWD/ref-both' --update --yes --only task"
assert "cat ~/bootstrap-ref-marker" "origin-newer"

# HEAD is not a branch or tag; omitting ref selects the default branch
assert_fail "mise bootstrap --from '$ref_source?ref=HEAD' --from-dir '$PWD/ref-head' --yes --only task"
assert_directory_not_exists "$PWD/ref-head"
