#!/bin/bash
# Run the complete release workflow: build, sign, promote, clean, sync

set -e

BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/lock-helpers.sh"
source "$BUILD_ROOT/helpers/basecamp-notifier.sh"

SYNC_REMOTE=""
SKIP_PROD_CHECK=false
DRY_RUN=false

print_header "Complete Release Workflow"

echo ""
print_info "This will run the complete release workflow:"
echo "  1. Build packages"
echo "  2. Sign packages"
echo "  3. Promote to production"
echo "  4. Clean old versions"
echo "  5. Update repository database"
echo "  6. Sync to remote"
echo ""

# Parse arguments
BUILD_ARGS=()
while [[ $# -gt 0 ]]; do
  case $1 in
  --arch)
    ARCH="$2"
    BUILD_ARGS+=("--arch" "$2")
    update_arch_paths
    shift 2
    ;;
  --mirror)
    MIRROR="$2"
    BUILD_ARGS+=("--mirror" "$2")
    update_arch_paths
    shift 2
    ;;
  --package)
    # Greedy like bin/build: consume every name up to the next --option.
    BUILD_ARGS+=("--package")
    shift
    while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do
      BUILD_ARGS+=("$1")
      shift
    done
    ;;
  --sync-remote)
    SYNC_REMOTE="$2"
    shift 2
    ;;
  --skip-prod-check)
    SKIP_PROD_CHECK=true
    shift
    ;;
  --dry-run)
    DRY_RUN=true
    BUILD_ARGS+=("--dry-run")
    shift
    ;;
  -h | --help)
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  --arch <arch>         Target architecture (x86_64 or aarch64, default: x86_64)"
    echo "  --mirror <mirror>     Mirror to use (edge, rc, or stable, default: edge)"
    echo "  --package <names>     Build only the specified package(s) (space-separated)"
    echo "  --sync-remote <path>  Rclone remote for sync (default: pkgs.omarchy.org:omarchy-pkgs)"
    echo "  --skip-prod-check     Skip production environment check during sync
  --dry-run             Show build plan only; do not sign/promote/clean/update/sync"
    echo "  -h, --help            Show this help message"
    echo ""
    echo "This script runs the complete workflow:"
    echo "  build → sign → promote → clean → sync"
    exit 0
    ;;
  *)
    print_error "Unknown option: $1"
    exit 1
    ;;
  esac
done

echo ""
print_info "Target architecture: $ARCH"
print_info "Mirror: $MIRROR"
print_info "Build workspace: $BUILD_OUTPUT_DIR"

# One channel mutation at a time: a timer firing mid-run or a second operator
# waits here instead of interleaving a partial publish.
if [[ "$DRY_RUN" != true ]]; then
  acquire_release_lock || exit 1
fi

# --- reporting ---------------------------------------------------------------

RELEASE_STARTED_AT=$(date +%s)
RELEASE_COMMIT=$(git -C "$BUILD_ROOT" rev-parse --short HEAD 2>/dev/null || echo "unknown")
RELEASE_COMMIT_SUBJECT=$(git -C "$BUILD_ROOT" log -1 --pretty=%s 2>/dev/null || echo "")
RELEASE_CONTEXT="Channel: <strong>$MIRROR</strong> · Arch: $ARCH · $(hostname -s 2>/dev/null || echo host)"
if [[ "$RELEASE_COMMIT" != "unknown" ]]; then
  RELEASE_CONTEXT+="<br>Commit: <code>$RELEASE_COMMIT</code> $(basecamp_html_escape <<<"$RELEASE_COMMIT_SUBJECT")"
fi

# Captured after the build step: promote MOVES these files out of build-output,
# so the list has to be taken while they are still there.
BUILT_FILES=""

# What the scheduled version check queued, when it was the one that asked for
# this run. Absent for a manual run, which is fine — the report just omits it.
QUEUED_PACKAGES=""
QUEUE_FILE=$(sync_queue_file "$MIRROR" "$ARCH")
# A queue written under the pre-architecture name belongs to x86_64.
if [[ "$ARCH" == "x86_64" && ! -s "$QUEUE_FILE" && -s "$(legacy_sync_queue_file "$MIRROR")" ]]; then
  QUEUE_FILE=$(legacy_sync_queue_file "$MIRROR")
fi
[[ -s "$QUEUE_FILE" ]] && QUEUED_PACKAGES=$(grep -c '' "$QUEUE_FILE")

if [[ "$DRY_RUN" != true ]]; then
  start_details="$RELEASE_CONTEXT"
  if [[ -n "$QUEUED_PACKAGES" && "$QUEUED_PACKAGES" != "0" ]]; then
    start_details+="<br><br><strong>$QUEUED_PACKAGES package(s) queued:</strong><br>"
    start_details+="$(head -25 "$QUEUE_FILE" | tr '\n' ' ' | basecamp_html_escape)"
    ((QUEUED_PACKAGES > 25)) && start_details+=" …and $((QUEUED_PACKAGES - 25)) more"
  fi
  notify_start "Release started: $MIRROR" "$start_details"
fi

# Step 1: Build
echo ""
if [[ "$DRY_RUN" == true ]]; then
  print_info "Step 1/6: Planning build..."
else
  print_info "Step 1/6: Building packages..."
fi
BUILD_RESULT_DIR=$(mktemp -d)
trap 'rm -rf "$BUILD_RESULT_DIR"' EXIT
build_status=0
OMARCHY_BUILD_RESULT_DIR="$BUILD_RESULT_DIR" "$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || build_status=$?
partial=false
if [[ "$build_status" == 2 && -f "$BUILD_RESULT_DIR/complete" && "$DRY_RUN" != true ]]; then
  if [[ "${OMARCHY_DEFER_RUNTIME_DEPS:-false}" == true ]]; then
    print_error "The deferred release pair must succeed together; nothing will be published"
    notify_error "Release failed: Incomplete release pair" "$RELEASE_CONTEXT"
    exit 1
  fi
  partial=true
  while IFS= read -r file; do
    [[ -f "$BUILD_OUTPUT_DIR/$file" ]] || {
      print_error "Completed build artifact is missing: $file"
      notify_error "Release failed: Missing completed artifact" "$RELEASE_CONTEXT"
      exit 1
    }
  done < "$BUILD_RESULT_DIR/artifacts"
  # Exclude partial split outputs or leftovers from failed builds. Moving
  # them out of the flat publication directory keeps them available for
  # diagnosis without handing them to sign/promote.
  unpublished=""
  for path in "$BUILD_OUTPUT_DIR"/*.pkg.tar.*; do
    [[ -f "$path" ]] || continue
    file=${path##*/}
    if ! grep -Fxq -- "${file%.sig}" "$BUILD_RESULT_DIR/artifacts"; then
      [[ -n "$unpublished" ]] || unpublished=$(mktemp -d "$BUILD_OUTPUT_DIR/.unpublished.XXXXXX")
      mv -- "$path" "$unpublished/"
    fi
  done
  print_warning "Some packages failed; publishing the completed packages before retrying the failures"
elif [[ "$build_status" != 0 ]]; then
  print_error "Build failed"
  notify_error "Release failed: Build step failed" "$RELEASE_CONTEXT"
  exit 1
fi

if [[ "$DRY_RUN" == true ]]; then
  echo ""
  print_success "Release dry run complete (build plan only)."
  exit 0
fi

BUILT_FILES=$(built_package_files "$BUILD_OUTPUT_DIR")
BUILT_COUNT=$(grep -c '' <<<"$BUILT_FILES")
[[ -z "$BUILT_FILES" ]] && BUILT_COUNT=0
print_info "Built $BUILT_COUNT package(s) this run"

if [[ "$partial" == true && "$BUILT_COUNT" == 0 ]]; then
  print_error "No completed packages to publish"
  notify_error "Release failed: No completed packages" "$RELEASE_CONTEXT"
  exit 1
fi

# Step 2: Sign
echo ""
print_info "Step 2/6: Signing packages..."
"$BUILD_ROOT/bin/sign" --arch "$ARCH" --mirror "$MIRROR" || {
  print_error "Signing failed"
  notify_error "Release failed: Signing step failed" "$RELEASE_CONTEXT"
  exit 1
}

# Step 3: Promote
echo ""
print_info "Step 3/6: Promoting to production..."
"$BUILD_ROOT/bin/promote-build" --arch "$ARCH" --mirror "$MIRROR" || {
  print_error "Promotion failed"
  notify_error "Release failed: Promotion step failed" "$RELEASE_CONTEXT"
  exit 1
}

# Step 4: Clean
echo ""
print_info "Step 4/6: Cleaning old versions..."
"$BUILD_ROOT/bin/clean-repo" --arch "$ARCH" --mirror "$MIRROR" || {
  print_error "Cleaning failed"
  notify_error "Release failed: Clean step failed" "$RELEASE_CONTEXT"
  exit 1
}

# Step 5: Update DB
echo ""
print_info "Step 5/6: Updating repository database..."
"$BUILD_ROOT/bin/update-repo" --arch "$ARCH" --mirror "$MIRROR" || {
  print_error "Database update failed"
  notify_error "Release failed: Database update step failed" "$RELEASE_CONTEXT"
  exit 1
}

# Step 6: Sync
echo ""
print_info "Step 6/6: Syncing to remote..."
SYNC_ARGS=("--mirror" "$MIRROR" "--arch" "$ARCH")
if [[ -n "$SYNC_REMOTE" ]]; then
  SYNC_ARGS+=("--remote" "$SYNC_REMOTE")
fi
if [[ "$SKIP_PROD_CHECK" == true ]]; then
  SYNC_ARGS+=("--skip-prod-check")
fi
"$BUILD_ROOT/bin/sync-repo" "${SYNC_ARGS[@]}" || {
  print_error "Sync failed"
  notify_error "Release failed: Sync step failed" "$RELEASE_CONTEXT"
  exit 1
}

duration=$(format_duration $(($(date +%s) - RELEASE_STARTED_AT)))
summary="$RELEASE_CONTEXT<br>Duration: $duration"
if ((BUILT_COUNT > 0)); then
  summary+="<br><br><strong>$BUILT_COUNT package(s) published:</strong>"
  summary+="$(format_package_list_html "$BUILT_FILES")"
  summary+="<br><br>Live at https://pkgs.omarchy.org/$MIRROR/$ARCH/"
  if [[ "$partial" == true ]]; then
    failed=$(tr '\n' ' ' < "$BUILD_RESULT_DIR/failed" | basecamp_html_escape)
    blocked=$(tr '\n' ' ' < "$BUILD_RESULT_DIR/blocked" | basecamp_html_escape)
    [[ -z "$failed" ]] || summary+="<br><br>Failed: $failed"
    [[ -z "$blocked" ]] || summary+="<br>Blocked by failed dependencies: $blocked"
    summary+="<br>Published packages will be skipped on retry; failed packages remain queued."
    notify_info "Partial release published: $MIRROR" "$summary"
  else
    notify_success "Release published: $MIRROR" "$summary"
  fi
else
  # Rare by construction: a release only runs when the version check queued
  # work, so publishing nothing means the check and the builder disagreed
  # about what needs building. Worth reporting for exactly that reason — if
  # these start recurring, something is flagging a package that never builds.
  summary+="<br>No packages needed building — the version check and the"
  summary+=" builder disagree about what is out of date."
  if [[ -n "$QUEUED_PACKAGES" && "$QUEUED_PACKAGES" != "0" ]]; then
    summary+="<br>Queued but not built: $(head -25 "$QUEUE_FILE" | tr '\n' ' ' | basecamp_html_escape)"
  fi
  notify_info "Release ran with nothing to publish: $MIRROR" "$summary"
fi

echo ""
if [[ "$partial" == true ]]; then
  print_warning "Completed packages published; unsuccessful packages remain for retry"
  # Preserve the scheduled queue and failure backoff. The next version
  # check/build compares against the updated repository and skips successes.
  exit 1
fi
print_success "Release workflow completed successfully!"
