#!/bin/sh
#
# git-issue-export-github - Export issues to GitHub
#
# Usage: git issue export github:<owner>/<repo> [options]
#

set -e

usage() {
	cat <<EOF
usage: git issue export github:<owner>/<repo> [options]

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

Authentication:
   Requires GitHub CLI (gh) to be installed and authenticated.
   Run 'gh auth login' 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., github:owner/repo)" >&2
	usage
fi

# Parse provider string
case "$provider" in
	github:*/*)
		gh_repo="${provider#github:}"
		gh_owner="${gh_repo%/*}"
		gh_reponame="${gh_repo#*/}"
		;;
	*)
		echo "error: invalid provider string '$provider'" >&2
		echo "       expected format: github:<owner>/<repo>" >&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 gh >/dev/null 2>&1 || {
	echo "error: 'gh' (GitHub CLI) is required but not found" >&2
	echo "       install: https://cli.github.com/" >&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 gh auth
gh auth status >/dev/null 2>&1 || {
	echo "error: 'gh' is not authenticated. Run 'gh auth login' first." >&2
	exit 1
}

. "$(dirname "$0")/git-issue-lib"

# Auto-seed cache from authenticated gh user (free, no extra API call needed)
if test "$dry_run" -eq 0
then
	_seed_user="$(gh api /user 2>/dev/null || echo '{}')"
	_seed_login="$(printf '%s' "$_seed_user" | jq -r '.login // empty')"
	_seed_id="$(printf '%s' "$_seed_user" | jq -r '.id // empty')"
	_seed_email="$(git config user.email 2>/dev/null || true)"
	if test -n "$_seed_login" && test -n "$_seed_id" && test -n "$_seed_email"
	then
		_existing="$(lookup_cached_login "$_seed_email" "github")"
		if test -z "$_existing"
		then
			cache_platform_user "$_seed_email" "$_seed_login" "$_seed_id" "github"
		fi
	fi
fi

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

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

exported=0
skipped=0
synced=0

target_prefix="github:$gh_owner/$gh_reponame#"

# Resolve an email to a GitHub username for assignee export
# Five-tier lookup optimized by cost and coverage:
#   0. Cache (free)
#   1. Noreply pattern (free, deterministic)
#   2. Repo commits API (5000 req/hr, works for private emails)
#   3. Search commits API (30 req/min, works for private emails across all repos)
#   4. Search users API (30 req/min, public emails only)
resolve_github_assignee() {
	_rga_email="$1"

	# Tier 0: Persistent cache (populated during import or previous export)
	_rga_login="$(lookup_cached_login "$_rga_email" "github")"
	if test -n "$_rga_login"
	then
		# Verify the user still exists (username may have changed)
		_rga_cached_id="$(lookup_cached_user_id "$_rga_email" "github")"
		if test -n "$_rga_cached_id"
		then
			_rga_current="$(gh api "/user/$_rga_cached_id" 2>/dev/null | jq -r '.login // empty')"
			if test -n "$_rga_current"
			then
				# Update cache if username changed
				if test "$_rga_current" != "$_rga_login"
				then
					cache_platform_user "$_rga_email" "$_rga_current" "$_rga_cached_id" "github"
				fi
				printf '%s' "$_rga_current"
				return 0
			fi
		fi
		# ID lookup failed but we have a cached login, try it directly
		printf '%s' "$_rga_login"
		return 0
	fi

	# Tier 1: Extract username from noreply email pattern (free, no API)
	case "$_rga_email" in
		*@users.noreply.github.com)
			_rga_noreply_login="${_rga_email%@users.noreply.github.com}"
			# Handle numeric prefix format: 12345+username@users.noreply.github.com
			case "$_rga_noreply_login" in
				*+*)
					_rga_noreply_login="${_rga_noreply_login#*+}"
					;;
			esac
			# Verify user exists
			_rga_verify="$(gh api "/users/$_rga_noreply_login" 2>/dev/null || echo '{}')"
			_rga_verify_id="$(printf '%s' "$_rga_verify" | jq -r '.id // empty')"
			if test -n "$_rga_verify_id"
			then
				cache_platform_user "$_rga_email" "$_rga_noreply_login" "$_rga_verify_id" "github"
				printf '%s' "$_rga_noreply_login"
				return 0
			fi
			;;
	esac

	# Tier 2: Repo commits API (5000 req/hr, works for private emails)
	# GitHub links commits to accounts via email settings, even when email is private
	_rga_repo_commit="$(gh api "/repos/$gh_owner/$gh_reponame/commits?author=$(printf '%s' "$_rga_email" | jq -sRr @uri)&per_page=1" 2>/dev/null || echo '[]')"
	_rga_repo_login="$(printf '%s' "$_rga_repo_commit" | jq -r '.[0].author.login // empty')"
	_rga_repo_id="$(printf '%s' "$_rga_repo_commit" | jq -r '.[0].author.id // empty')"

	if test -n "$_rga_repo_login"
	then
		cache_platform_user "$_rga_email" "$_rga_repo_login" "$_rga_repo_id" "github"
		printf '%s' "$_rga_repo_login"
		return 0
	fi

	# Tier 3: Search commits API (30 req/min, works for private emails across all repos)
	_rga_commit_search="$(gh api "/search/commits?q=author-email:$(printf '%s' "$_rga_email" | jq -sRr @uri)&per_page=1" 2>/dev/null || echo '{}')"
	_rga_commit_login="$(printf '%s' "$_rga_commit_search" | jq -r '.items[0].author.login // empty')"
	_rga_commit_id="$(printf '%s' "$_rga_commit_search" | jq -r '.items[0].author.id // empty')"

	if test -n "$_rga_commit_login"
	then
		cache_platform_user "$_rga_email" "$_rga_commit_login" "$_rga_commit_id" "github"
		printf '%s' "$_rga_commit_login"
		return 0
	fi

	# Tier 4: Search users API (30 req/min, public emails only — last resort)
	_rga_search="$(gh api "/search/users?q=$(printf '%s' "$_rga_email" | jq -sRr @uri)+in:email" 2>/dev/null || echo '{}')"
	_rga_found_login="$(printf '%s' "$_rga_search" | jq -r '.items[0].login // empty')"
	_rga_found_id="$(printf '%s' "$_rga_search" | jq -r '.items[0].id // empty')"

	if test -n "$_rga_found_login"
	then
		cache_platform_user "$_rga_email" "$_rga_found_login" "$_rga_found_id" "github"
		printf '%s' "$_rga_found_login"
		return 0
	fi

	# All lookups failed
	return 0
}

# Write refs to file to avoid subshell pipeline (counters would be lost)
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 via interpret-trailers
	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 before them
			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 any commit in the 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 repo
		case "$existing_pid" in
			"$target_prefix"*)
				# Already exported to this repo — sync new comments and state
				issue_number="${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_number"
					synced=$((synced + 1))
					continue
				fi

				# Get root commit
				root="$(git rev-list --max-parents=0 "$ref")"

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

				# Export new comments (skip if commit SHA already in exported list)
				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 this commit was already exported (check Exported-Commit trailer list)
					if echo "$exported_commits" | grep -qF "$cmt" 2>/dev/null
					then
						continue
					fi

					# Also skip if commit has Provider-Comment-ID (imported from GitHub)
					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 commits with State trailer (metadata changes, not comments)
					cmt_state_trailer="$(git log -1 --format='%(trailers:key=State,valueonly)' "$cmt" | sed '/^$/d')"
					if test -n "$cmt_state_trailer"
					then
						continue
					fi

					# Skip commits with Provider-ID trailer (export 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

					# This is a comment commit - export it
					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

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

$cmt_body"
					fi

					# Post comment to GitHub and capture response
					comment_response="$(gh api --method POST "/repos/$gh_owner/$gh_reponame/issues/$issue_number/comments" \
						-f body="$comment_text" 2>/dev/null)" || continue

					# Extract comment ID from response
					comment_id="$(printf '%s' "$comment_response" | jq -r '.id' 2>/dev/null)"

					if test -n "$comment_id" && test "$comment_id" != "null"
					then
						# Record Provider-Comment-ID by creating a metadata commit
						# Include original commit SHA to associate exported comment with local commit
						comment_provider_id="github:$gh_owner/$gh_reponame#comment-$comment_id"
						printf 'Record export of comment to GitHub #%s\n' "$issue_number" > "$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 any comments
				if test "$new_comments_exported" -gt 0
				then
					git update-ref "$ref" "$parent" "$issue_head"
				fi

				# Sync state and assignee
				case "$state" in
					open)   gh_state="open" ;;
					closed) gh_state="closed" ;;
					*)      gh_state="open" ;;
				esac

				gh api --method PATCH "/repos/$gh_owner/$gh_reponame/issues/$issue_number" \
					-f state="$gh_state" >/dev/null 2>&1 || true

				# Sync assignee (only if local issue has an opinion)
				if test "$has_assignee" -eq 1
				then
					if test -n "$assignee_email"
					then
						_sync_login="$(resolve_github_assignee "$assignee_email")"
						if test -n "$_sync_login"
						then
							_assignee_json="$(jq -n --arg a "$_sync_login" '{assignee: $a}')"
							printf '%s' "$_assignee_json" | gh api --method PATCH \
								"/repos/$gh_owner/$gh_reponame/issues/$issue_number" \
								--input - >/dev/null 2>&1 || {
								printf 'Warning: failed to sync assignee %s to #%s\n' "$_sync_login" "$issue_number" >&2
							}
						else
							printf 'Warning: could not resolve %s to GitHub user for %s\n' "$assignee_email" "$short_id" >&2
						fi
					else
						# Explicitly unassigned in local issue
						printf '{"assignee":null}' | gh api --method PATCH \
							"/repos/$gh_owner/$gh_reponame/issues/$issue_number" \
							--input - >/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_number" "$new_comments_exported"
				else
					printf 'Synced %s with #%s\n' "$short_id" "$issue_number"
				fi
				continue
				;;
			*)
				# Foreign Provider-ID (imported from a different repo), skip
				skipped=$((skipped + 1))
				printf 'Skipped %s (imported from %s)\n' "$short_id" "$existing_pid"
				continue
				;;
		esac
	fi

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

	# Build labels JSON array
	labels_json="[]"
	if test -n "$labels"
	then
		# Split on comma (with optional space after), then trim whitespace
		labels_json="$(printf '%s' "$labels" | jq -R 'split(",") | map(ltrimstr(" ") | rtrimstr(" "))')"
	fi

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

	# Build complete JSON payload
	if test -n "$assignee_login"
	then
		json_payload="$(jq -n \
			--arg title "$title" \
			--arg body "$body" \
			--argjson labels "$labels_json" \
			--arg assignee "$assignee_login" \
			'{title: $title, body: $body, labels: $labels, assignee: $assignee}')"
	else
		json_payload="$(jq -n \
			--arg title "$title" \
			--arg body "$body" \
			--argjson labels "$labels_json" \
			'{title: $title, body: $body, labels: $labels}')"
	fi

	# Create the issue on GitHub
	create_response="$(printf '%s' "$json_payload" | gh api --method POST "/repos/$gh_owner/$gh_reponame/issues" \
		--input - 2>&1)" || {
		echo "error: failed to create issue on GitHub: $create_response" >&2
		continue
	}

	issue_number="$(printf '%s' "$create_response" | jq -r '.number')"
	provider_id="github:$gh_owner/$gh_reponame#$issue_number"

	# Export comments (skip root, skip commits with trailers = metadata changes)
	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 that have any trailers (state changes, edits, provider-id records)
		cmt_trailers="$(git log -1 --format='%(trailers)' "$cmt" | sed '/^$/d')"
		if test -n "$cmt_trailers"
		then
			continue
		fi

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

		comment_text="$cmt_body"
		if test -n "$cmt_full"
		then
			comment_text="$comment_text

$cmt_full"
		fi

		# Post comment and capture response
		comment_response="$(gh api --method POST "/repos/$gh_owner/$gh_reponame/issues/$issue_number/comments" \
			-f body="$comment_text" 2>/dev/null)" || continue

		# Extract comment ID
		comment_id="$(printf '%s' "$comment_response" | jq -r '.id' 2>/dev/null)"

		if test -n "$comment_id" && test "$comment_id" != "null"
		then
			# Record Provider-Comment-ID and original commit SHA
			comment_provider_id="github:$gh_owner/$gh_reponame#comment-$comment_id"
			printf 'Record export of comment to GitHub #%s\n' "$issue_number" > "$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
		gh api --method PATCH "/repos/$gh_owner/$gh_reponame/issues/$issue_number" \
			-f state="closed" >/dev/null 2>&1 || true
	fi

	# Record Provider-ID locally by appending a child commit
	printf '%s\n' "Record export to GitHub #$issue_number" > "$tmpfile"
	git interpret-trailers --in-place --trailer "Provider-ID: $provider_id" "$tmpfile"

	# Use parent from comment chain if comments were exported, otherwise use issue_head
	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_number" "$title"
done < "$refs_file"

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