#!/bin/sh
#
# git-issue-export-gitlab - Export issues to GitLab
#
# Usage: git issue export gitlab:<group>/<project> [options]
#

set -e

usage() {
	cat <<EOF
usage: git issue export gitlab:<group>/<project> [options]

Options:
   --dry-run         Show what would be exported without exporting
   -h, --help        Show this help

Authentication:
   Requires GitLab authentication via 'glab auth login'.
   Run 'glab auth login' first if not already authenticated.
EOF
	exit 1
}

provider=""
dry_run=0

while test $# -gt 0
do
	case "$1" in
		--dry-run)
			dry_run=1
			shift
			;;
		-h|--help)
			usage
			;;
		--)
			shift
			break
			;;
		-*)
			echo "error: unknown option '$1'" >&2
			usage
			;;
		*)
			if test -z "$provider"
			then
				provider="$1"
			fi
			shift
			;;
	esac
done

if test -z "$provider"
then
	echo "error: provider is required (e.g., gitlab:group/project)" >&2
	usage
fi

# Parse provider string
case "$provider" in
	gitlab:*/*)
		gl_path="${provider#gitlab:}"
		gl_group="${gl_path%/*}"
		gl_project="${gl_path#*/}"
		;;
	*)
		echo "error: invalid provider string '$provider'" >&2
		echo "       expected format: gitlab:<group>/<project>" >&2
		exit 1
		;;
esac

# Verify we are inside a git repository
git rev-parse --git-dir >/dev/null 2>&1 || {
	echo "fatal: not a git repository" >&2
	exit 128
}

# Check prerequisites
command -v glab >/dev/null 2>&1 || {
	echo "error: 'glab' (GitLab CLI) is required but not found" >&2
	echo "       install: https://gitlab.com/gitlab-org/cli" >&2
	echo "       brew install glab" >&2
	exit 1
}

command -v jq >/dev/null 2>&1 || {
	echo "error: 'jq' is required but not found" >&2
	echo "       install: https://stedolan.github.io/jq/" >&2
	exit 1
}

# Check glab auth
glab auth status >/dev/null 2>&1 || {
	echo "error: 'glab' is not authenticated. Run 'glab auth login' first." >&2
	exit 1
}

# Source shared library for user cache functions
. "$(dirname "$0")/git-issue-lib"

# Use glab api for all GitLab API calls
gl_repo="$gl_group/$gl_project"
# URL-encode the project path for API calls
gl_project_encoded="$(printf '%s' "$gl_repo" | sed 's|/|%2F|g')"

# Get the empty tree SHA
empty_tree="$(git hash-object -t tree /dev/null)"

exported=0
skipped=0
synced=0

target_prefix="gitlab:$gl_group/$gl_project#"

# Auto-seed cache with authenticated user identity
_gl_self="$(glab api /user 2>/dev/null || echo '{}')"
_gl_self_email="$(printf '%s' "$_gl_self" | jq -r '.public_email // .email // empty')"
_gl_self_login="$(printf '%s' "$_gl_self" | jq -r '.username // empty')"
_gl_self_id="$(printf '%s' "$_gl_self" | jq -r '.id // empty')"
if test -n "$_gl_self_email" && test -n "$_gl_self_login" && test -n "$_gl_self_id"
then
	cache_platform_user "$_gl_self_email" "$_gl_self_login" "$_gl_self_id" "gitlab"
fi

# Resolve an email to a GitLab user ID for assignee export
# Uses three-tier lookup: cache -> GitLab search API -> noreply pattern
# Returns GitLab user ID (integer), since GitLab API requires assignee_ids
resolve_gitlab_assignee() {
	_rgla_email="$1"

	# Tier 1: Persistent cache (populated during import or auto-seed)
	_rgla_login="$(lookup_cached_login "$_rgla_email" "gitlab")"
	if test -n "$_rgla_login"
	then
		_rgla_cached_id="$(lookup_cached_user_id "$_rgla_email" "gitlab")"
		if test -n "$_rgla_cached_id"
		then
			# Verify the user still exists
			_rgla_current="$(glab api "/users/$_rgla_cached_id" 2>/dev/null | jq -r '.username // empty')"
			if test -n "$_rgla_current"
			then
				# Update cache if username changed
				if test "$_rgla_current" != "$_rgla_login"
				then
					cache_platform_user "$_rgla_email" "$_rgla_current" "$_rgla_cached_id" "gitlab"
				fi
				printf '%s' "$_rgla_cached_id"
				return 0
			fi
		fi
	fi

	# Tier 2: GitLab user search API (works for public emails)
	_rgla_search="$(glab api "/users?search=$_rgla_email" 2>/dev/null || echo '[]')"
	_rgla_search_count="$(printf '%s' "$_rgla_search" | jq '. | length')"
	_rgla_j=0
	while test "$_rgla_j" -lt "$_rgla_search_count"
	do
		_rgla_found_email="$(printf '%s' "$_rgla_search" | jq -r ".[$_rgla_j].public_email // empty")"
		if test "$_rgla_found_email" = "$_rgla_email"
		then
			_rgla_found_login="$(printf '%s' "$_rgla_search" | jq -r ".[$_rgla_j].username // empty")"
			_rgla_found_id="$(printf '%s' "$_rgla_search" | jq -r ".[$_rgla_j].id // empty")"
			if test -n "$_rgla_found_id"
			then
				cache_platform_user "$_rgla_email" "$_rgla_found_login" "$_rgla_found_id" "gitlab"
				printf '%s' "$_rgla_found_id"
				return 0
			fi
		fi
		_rgla_j=$((_rgla_j + 1))
	done

	# Tier 3: Extract username from noreply email pattern
	case "$_rgla_email" in
		*@users.noreply.gitlab.com)
			_rgla_noreply_login="${_rgla_email%@users.noreply.gitlab.com}"
			# Verify user exists and get ID
			_rgla_verify="$(glab api "/users?username=$_rgla_noreply_login" 2>/dev/null || echo '[]')"
			_rgla_verify_id="$(printf '%s' "$_rgla_verify" | jq -r '.[0].id // empty')"
			if test -n "$_rgla_verify_id"
			then
				cache_platform_user "$_rgla_email" "$_rgla_noreply_login" "$_rgla_verify_id" "gitlab"
				printf '%s' "$_rgla_verify_id"
				return 0
			fi
			;;
	esac

	# All lookups failed
	return 0
}

tmpfile="$(mktemp)"
refs_file="$(mktemp)"
trap 'rm -f "$tmpfile" "$refs_file"' EXIT

# Write refs to file to avoid subshell pipeline
git for-each-ref --format='%(refname)' refs/issues/ > "$refs_file"

while IFS= read -r ref
do
	uuid="${ref#refs/issues/}"
	short_id="$(printf '%s' "$uuid" | cut -c1-7)"

	# Get root commit
	root="$(git rev-list --max-parents=0 "$ref")"
	title="$(git log -1 --format='%s' "$root")"

	# Extract body, stripping trailer block
	raw_body="$(git log -1 --format='%b' "$root")"
	body=""
	if test -n "$raw_body"
	then
		trailer_block="$(printf '%s\n' "$raw_body" | git interpret-trailers --parse 2>/dev/null)" || trailer_block=""
		if test -n "$trailer_block"
		then
			n_trailers="$(printf '%s\n' "$trailer_block" | wc -l | tr -d ' ')"
			n_body="$(printf '%s\n' "$raw_body" | wc -l | tr -d ' ')"
			# Subtract trailers + blank separator line
			n_keep=$((n_body - n_trailers - 1))
			if test "$n_keep" -gt 0
			then
				body="$(printf '%s\n' "$raw_body" | head -n "$n_keep")"
			fi
		else
			body="$raw_body"
		fi
	fi

	# Get current state
	state="$(git log --format='%(trailers:key=State,valueonly)' "$ref" | sed '/^$/d' | head -1)"
	state="$(printf '%s' "$state" | sed 's/^[[:space:]]*//')"

	# Get labels
	labels="$(git log --format='%(trailers:key=Labels,valueonly)' "$ref" | sed '/^$/d' | head -1)"
	labels="$(printf '%s' "$labels" | sed 's/^[[:space:]]*//')"

	# Get assignee (canonical email)
	# Use full trailer format to distinguish "no trailer" from "empty value"
	_assignee_line="$(git log --format='%(trailers:key=Assignee)' "$ref" | sed '/^$/d' | head -1)"
	has_assignee=0
	assignee_email=""
	if test -n "$_assignee_line"
	then
		has_assignee=1
		assignee_email="$(printf '%s' "$_assignee_line" | sed 's/^Assignee:[[:space:]]*//')"
	fi

	# Check for Provider-ID in commit chain
	existing_pid=""
	existing_pid="$(git log --format='%(trailers:key=Provider-ID,valueonly)' "$ref" | \
		sed '/^$/d' | sed 's/^[[:space:]]*//' | head -1)"

	if test -n "$existing_pid"
	then
		# Check if this Provider-ID is for the target GitLab project
		case "$existing_pid" in
			"$target_prefix"*)
				# Already exported to this project — sync comments and state
				issue_iid="${existing_pid#$target_prefix}"

				if test "$dry_run" -eq 1
				then
					printf '[dry-run] Would sync %s (already exported as #%s)\n' "$short_id" "$issue_iid"
					synced=$((synced + 1))
					continue
				fi

				# Get all commit SHAs that have already been exported
				exported_commits="$(git log --format='%(trailers:key=Exported-Commit,valueonly)' "$ref" | sed '/^$/d' | sed 's/^[[:space:]]*//')"

				# Export new comments
				issue_head="$(git rev-parse "$ref")"
				commits="$(git rev-list --reverse "$root..$ref")"
				new_comments_exported=0
				parent="$issue_head"

				for cmt in $commits
				do
					# Skip if already exported
					if echo "$exported_commits" | grep -qF "$cmt" 2>/dev/null
					then
						continue
					fi

					# Skip if has Provider-Comment-ID (imported note)
					cmt_provider_comment_id="$(git log -1 --format='%(trailers:key=Provider-Comment-ID,valueonly)' "$cmt" | sed '/^$/d' | sed 's/^[[:space:]]*//')"
					if test -n "$cmt_provider_comment_id"
					then
						continue
					fi

					# Skip state changes
					cmt_state_trailer="$(git log -1 --format='%(trailers:key=State,valueonly)' "$cmt" | sed '/^$/d')"
					if test -n "$cmt_state_trailer"
					then
						continue
					fi

					# Skip Provider-ID record commits
					cmt_provider_id="$(git log -1 --format='%(trailers:key=Provider-ID,valueonly)' "$cmt" | sed '/^$/d')"
					if test -n "$cmt_provider_id" && test "$cmt_provider_id" = "$existing_pid"
					then
						continue
					fi

					# Export comment
					cmt_subject="$(git log -1 --format='%s' "$cmt")"
					cmt_body_full="$(git log -1 --format='%b' "$cmt")"

					# Strip trailers from body
					cmt_body=""
					if test -n "$cmt_body_full"
					then
						cmt_trailers="$(printf '%s\n' "$cmt_body_full" | git interpret-trailers --parse 2>/dev/null)" || cmt_trailers=""
						if test -n "$cmt_trailers"
						then
							n_trailers="$(printf '%s\n' "$cmt_trailers" | wc -l | tr -d ' ')"
							n_body="$(printf '%s\n' "$cmt_body_full" | wc -l | tr -d ' ')"
							n_keep=$((n_body - n_trailers - 1))
							if test "$n_keep" -gt 0
							then
								cmt_body="$(printf '%s\n' "$cmt_body_full" | head -n "$n_keep")"
							fi
						else
							cmt_body="$cmt_body_full"
						fi
					fi

					note_text="$cmt_subject"
					if test -n "$cmt_body"
					then
						note_text="$note_text

$cmt_body"
					fi

					# Post note to GitLab
					note_response="$(glab api --method POST "projects/${gl_project_encoded}/issues/${issue_iid}/notes" \
						--field "body=$note_text" 2>/dev/null)" || continue

					# Extract note ID
					note_id="$(printf '%s' "$note_response" | jq -r '.id' 2>/dev/null)"

					if test -n "$note_id" && test "$note_id" != "null"
					then
						# Record Provider-Comment-ID
						comment_provider_id="gitlab:$gl_group/$gl_project#note-$note_id"
						printf 'Record export of comment to GitLab #%s\n' "$issue_iid" > "$tmpfile"
						git interpret-trailers --in-place \
							--trailer "Provider-Comment-ID: $comment_provider_id" \
							--trailer "Exported-Commit: $cmt" \
							"$tmpfile"

						new_commit="$(git commit-tree -p "$parent" -- "$empty_tree" < "$tmpfile")"
						parent="$new_commit"
						new_comments_exported=$((new_comments_exported + 1))
					fi
				done

				# Update ref if we exported comments
				if test "$new_comments_exported" -gt 0
				then
					git update-ref "$ref" "$parent" "$issue_head"
				fi

				# Sync state (map to GitLab state_event)
				case "$state" in
					closed)
						glab api --method PUT "projects/${gl_project_encoded}/issues/${issue_iid}" \
							--field "state_event=close" >/dev/null 2>&1 || true
						;;
					open)
						glab api --method PUT "projects/${gl_project_encoded}/issues/${issue_iid}" \
							--field "state_event=reopen" >/dev/null 2>&1 || true
						;;
				esac

				# Sync assignee (only if local issue has an opinion)
				if test "$has_assignee" -eq 1
				then
					if test -n "$assignee_email"
					then
						_sync_user_id="$(resolve_gitlab_assignee "$assignee_email")"
						if test -n "$_sync_user_id"
						then
							glab api --method PUT "projects/${gl_project_encoded}/issues/${issue_iid}" \
								--field "assignee_ids[]=$_sync_user_id" >/dev/null 2>&1 || {
								printf 'Warning: failed to sync assignee to #%s\n' "$issue_iid" >&2
							}
						else
							printf 'Warning: could not resolve %s to GitLab user for %s\n' "$assignee_email" "$short_id" >&2
						fi
					else
						# Explicitly unassigned in local issue — clear assignees
						glab api --method PUT "projects/${gl_project_encoded}/issues/${issue_iid}" \
							--field "assignee_ids[]=0" >/dev/null 2>&1 || true
					fi
				fi

				synced=$((synced + 1))
				if test "$new_comments_exported" -gt 0
				then
					printf 'Synced %s with #%s (%d new comment(s) exported)\n' "$short_id" "$issue_iid" "$new_comments_exported"
				else
					printf 'Synced %s with #%s\n' "$short_id" "$issue_iid"
				fi
				continue
				;;
			*)
				# Foreign Provider-ID (from different source)
				skipped=$((skipped + 1))
				printf 'Skipped %s (imported from %s)\n' "$short_id" "$existing_pid"
				continue
				;;
		esac
	fi

	# No Provider-ID — export as new GitLab issue
	if test "$dry_run" -eq 1
	then
		printf '[dry-run] Would export %s: %s\n' "$short_id" "$title"
		exported=$((exported + 1))
		continue
	fi

	# Resolve assignee email to GitLab user ID
	_export_assignee_id=""
	if test "$has_assignee" -eq 1 && test -n "$assignee_email"
	then
		_export_assignee_id="$(resolve_gitlab_assignee "$assignee_email")"
		if test -z "$_export_assignee_id"
		then
			printf 'Warning: could not resolve %s to GitLab user for %s\n' "$assignee_email" "$short_id" >&2
		fi
	fi

	# Create issue on GitLab using glab api
	if test -n "$labels" && test -n "$_export_assignee_id"
	then
		create_response="$(glab api --method POST "projects/${gl_project_encoded}/issues" \
			--field "title=$title" \
			--field "description=$body" \
			--field "labels=$labels" \
			--field "assignee_ids[]=$_export_assignee_id" 2>&1)" || {
			echo "error: failed to create issue on GitLab" >&2
			continue
		}
	elif test -n "$labels"
	then
		create_response="$(glab api --method POST "projects/${gl_project_encoded}/issues" \
			--field "title=$title" \
			--field "description=$body" \
			--field "labels=$labels" 2>&1)" || {
			echo "error: failed to create issue on GitLab" >&2
			continue
		}
	elif test -n "$_export_assignee_id"
	then
		create_response="$(glab api --method POST "projects/${gl_project_encoded}/issues" \
			--field "title=$title" \
			--field "description=$body" \
			--field "assignee_ids[]=$_export_assignee_id" 2>&1)" || {
			echo "error: failed to create issue on GitLab" >&2
			continue
		}
	else
		create_response="$(glab api --method POST "projects/${gl_project_encoded}/issues" \
			--field "title=$title" \
			--field "description=$body" 2>&1)" || {
			echo "error: failed to create issue on GitLab" >&2
			continue
		}
	fi

	# Check for API errors
	if printf '%s' "$create_response" | jq -e '.error' >/dev/null 2>&1
	then
		error_msg="$(printf '%s' "$create_response" | jq -r '.error // .message // "unknown error"')"
		echo "error: GitLab API error: $error_msg" >&2
		continue
	fi

	issue_iid="$(printf '%s' "$create_response" | jq -r '.iid')"
	provider_id="gitlab:$gl_group/$gl_project#$issue_iid"

	# Export comments (skip root, skip metadata commits)
	issue_head="$(git rev-parse "$ref")"
	commits="$(git rev-list --reverse "$root..$ref")"
	parent="$issue_head"
	comments_exported=0

	for cmt in $commits
	do
		# Skip commits with trailers (metadata changes)
		cmt_trailers="$(git log -1 --format='%(trailers)' "$cmt" | sed '/^$/d')"
		if test -n "$cmt_trailers"
		then
			continue
		fi

		cmt_subject="$(git log -1 --format='%s' "$cmt")"
		cmt_full="$(git log -1 --format='%b' "$cmt" | sed '/^$/d')"

		note_text="$cmt_subject"
		if test -n "$cmt_full"
		then
			note_text="$note_text

$cmt_full"
		fi

		# Post note to GitLab
		note_response="$(glab api --method POST "projects/${gl_project_encoded}/issues/${issue_iid}/notes" \
			--field "body=$note_text" 2>/dev/null)" || continue

		note_id="$(printf '%s' "$note_response" | jq -r '.id' 2>/dev/null)"

		if test -n "$note_id" && test "$note_id" != "null"
		then
			# Record Provider-Comment-ID and Exported-Commit
			comment_provider_id="gitlab:$gl_group/$gl_project#note-$note_id"
			printf 'Record export of comment to GitLab #%s\n' "$issue_iid" > "$tmpfile"
			git interpret-trailers --in-place \
				--trailer "Provider-Comment-ID: $comment_provider_id" \
				--trailer "Exported-Commit: $cmt" \
				"$tmpfile"

			new_commit="$(git commit-tree -p "$parent" -- "$empty_tree" < "$tmpfile")"
			parent="$new_commit"
			comments_exported=$((comments_exported + 1))
		fi
	done

	# Sync state if closed
	if test "$state" = "closed"
	then
		glab api --method PUT "projects/${gl_project_encoded}/issues/${issue_iid}" \
			--field "state_event=close" >/dev/null 2>&1 || true
	fi

	# Record Provider-ID locally
	printf '%s\n' "Record export to GitLab #$issue_iid" > "$tmpfile"
	git interpret-trailers --in-place --trailer "Provider-ID: $provider_id" "$tmpfile"

	# Use parent from comment chain if comments were exported
	if test "$comments_exported" -gt 0
	then
		new_commit="$(git commit-tree -p "$parent" -- "$empty_tree" < "$tmpfile")"
	else
		new_commit="$(git commit-tree -p "$issue_head" -- "$empty_tree" < "$tmpfile")"
	fi
	git update-ref -- "$ref" "$new_commit" "$issue_head"

	exported=$((exported + 1))
	printf 'Exported %s as #%s: %s\n' "$short_id" "$issue_iid" "$title"
done < "$refs_file"

printf 'Exported %d issues (%d skipped, %d synced)\n' "$exported" "$skipped" "$synced"
