#!/bin/sh
#
# portsync - POSIX-compliant script for scratchpkg that synchronizes ports and news

# Print errors and exit
die() {
	printf "ERROR: %s\n" "$*" >&2
	exit 1
}

# Check root permissions
[ "$(id -u)" = 0 ] || die "This operation needs root access!"

# Vars
PRN=0
RST=0
HELP=0
SYNC_REPOS=""
_seen_repos=""

# Print informational messages
msg() { printf "==> %s\n" "$1"; }
msgwarn() { printf "WARNING: %s\n" "$1" >&2; }

# Create directories if they don't exist
mkd() {
	[ -d "$1" ] || mkdir -pv "$1" || die "Failed to create directory: $1"
}

# Wrapper for git commands with specific paths
bare_git() {
	git --git-dir="$baredir/${portdir}.git" --work-tree="$worktree/$portdir" "$@"
}

# Show help message
usage() {
cat << 'EOF'
Usage:
  portsync [options]
  
Options:
  -p, --prune	        prune local git repository
  -r, --reset	        reset git repositories
  -s, --source <repos>  sync specific repos only (space-separated)
  -h, --help	        show this help message

Examples:
  portsync                 sync all repos
  portsync --source main   sync only 'main'
  portsync -s main custom  sync 'main' and 'custom'
  portsync -r -s main      reset and sync only 'main'
EOF
exit 0
}

# Parse command line options
parse_opts() {
	while [ "$1" ]; do
		case $1 in
			-p|--prune) PRN=1;;
			-r|--reset) RST=1;;
			-h|--help)  HELP=1;;
			-s|--source)
				shift
				while [ "$1" ]; do
					case $1 in
						-*) break;;
						 *) SYNC_REPOS="$SYNC_REPOS $1";;
					esac
					shift
				done
				continue
				;;
			*) die "Invalid options: $1";;
		esac
		shift
	done
}

# Clean old objects from git repository
prune_local() {
	msg "pruning old objects from ${portdir}..."
	# Expire all old commits and their objects
	bare_git reflog expire --expire-unreachable=now --all || {
		msgwarn "Failed to expire reflog for $portdir"
		return 1
	}
	# Remove old objects
	bare_git gc --quiet --prune=all || {
		msgwarn "Failed to garbage collect for $portdir"
		return 1
	}
	return 0
}

# Completely reset repositories
reset_repos() {
	printf "%s\n" "Removing REPOS..."
	while read -r _rr_dir _rr_url _rr_branch _; do
		case "$_rr_dir" in ""|"#"*) continue ;; esac
		_rr_portdir="${_rr_dir##*/}"

		# Skip local repositories without a URL (manually maintained)
		[ -z "$_rr_url" ] && {
			msg "Skipping local repo '$_rr_dir' (no URL)"
			continue
		}

		if [ -n "$SYNC_REPOS" ]; then
			case " $SYNC_REPOS " in
				*" $_rr_portdir "*) ;;
				*) continue ;;
			esac
		fi
		rm -rf "$baredir/${_rr_portdir}.git" "$_rr_dir" || \
			die "Failed to remove repo: $_rr_portdir"
	done < "$repo_file"
	msg "Repos reset completed!"
}

# Synchronize an individual repository
sync_repo() {
	# Usage:
	#   sync_repo <repo dir> <repo url> <repo branch>
	#
	# Arguments:
	#   $1 - Repository destination directory
	#   $2 - Remote repository URL
	#   $3 - Branch to synchronize (optional, default: main)

	repodir="$1"
	portdir="${repodir##*/}"
	repourl="$2"
	repobranch="${3:-main}"

	msg "fetching $2"

	# Ensure worktree directory exists
	mkd "$repodir" || die "Failed to create directory: $repodir"

	# Create bare repository if it doesn't exist
	if [ ! -d "$baredir/${portdir}.git" ]; then
		git init --quiet --bare "$baredir/${portdir}.git" || \
			die "Failed to initialize git repository for $portdir"
	fi

	# Ensure 'origin' remote exists and points to the correct URL
	bare_git remote set-url origin "$repourl" 2>/dev/null || \
		bare_git remote add origin "$repourl" || \
			die "Failed to configure remote for $portdir"

	# Shallow fetch (only latest commit), quiet mode
	bare_git fetch --quiet --depth=1 origin "$repobranch" || \
		die "Failed to fetch from $repourl (branch: $repobranch).\nTry running: 'portsync -r'"

	# Update local branch and working tree in one step, quiet mode
	bare_git checkout --quiet -B "$repobranch" FETCH_HEAD || \
		die "Failed to checkout branch $repobranch for $portdir.\nTry running: 'portsync -r'"

	# Optional pruning
	[ "$PRN" -eq 1 ] && prune_local

	msg "${portdir} ports synced in $repodir"
	return 0
}

# Check for news and prompt user
check_news() {
	# Check if portnews is installed
	command -v portnews >/dev/null 2>&1 || return 0
	printf "%s\n" "Syncing NEWS..."

	# Sync news from ports (runs as root, creates structure and sets permissions)
	portnews sync || {
		msgwarn "Failed to sync news"
		return 1
	}

	# Count unread news
	unread_count=0
	news_unread="/var/lib/scratchpkg/news/unread"

	if [ -d "$news_unread" ]; then
		for f in "$news_unread"/*.news; do
			[ -f "$f" ] && unread_count=$((unread_count + 1))
		done
	fi

	# If there are unread news, prompt user
	if [ "$unread_count" -gt 0 ]; then
		msg "You have $unread_count unread news item(s)."
		printf "Do you want to read them now? [y/N] "
		read -r answer

		case "$answer" in
			y|Y|yes|YES)
				printf "\n"
				portnews unread
				printf "\n%s\n" "Use 'portnews' to launch TUI mode"
				printf "%s\n" "Use 'portnews N' to read a specific news item"
				printf "%s\n" "Use 'portnews mv read N' to mark as read"
				exit 1
				;;
			*)
				printf "%s\n" "You can read them later with 'portnews' or 'scratch news'"
				;;
		esac
	fi
}

# ====== MAIN SCRIPT ======

# Parse command line options
parse_opts "$@"

# Show help if requested
[ "$HELP" -eq 1 ] && usage

# Define working directories
baredir="/var/cache/scratchpkg/repos"
mkd "$baredir" || die "Failed to create cache directory"
worktree="/usr/ports"
mkd "$worktree" || die "Failed to create worktree directory"

# Check that configuration file exists
repo_file="/etc/scratchpkg.repo"
[ -e "$repo_file" ] || die "Missing repo file: $repo_file"

# Reset repositories if requested
[ "$RST" -eq 1 ] && reset_repos

# Process each repository from configuration file
printf "%s\n" "Syncing PORTS..."
while read -r repodir repourl repobranch _; do
	case "$repodir" in ""|"#"*) continue ;; esac
	_portdir="${repodir##*/}"
	if [ -n "$SYNC_REPOS" ]; then
		case " $SYNC_REPOS " in
			*" $_portdir "*) _seen_repos="$_seen_repos $_portdir";;
			*) continue ;;
		esac
	fi
	# Skip entries without a URL (local repositories)
	[ -n "$repourl" ] || { msgwarn "No URL for repo '$repodir', skipping."; continue; }
	sync_repo "$repodir" "$repourl" "${repobranch:-main}" || \
		die "Sync failed for repository $repodir"
done < "$repo_file"

# Warn about any requested repositories that were not found in the config file
if [ -n "$SYNC_REPOS" ]; then
	for _req in $SYNC_REPOS; do
		case " $_seen_repos " in
			*" $_req "*) ;;
			*) msgwarn "Repo '$_req' not found in $repo_file" ;;
		esac
	done
fi

# Check for news after all repos are synced
check_news

exit 0
