#!/usr/bin/env bash
# success — land the attempt's branch on main, then take the place away.
#
# ENVIRONMENT:
#   TICKET_ID, TICKET_REF, TICKET_FLOW   as before received them
#   ATTEMPT_DIR      the worktree before made; unset means the current directory,
#                    while an empty value means no attempt tree exists
#   RUN_ID, RUN_CAUSE, RUN_EXIT, BOT_CMD the run's outcome and its key
#   BOT_HOME         the home containing the named run's retained evidence
#   SDLC_REPO        baggage from before: the repository's checkout
#   SDLC_BRANCH      baggage from before: ticket/<TICKET_ID>
#   Both baggage values are re-derived from the worktree when absent, so a
#   hand-run settlement standing in the tree still works. On a no-tree retry,
#   the local ticket branch tip is the identity checked and pushed.
#
# WHAT IT DOES
#   1. resolve the local branch tip, fetch, then compare it with origin/main
#   2. if the branch adds no commits, there is nothing to land — say so. If it
#      does, require the flow's sealed run witness to name this exact tip.
#      Refuse a moved main rather than creating history after verification.
#   3. the push is a plain `git push origin <tip>:main` — FAST-FORWARD ONLY.
#      A rejected push leaves the branch and worktree standing for a human or
#      a retry to look at.
#   4. refresh origin/main, then fast-forward a clean registered main checkout
#      when safe; invoke its deploy hook only at the expected deployment commit
#   5. remove the worktree and its baseline sibling, then delete the branch
#      locally and on origin
#
# STDOUT:  "landed <base>..<tip>" for a settled landing, "nothing to
#          land" for a settled no-op, or one activation receipt: "activated"
#          or "activation-pending <base>..<tip>". Pending is the only stdout
#          when the durable snapshot's checkout or deployment cannot activate.
# EXIT:    0 settled or no-op; 1 landing, deploy, or teardown failure; 2 when
#          run evidence is missing or does not identify the candidate; 3 when
#          main moved; 4 when durable activation remains pending.
if ((BASH_VERSINFO[0] < 3 || (BASH_VERSINFO[0] == 3 && BASH_VERSINFO[1] < 2))); then
	printf 'Bash %s is unsupported; Bash 3.2 or newer is required\n' "$BASH_VERSION" >&2
	exit 2
fi

set -euo pipefail

git_with_optional_timeout() {
	if command -v timeout >/dev/null 2>&1; then
		timeout -k 10 60 git "$@"
	else
		git "$@"
	fi
}

ACTIVATION_BASE=${ACTIVATION_BASE-}
ACTIVATION_TIP=${ACTIVATION_TIP-}
if ([ -n "$ACTIVATION_BASE" ] && [ -z "$ACTIVATION_TIP" ]) ||
	([ -z "$ACTIVATION_BASE" ] && [ -n "$ACTIVATION_TIP" ]); then
	echo "success: ACTIVATION_BASE and ACTIVATION_TIP must be supplied together" >&2
	exit 1
fi

checkout_left_alone() {
	echo "checkout left alone: $1" >&2
}

deploy_checkout() {
	local expected_tip=$1 branch status head hook hook_status
	if ! git_with_optional_timeout -C "$REPO" fetch --quiet origin; then
		checkout_left_alone "could not refresh origin/main"
		return 4
	fi
	if ! branch=$(git -C "$REPO" symbolic-ref --quiet --short HEAD 2>/dev/null); then
		checkout_left_alone "checkout is detached"
		return 4
	fi
	if [ "$branch" != "main" ]; then
		checkout_left_alone "checkout is on $branch, not main"
		return 4
	fi
	if ! status=$(git -C "$REPO" status --porcelain); then
		checkout_left_alone "could not inspect checkout status"
		return 4
	fi
	if [ -n "$status" ]; then
		checkout_left_alone "checkout is dirty"
		return 4
	fi
	if ! git -C "$REPO" merge --ff-only origin/main >/dev/null 2>&1; then
		checkout_left_alone "could not fast-forward main to origin/main"
		return 4
	fi
	if ! head=$(git -C "$REPO" rev-parse --verify 'HEAD^{commit}' 2>/dev/null) || [ "$head" != "$expected_tip" ]; then
		checkout_left_alone "checkout did not reach the requested tip"
		return 4
	fi
	hook="$REPO/sdlc/scripts/deploy"
	if [ -x "$hook" ]; then
		if (cd "$REPO" && LANDED_BASE="$BASE" LANDED_TIP="$TIP" "$hook" >&2); then
			:
		else
			hook_status=$?
			if [ "$hook_status" -eq 4 ]; then
				return 4
			fi
			echo "success: deploy hook failed" >&2
			return 1
		fi
	fi
}

activate_only() {
	if ! git_with_optional_timeout -C "$REPO" fetch --quiet origin; then
		checkout_left_alone "could not refresh origin/main"
		printf 'activation-pending %s..%s\n' "$ACTIVATION_BASE" "$ACTIVATION_TIP"
		return 4
	fi
	if ! BASE=$(git -C "$REPO" rev-parse --verify "${ACTIVATION_BASE}^{commit}" 2>/dev/null) ||
		[ "$BASE" != "$ACTIVATION_BASE" ]; then
		echo "success: ACTIVATION_BASE is not a commit identity" >&2
		return 1
	fi
	if ! TIP=$(git -C "$REPO" rev-parse --verify "${ACTIVATION_TIP}^{commit}" 2>/dev/null) ||
		[ "$TIP" != "$ACTIVATION_TIP" ]; then
		echo "success: ACTIVATION_TIP is not a commit identity" >&2
		return 1
	fi
	if ! git -C "$REPO" merge-base --is-ancestor "$BASE" "$TIP"; then
		echo "success: ACTIVATION_BASE is not an ancestor of ACTIVATION_TIP" >&2
		return 1
	fi
	if ! origin_tip=$(git -C "$REPO" rev-parse --verify 'origin/main^{commit}' 2>/dev/null) ||
		! git -C "$REPO" merge-base --is-ancestor "$TIP" "$origin_tip"; then
		echo "success: origin/main does not contain ACTIVATION_TIP" >&2
		return 1
	fi
	local activation_status
	if deploy_checkout "$origin_tip"; then
		activation_status=0
	else
		activation_status=$?
	fi
	if [ "$activation_status" -eq 4 ]; then
		printf 'activation-pending %s..%s\n' "$BASE" "$TIP"
		return 4
	fi
	if [ "$activation_status" -ne 0 ]; then
		return "$activation_status"
	fi
	printf 'activated %s..%s\n' "$BASE" "$TIP"
}

if [ -n "$ACTIVATION_BASE" ] && [ -n "$ACTIVATION_TIP" ]; then
	: "${SDLC_REPO:?success: SDLC_REPO is required for activation}"
	REPO=$SDLC_REPO
	if ! git -C "$REPO" rev-parse --git-dir >/dev/null 2>&1; then
		echo "success: $REPO is not a Git repository" >&2
		exit 1
	fi
	if activate_only; then
		exit 0
	else
		exit $?
	fi
fi

: "${TICKET_ID:?success: TICKET_ID is required}"
case "$TICKET_ID" in
[0-9][0-9][0-9][0-9]) ;;
*)
	echo "success: TICKET_ID must be exactly four digits" >&2
	exit 1
	;;
esac

BRANCH=${SDLC_BRANCH:-ticket/$TICKET_ID}
ATTEMPT_TREE_PRESENT=1
if [ "${ATTEMPT_DIR+x}" = x ]; then
	TREE=$ATTEMPT_DIR
	if [ -z "$TREE" ]; then
		ATTEMPT_TREE_PRESENT=0
	fi
else
	TREE=$(pwd -P)
fi
BASELINE=
if [ "$ATTEMPT_TREE_PRESENT" -eq 1 ]; then
	BASELINE="$TREE.baseline"
fi

resolve_repo() {
	local from=$1 common
	common=$(git -C "$from" rev-parse --path-format=absolute --git-common-dir 2>/dev/null) || return 1
	# The common dir is <checkout>/.git; its parent is the checkout.
	(CDPATH='' cd -- "$common/.." && pwd -P)
}

if [ -n "${SDLC_REPO-}" ]; then
	REPO=$SDLC_REPO
elif [ "$ATTEMPT_TREE_PRESENT" -eq 1 ] && [ -d "$TREE" ] && REPO=$(resolve_repo "$TREE"); then
	:
else
	echo "success: cannot tell which checkout owns $BRANCH — no SDLC_REPO and no readable worktree" >&2
	exit 1
fi
if ! git -C "$REPO" rev-parse --git-dir >/dev/null 2>&1; then
	echo "success: $REPO is not a Git repository" >&2
	exit 1
fi
if ! common=$(git -C "$REPO" rev-parse --path-format=absolute --git-common-dir 2>/dev/null) ||
	! REPO_COMMON=$(CDPATH='' cd -- "$common" && pwd -P); then
	echo "success: cannot determine the Git common directory for $REPO" >&2
	exit 1
fi

candidate_owned() {
	local candidate=$1 top candidate_path top_path candidate_common
	top=$(git -C "$candidate" rev-parse --show-toplevel 2>/dev/null) || return 1
	candidate_path=$(CDPATH='' cd -- "$candidate" && pwd -P) || return 1
	top_path=$(CDPATH='' cd -- "$top" && pwd -P) || return 1
	[ "$candidate_path" = "$top_path" ] || return 1
	candidate_common=$(git -C "$candidate" rev-parse --path-format=absolute --git-common-dir 2>/dev/null) || return 1
	candidate_common=$(CDPATH='' cd -- "$candidate_common" && pwd -P) || return 1
	[ "$candidate_common" = "$REPO_COMMON" ]
}

lexical_path() {
	local path=$1 part
	local -a parts=() source=()
	case "$path" in
	/*) ;;
	*) return 1 ;;
	esac
	IFS=/ read -r -a source <<< "$path"
	for part in "${source[@]}"; do
		case "$part" in
		'' | .) ;;
		..)
			if [ "${#parts[@]}" -gt 0 ]; then
				unset 'parts[${#parts[@]} - 1]'
			fi
			;;
		*) parts+=("$part") ;;
		esac
	done
	local IFS=/
	printf '/%s\n' "${parts[*]}"
}

orphan_owned() {
	local candidate=$1 candidate_path pointer normalized extra
	[ -d "$candidate" ] || return 1
	[ -f "$candidate/.git" ] && [ ! -L "$candidate/.git" ] || return 1
	{
		IFS= read -r pointer || [ -n "$pointer" ] || return 1
		if IFS= read -r extra || [ -n "$extra" ]; then
			return 1
		fi
	} <"$candidate/.git" || return 1
	case "$pointer" in
	"gitdir: "?*) pointer=${pointer#gitdir: } ;;
	*) return 1 ;;
	esac
	candidate_path=$(CDPATH='' cd -- "$candidate" && pwd -P) || return 1
	case "$pointer" in
	/*) normalized=$(lexical_path "$pointer") ;;
	*) normalized=$(lexical_path "$candidate_path/$pointer") ;;
	esac
	case "$normalized" in
	"$REPO_COMMON"/*) return 0 ;;
	*) return 1 ;;
	esac
}

registered_worktree() {
	local candidate=$1 candidate_path listing line registered
	candidate_path=$(lexical_path "$candidate") || return 1
	listing=$(mktemp) || return 1
	if ! git -C "$REPO" worktree list --porcelain -z >"$listing"; then
		rm -f -- "$listing"
		return 1
	fi
	while IFS= read -r -d '' line; do
		case "$line" in
		"worktree "*)
			registered=$(lexical_path "${line#worktree }") || continue
			if [ "$registered" = "$candidate_path" ]; then
				rm -f -- "$listing"
				return 0
			fi
			;;
		esac
	done <"$listing"
	rm -f -- "$listing"
	return 1
}

owned_worktree() {
	candidate_owned "$1" || orphan_owned "$1" || registered_worktree "$1"
}

remove_tree() {
	local candidate=$1
	if git -C "$REPO" worktree remove --force "$candidate" >/dev/null 2>&1; then
		return 0
	fi
	[ -e "$candidate" ] || return 0
	if ! owned_worktree "$candidate"; then
		echo "success: refusing to remove $candidate: it is not a worktree owned by $REPO" >&2
		return 1
	fi
	rm -rf "$candidate"
}

remove_baseline() {
	if ! owned_worktree "$BASELINE"; then
		echo "success: refusing to remove $BASELINE: it is not a worktree owned by $REPO" >&2
		return 1
	fi
	remove_tree "$BASELINE"
}

# Refuse before changing anything: a worktree that is not ours is not ours
# to remove, and a half teardown is worse than none.
TREE_ORPHAN=0
if [ "$ATTEMPT_TREE_PRESENT" -eq 1 ]; then
	if [ -e "$TREE" ]; then
		if candidate_owned "$TREE"; then
			:
		elif orphan_owned "$TREE"; then
			TREE_ORPHAN=1
		else
			echo "success: refusing to remove $TREE: it is not a worktree owned by $REPO" >&2
			exit 1
		fi
	fi
	if [ -e "$BASELINE" ] && ! owned_worktree "$BASELINE"; then
		echo "success: refusing to remove $BASELINE: it is not a worktree owned by $REPO" >&2
		exit 1
	fi
fi

# 1. The tip to land.
if ! TIP=$(git -C "$REPO" rev-parse --verify "refs/heads/$BRANCH^{commit}" 2>/dev/null); then
	echo "success: $BRANCH has no local tip in $REPO" >&2
	exit 1
fi
ATTEMPT_TIP=$TIP
if [ "$ATTEMPT_TREE_PRESENT" -eq 0 ]; then
	echo "success: no attempt tree supplied; skipping worktree HEAD check" >&2
elif [ "$TREE_ORPHAN" -eq 0 ]; then
	if ! HEAD_SHA=$(git -C "$TREE" rev-parse --verify 'HEAD^{commit}' 2>/dev/null) || [ "$HEAD_SHA" != "$TIP" ]; then
		echo "success: the worktree HEAD is not $BRANCH's tip — refusing to land a commit the attempt did not end on" >&2
		exit 1
	fi
fi
if ! git_with_optional_timeout -C "$REPO" fetch --quiet origin; then
	echo "success: git fetch origin failed" >&2
	exit 1
fi
BASE=$(git -C "$REPO" rev-parse --verify 'origin/main^{commit}')
FETCHED_MAIN_TIP=$BASE
LANDING_RECEIPT=

# Read the flow's sealed witness back out of the run record. `bot show --check`
# owns the record format and the multi-recording rules: it prints the sealed
# capture when the record holds one successful recording of the named check, or
# several that agree, and refuses with a diagnostic for absence, conflict, or a
# malformed record. The shell only compares its one printed line to one sha.
verify_run_witness() {
	local witness_file witness accessor_stderr
	case "${TICKET_FLOW-}" in
	build) witness_file=flows/build/05-verify/gate/06-witness ;;
	quickfix) witness_file=flows/quickfix/02-land/gate/05-witness ;;
	review) witness_file=flows/review/02-file/gate/04-witness ;;
	plan) witness_file=flows/plan/03-land/gate/06-witness ;;
	repair) witness_file=flows/repair/02-land/gate/05-witness ;;
	*) echo "success: no final witness gate for flow ${TICKET_FLOW-<missing>}" >&2; return 1 ;;
	esac
	if [ -z "${RUN_ID-}" ] || [ -z "${BOT_CMD-}" ] || [ -z "${BOT_HOME-}" ]; then
		echo "success: RUN_ID, BOT_CMD, and BOT_HOME are required to verify a landing" >&2
		return 1
	fi
	case "$RUN_ID" in '' | . | .. | /* | */*) echo "success: RUN_ID does not name one run directory" >&2; return 1 ;; esac
	if ! accessor_stderr=$(mktemp); then
		echo "success: cannot capture run evidence" >&2
		return 1
	fi
	if ! witness=$("$BOT_CMD" show "$RUN_ID" --check "$witness_file" --home "$BOT_HOME" 2>"$accessor_stderr"); then
		echo "success: bot show --check $witness_file failed for run $RUN_ID: $(cat "$accessor_stderr")" >&2
		rm -f -- "$accessor_stderr"
		return 1
	fi
	rm -f -- "$accessor_stderr"
	if [[ ! "$witness" =~ ^verified\ [0-9a-f]{40}$ ]]; then
		echo "success: run witness must be exactly one lowercase verified SHA line" >&2
		return 1
	fi
	WITNESSED_TIP=${witness#verified }
}

# 2. Land, unless the attempt added nothing to main.
if [ "$(git -C "$REPO" rev-list --count "origin/main..$TIP")" -eq 0 ]; then
	LANDING_RECEIPT="nothing to land: $BRANCH adds no commits to main"
	BASE=$ATTEMPT_TIP
	TIP=$FETCHED_MAIN_TIP
else
	if [ "$TREE_ORPHAN" -eq 1 ]; then
		echo "success: cannot verify the unregistered worktree HEAD before landing" >&2
		exit 1
	fi
	if ! verify_run_witness; then
		exit 2
	fi
	if [ "$WITNESSED_TIP" != "$TIP" ]; then
		echo "success: witnessed candidate $WITNESSED_TIP does not match push candidate $TIP" >&2
		exit 2
	fi
	if git -C "$REPO" merge-base --is-ancestor origin/main "$TIP"; then
		:
	else
		ANCESTRY_STATUS=$?
		if [ "$ANCESTRY_STATUS" -ne 1 ]; then
			echo "success: cannot compare the candidate with origin/main" >&2
			exit 1
		fi
		echo "success: origin/main moved beyond witnessed candidate $TIP; the branch is left standing" >&2
		exit 3
	fi
	validate_ticket_tree() {
		local state tasks_status
		if [ ! -x "$REPO/sdlc/project/tasks" ]; then
			echo "success: deployed tasks reader is not executable" >&2
			return 1
		fi
		if ! state=$(mktemp -d); then
			echo "success: cannot create an isolated ticket-tree view" >&2
			return 1
		fi
		if ! git init --bare --quiet "$state/origin.git" >/dev/null 2>&1 ||
			! git -C "$REPO" push --quiet "$state/origin.git" "$TIP:refs/heads/main" >/dev/null 2>&1 ||
			! git init --quiet "$state/view" >/dev/null 2>&1 ||
			! git -C "$state/view" remote add origin "$state/origin.git" >/dev/null 2>&1; then
			rm -rf -- "$state"
			echo "success: cannot prepare the isolated ticket-tree view" >&2
			return 1
		fi
		if (cd "$state/view" && "$REPO/sdlc/project/tasks" >/dev/null 2>"$state/tasks.stderr"); then
			rm -rf -- "$state"
			return 0
		else
			tasks_status=$?
		fi
		cat "$state/tasks.stderr" >&2
		if ! grep -q '^tasks:' "$state/tasks.stderr"; then
			echo "success: deployed tasks reader failed without a diagnostic" >&2
		fi
		rm -rf -- "$state"
		return "$tasks_status"
	}
	if ! validate_ticket_tree; then
		exit 1
	fi
	if ! git -C "$REPO" push --quiet origin "$TIP:main"; then
		echo "success: $TIP could not fast-forward main — main moved; the branch is left standing" >&2
		exit 3
	fi
	LANDING_RECEIPT="landed $BASE..$TIP"
fi

# 3. Deploy the registered checkout after origin/main reflects settlement.
# A durable snapshot remains pending when its checkout or deployment cannot
# activate it.
failures=

if deploy_checkout "$TIP"; then
	ACTIVATION_STATUS=0
else
	ACTIVATION_STATUS=$?
fi
if [ "$ACTIVATION_STATUS" -eq 1 ]; then
	failures="${failures}${failures:+, }deploy hook"
fi
if [ "$ACTIVATION_STATUS" -eq 4 ]; then
	printf 'activation-pending %s..%s\n' "$BASE" "$TIP"
elif [ "$ACTIVATION_STATUS" -eq 0 ] && [ -n "$LANDING_RECEIPT" ]; then
	echo "$LANDING_RECEIPT"
fi

# 4. Teardown. Every failure is named; none of them is silent.
prune_worktrees() {
	git -C "$REPO" worktree prune >/dev/null 2>&1
}
remove_local_branch() {
	if git -C "$REPO" show-ref --verify --quiet "refs/heads/$BRANCH"; then
		git -C "$REPO" branch -D "$BRANCH" >/dev/null 2>&1
	fi
}
remove_origin_branch() {
	if git -C "$REPO" ls-remote --exit-code --heads origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
		git -C "$REPO" push --quiet origin --delete "$BRANCH" >/dev/null 2>&1
	fi
}

cleanup() {
	local name=$1
	shift
	if ! "$@"; then
		failures="${failures}${failures:+, }$name"
	fi
}

if [ "$ATTEMPT_TREE_PRESENT" -eq 1 ]; then
	if [ -e "$TREE" ]; then
		cleanup "worktree removal $TREE" remove_tree "$TREE"
	fi
	if [ -e "$BASELINE" ]; then
		cleanup "baseline worktree removal $BASELINE" remove_baseline
	fi
fi
cleanup "worktree prune" prune_worktrees
cleanup "local branch $BRANCH" remove_local_branch
cleanup "origin branch $BRANCH" remove_origin_branch

if [ -n "$failures" ]; then
	echo "success: settlement failed: $failures" >&2
fi
if [ "$ACTIVATION_STATUS" -eq 4 ]; then
	exit 4
fi
if [ -n "$failures" ]; then
	exit 1
fi
if [ "$ATTEMPT_TREE_PRESENT" -eq 1 ]; then
	echo "torn down $BRANCH and $TREE"
else
	echo "torn down $BRANCH"
fi
