#!/usr/bin/env bash
set -euo pipefail

repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"

platform="${1:-all}"
build_number="${BUILD_NUMBER:-${RELEASE_BUILD_NUMBER:-${RELEASE_REF:-manual}}}"
out_root="${BURROW_RELEASE_OUT:-${repo_root}/dist/builds/${build_number}}"
apple_root="${out_root}/apple"
archives_root="${apple_root}/archives"
derived_data="${BURROW_DERIVED_DATA_PATH:-${repo_root}/.derived-data/apple}"
source_packages="${BURROW_SWIFTPM_CACHE_PATH:-${XDG_CACHE_HOME:-${HOME}/.cache}/burrow/swiftpm}"
project="Apple/Burrow.xcodeproj"
scheme="${BURROW_APPLE_SCHEME:-App}"
bundle_id="${BURROW_APPLE_BUNDLE_ID:-net.burrow.app}"
network_extension_bundle_id="${BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID:-${bundle_id}.network}"
signing_ready="${BURROW_APPLE_SIGNING_READY:-0}"
signing_mode="${BURROW_APPLE_SIGNING_MODE:-keychain}"
require_signing="${BURROW_APPLE_REQUIRE_SIGNING:-false}"
sparkle_feed_url="${BURROW_SPARKLE_FEED_URL:-https://releases.burrow.net/sparkle/appcast.xml}"
sparkle_public_ed_key="${BURROW_SPARKLE_PUBLIC_ED_KEY:-Myv9ZNZT6YGKMtMezh52ra4WqaeEKc4VlvVU0evhJeI=}"
sparkle_sign_with_kms="${BURROW_SPARKLE_SIGN_WITH_KMS:-false}"
notarize_macos="${BURROW_NOTARIZE_MACOS:-false}"

mkdir -p "$apple_root" "$archives_root" "$derived_data" "$source_packages"

truthy() {
  case "${1:-}" in
    1|true|TRUE|True|yes|YES|Yes|y|Y|on|ON|On) return 0 ;;
    *) return 1 ;;
  esac
}

sha256_file() {
  local file="$1"
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$file" > "${file}.sha256"
  else
    sha256sum "$file" > "${file}.sha256"
  fi
}

safe_profile_slug() {
  python3 - "$1" <<'PY'
import re
import sys

print(re.sub(r"[^A-Za-z0-9]", "", sys.argv[1]).lower())
PY
}

profile_for() {
  local identifier="$1"
  local suffix="$2"
  local slug path
  slug="$(safe_profile_slug "$identifier")"
  for path in \
    ".forgejo/actions/export/${slug}${suffix}.provisionprofile" \
    ".forgejo/actions/export/${slug}${suffix}.mobileprovision" \
    "Apple/Profiles/${slug}${suffix}.provisionprofile" \
    "Apple/Profiles/${slug}${suffix}.mobileprovision"; do
    if [[ -s "$path" ]]; then
      printf '%s\n' "$path"
      return 0
    fi
  done
  return 1
}

generate_entitlements_from_profile() {
  local profile="$1"
  local output="$2"
  Scripts/apple/profile-entitlements.py "$profile" --output "$output"
}

apple_signing_available() {
  if [[ "$signing_mode" == "google-kms" || "$signing_mode" == "google-kms-staged" || "$signing_mode" == "kms" ]]; then
    return 0
  fi
  [[ "$signing_ready" == "1" ]]
}

kms_sign_bundle() {
  local family="$1"
  local bundle_path="$2"
  local entitlements="$3"
  local runtime_flag=()
  if [[ "$family" == "developer-id" ]]; then
    runtime_flag=(--runtime)
  fi
  Scripts/apple/sign-with-google-kms.sh \
    --family "$family" \
    --path "$bundle_path" \
    --entitlements "$entitlements" \
    "${runtime_flag[@]}"
}

kms_sign_file() {
  local family="$1"
  local file_path="$2"
  local runtime_flag=()
  if [[ "$family" == "developer-id" ]]; then
    runtime_flag=(--runtime)
  fi
  Scripts/apple/sign-with-google-kms.sh \
    --family "$family" \
    --path "$file_path" \
    "${runtime_flag[@]}"
}

write_version() {
  mkdir -p Apple/Configuration
  printf 'CURRENT_PROJECT_VERSION = %s\n' "$build_number" > Apple/Configuration/Version.xcconfig
}

xcode_common_args=(
  -project "$project"
  -scheme "$scheme"
  -configuration Release
  -derivedDataPath "$derived_data"
  -clonedSourcePackagesDirPath "$source_packages"
  CURRENT_PROJECT_VERSION="$build_number"
  APP_BUNDLE_IDENTIFIER="$bundle_id"
  NETWORK_EXTENSION_BUNDLE_IDENTIFIER="$network_extension_bundle_id"
  BURROW_SPARKLE_FEED_URL="$sparkle_feed_url"
  BURROW_SPARKLE_PUBLIC_ED_KEY="$sparkle_public_ed_key"
)

xcode_auth_args=()
if [[ -n "${ASC_API_KEY_ID:-}" && -n "${ASC_API_ISSUER_ID:-}" && -n "${ASC_API_KEY_PATH:-}" && -f "${ASC_API_KEY_PATH:-}" ]]; then
  xcode_auth_args+=(
    -authenticationKeyID "$ASC_API_KEY_ID"
    -authenticationKeyIssuerID "$ASC_API_ISSUER_ID"
    -authenticationKeyPath "$ASC_API_KEY_PATH"
  )
fi

if [[ -n "${BURROW_APPLE_KEYCHAIN_PATH:-}" ]]; then
  xcode_common_args+=(OTHER_CODE_SIGN_FLAGS="--keychain ${BURROW_APPLE_KEYCHAIN_PATH}")
fi

require_xcodebuild() {
  if ! command -v xcodebuild >/dev/null 2>&1; then
    echo "xcodebuild is required for Apple release artifacts" >&2
    exit 1
  fi
  xcodebuild -version
  xcrun --find swiftc >/dev/null
}

unsigned_note() {
  local platform_name="$1"
  local artifact_name="$2"
  {
    echo "Burrow ${platform_name} signing assets were not available."
    echo
    echo "The lane built an unsigned validation artifact instead of an uploadable release artifact."
    echo "Seal the Apple release credentials with agenix and let CI sync provisioning profiles from App Store Connect to produce ${artifact_name}."
  } > "${apple_root}/README-${platform_name}-unsigned.txt"
}

ensure_signing_or_note() {
  local platform_name="$1"
  local artifact_name="$2"
  if apple_signing_available; then
    return 0
  fi
  if truthy "$require_signing"; then
    echo "Apple signing is required for ${platform_name}, but signing assets are not configured." >&2
    exit 1
  fi
  unsigned_note "$platform_name" "$artifact_name"
  return 1
}

write_export_options() {
  local method="$1"
  local path="$2"
  cat > "$path" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>method</key>
  <string>${method}</string>
  <key>destination</key>
  <string>export</string>
  <key>signingStyle</key>
  <string>automatic</string>
  <key>stripSwiftSymbols</key>
  <true/>
  <key>teamID</key>
  <string>${DEVELOPMENT_TEAM:-P6PV2R9443}</string>
</dict>
</plist>
EOF
}

build_ios_unsigned() {
  local product_dir zip_path
  xcodebuild build \
    "${xcode_common_args[@]}" \
    -destination "generic/platform=iOS Simulator" \
    ARCHS=arm64 \
    ONLY_ACTIVE_ARCH=YES \
    CODE_SIGNING_ALLOWED=NO \
    CODE_SIGNING_REQUIRED=NO

  product_dir="${derived_data}/Build/Products/Release-iphonesimulator"
  zip_path="${apple_root}/Burrow-iOS-simulator-${build_number}.unsigned.zip"
  if [[ -d "${product_dir}/Burrow.app" ]]; then
    (cd "$product_dir" && zip -qr "$zip_path" Burrow.app)
    sha256_file "$zip_path"
  fi
}

build_ios_release() {
  local archive_path export_dir export_options ipa
  if ! ensure_signing_or_note "iOS" "Burrow-iOS-${build_number}.ipa"; then
    build_ios_unsigned
    return 0
  fi
  if [[ "$signing_mode" == "google-kms-staged" ]]; then
    build_ios_kms_staged
    return 0
  fi
  if [[ "$signing_mode" == "google-kms" || "$signing_mode" == "kms" ]]; then
    build_ios_kms_release
    return 0
  fi

  archive_path="${archives_root}/Burrow-iOS-${build_number}.xcarchive"
  export_dir="${apple_root}/ios-export"
  export_options="${apple_root}/ExportOptions-iOS.plist"
  write_export_options "app-store-connect" "$export_options"

  xcodebuild archive \
    "${xcode_common_args[@]}" \
    -destination "generic/platform=iOS" \
    -archivePath "$archive_path" \
    ARCHS=arm64 \
    -allowProvisioningUpdates \
    "${xcode_auth_args[@]}"

  xcodebuild -exportArchive \
    -archivePath "$archive_path" \
    -exportPath "$export_dir" \
    -exportOptionsPlist "$export_options" \
    -allowProvisioningUpdates \
    "${xcode_auth_args[@]}"

  ipa="$(find "$export_dir" -maxdepth 1 -type f -name '*.ipa' | sort | head -n 1 || true)"
  if [[ -z "$ipa" ]]; then
    echo "iOS export completed but did not produce an IPA" >&2
    exit 1
  fi
  cp "$ipa" "${apple_root}/Burrow-iOS-${build_number}.ipa"
  sha256_file "${apple_root}/Burrow-iOS-${build_number}.ipa"
}

build_ios_kms_staged() {
  local product_dir source_app stage_dir ipa
  xcodebuild build \
    "${xcode_common_args[@]}" \
    -destination "generic/platform=iOS" \
    ARCHS=arm64 \
    ONLY_ACTIVE_ARCH=YES \
    SKIP_INSTALL=NO \
    CODE_SIGNING_ALLOWED=NO \
    CODE_SIGNING_REQUIRED=NO \
    CODE_SIGN_IDENTITY=

  product_dir="${derived_data}/Build/Products/Release-iphoneos"
  source_app="${product_dir}/Burrow.app"
  if [[ ! -d "$source_app" ]]; then
    echo "iOS build completed but did not produce Burrow.app" >&2
    exit 1
  fi

  stage_dir="${apple_root}/ios-unsigned/Payload"
  rm -rf "$stage_dir"
  mkdir -p "$stage_dir"
  ditto "$source_app" "${stage_dir}/Burrow.app"
  ipa="${apple_root}/Burrow-iOS-${build_number}.unsigned.ipa"
  rm -f "$ipa"
  (cd "${apple_root}/ios-unsigned" && zip -qry "$ipa" Payload)
  sha256_file "$ipa"
}

build_ios_kms_release() {
  local product_dir source_app stage_dir app ext app_profile ext_profile app_entitlements ext_entitlements ipa
  xcodebuild build \
    "${xcode_common_args[@]}" \
    -destination "generic/platform=iOS" \
    ARCHS=arm64 \
    ONLY_ACTIVE_ARCH=YES \
    SKIP_INSTALL=NO \
    CODE_SIGNING_ALLOWED=NO \
    CODE_SIGNING_REQUIRED=NO \
    CODE_SIGN_IDENTITY=

  product_dir="${derived_data}/Build/Products/Release-iphoneos"
  source_app="${product_dir}/Burrow.app"
  if [[ ! -d "$source_app" ]]; then
    echo "iOS build completed but did not produce Burrow.app" >&2
    exit 1
  fi

  stage_dir="${apple_root}/ios-kms/Payload"
  rm -rf "$stage_dir"
  mkdir -p "$stage_dir"
  ditto "$source_app" "${stage_dir}/Burrow.app"
  app="${stage_dir}/Burrow.app"
  ext="${app}/PlugIns/BurrowNetworkExtension.appex"
  if [[ ! -d "$ext" ]]; then
    echo "iOS app is missing BurrowNetworkExtension.appex" >&2
    exit 1
  fi

  app_profile="$(profile_for "$bundle_id" "appstore")" || {
    echo "missing iOS App Store provisioning profile for ${bundle_id}" >&2
    exit 1
  }
  ext_profile="$(profile_for "$network_extension_bundle_id" "appstore")" || {
    echo "missing iOS App Store provisioning profile for ${network_extension_bundle_id}" >&2
    exit 1
  }
  cp "$app_profile" "${app}/embedded.mobileprovision"
  cp "$ext_profile" "${ext}/embedded.mobileprovision"

  app_entitlements="${apple_root}/ios-kms/app-entitlements.plist"
  ext_entitlements="${apple_root}/ios-kms/extension-entitlements.plist"
  generate_entitlements_from_profile "$app_profile" "$app_entitlements"
  generate_entitlements_from_profile "$ext_profile" "$ext_entitlements"

  rm -rf "${app}/_CodeSignature" "${ext}/_CodeSignature"
  kms_sign_bundle ios "$ext" "$ext_entitlements"
  kms_sign_bundle ios "$app" "$app_entitlements"

  ipa="${apple_root}/Burrow-iOS-${build_number}.ipa"
  rm -f "$ipa"
  (cd "${apple_root}/ios-kms" && zip -qry "$ipa" Payload)
  sha256_file "$ipa"
}

build_macos_unsigned() {
  local product_dir zip_path
  xcodebuild build \
    "${xcode_common_args[@]}" \
    -destination "platform=macOS" \
    CODE_SIGNING_ALLOWED=NO \
    CODE_SIGNING_REQUIRED=NO

  product_dir="${derived_data}/Build/Products/Release"
  zip_path="${apple_root}/Burrow-macOS-${build_number}.unsigned.zip"
  if [[ -d "${product_dir}/Burrow.app" ]]; then
    ditto -c -k --keepParent "${product_dir}/Burrow.app" "$zip_path"
    sha256_file "$zip_path"
  fi
}

build_macos_release() {
  local archive_path export_dir export_options app dmg channel sparkle_dir length pubdate url
  if ! ensure_signing_or_note "macOS" "Burrow-macOS-${build_number}.dmg"; then
    build_macos_unsigned
    return 0
  fi
  if [[ "$signing_mode" == "google-kms-staged" ]]; then
    build_macos_kms_staged
    return 0
  fi
  if [[ "$signing_mode" == "google-kms" || "$signing_mode" == "kms" ]]; then
    build_macos_kms_release
    return 0
  fi

  archive_path="${archives_root}/Burrow-macOS-${build_number}.xcarchive"
  export_dir="${apple_root}/macos-export"
  export_options="${apple_root}/ExportOptions-macOS.plist"
  write_export_options "${BURROW_MACOS_EXPORT_METHOD:-developer-id}" "$export_options"

  xcodebuild archive \
    "${xcode_common_args[@]}" \
    -destination "generic/platform=macOS" \
    -archivePath "$archive_path" \
    -allowProvisioningUpdates \
    "${xcode_auth_args[@]}"

  xcodebuild -exportArchive \
    -archivePath "$archive_path" \
    -exportPath "$export_dir" \
    -exportOptionsPlist "$export_options" \
    -allowProvisioningUpdates \
    "${xcode_auth_args[@]}"

  app="$(find "$export_dir" -maxdepth 2 -type d -name 'Burrow.app' | sort | head -n 1 || true)"
  if [[ -z "$app" ]]; then
    echo "macOS export completed but did not produce Burrow.app" >&2
    exit 1
  fi

  dmg="${apple_root}/Burrow-macOS-${build_number}.dmg"
  rm -f "$dmg"
  hdiutil create -volname "Burrow ${build_number}" -srcfolder "$app" -ov -format UDZO "$dmg"
  sha256_file "$dmg"

  channel="${SPARKLE_CHANNEL:-build-${build_number}}"
  sparkle_dir="${out_root}/sparkle/${channel}"
  mkdir -p "$sparkle_dir"
  cp "$dmg" "$sparkle_dir/"
  length="$(wc -c < "$dmg" | tr -d ' ')"
  pubdate="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')"
  url="${SPARKLE_DOWNLOAD_PREFIX:-https://releases.burrow.net/sparkle}/${channel}/$(basename "$dmg")"
  cat > "${sparkle_dir}/appcast.xml" <<EOF
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
  <channel>
    <title>Burrow ${channel}</title>
    <item>
      <title>Burrow ${build_number}</title>
      <pubDate>${pubdate}</pubDate>
      <sparkle:version>${build_number}</sparkle:version>
      <sparkle:shortVersionString>0.1</sparkle:shortVersionString>
      <enclosure url="${url}" sparkle:version="${build_number}" length="${length}" type="application/octet-stream" />
    </item>
  </channel>
</rss>
EOF

  if truthy "$sparkle_sign_with_kms"; then
    Scripts/sparkle/sign-appcast-kms.py \
      --appcast "${sparkle_dir}/appcast.xml" \
      --artifact-dir "$sparkle_dir"
  fi
}

build_macos_kms_staged() {
  local product_dir source_app zip_path
  xcodebuild build \
    "${xcode_common_args[@]}" \
    -destination "platform=macOS" \
    CODE_SIGNING_ALLOWED=NO \
    CODE_SIGNING_REQUIRED=NO \
    CODE_SIGN_IDENTITY=

  product_dir="${derived_data}/Build/Products/Release"
  source_app="${product_dir}/Burrow.app"
  if [[ ! -d "$source_app" ]]; then
    echo "macOS build completed but did not produce Burrow.app" >&2
    exit 1
  fi

  zip_path="${apple_root}/Burrow-macOS-${build_number}.unsigned.zip"
  rm -f "$zip_path"
  ditto -c -k --keepParent "$source_app" "$zip_path"
  sha256_file "$zip_path"
}

write_sparkle_appcast() {
  local dmg="$1"
  local channel sparkle_dir length pubdate url
  channel="${SPARKLE_CHANNEL:-build-${build_number}}"
  sparkle_dir="${out_root}/sparkle/${channel}"
  mkdir -p "$sparkle_dir"
  cp "$dmg" "$sparkle_dir/"
  length="$(wc -c < "$dmg" | tr -d ' ')"
  pubdate="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')"
  url="${SPARKLE_DOWNLOAD_PREFIX:-https://releases.burrow.net/sparkle}/${channel}/$(basename "$dmg")"
  cat > "${sparkle_dir}/appcast.xml" <<EOF
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
  <channel>
    <title>Burrow ${channel}</title>
    <item>
      <title>Burrow ${build_number}</title>
      <pubDate>${pubdate}</pubDate>
      <sparkle:version>${build_number}</sparkle:version>
      <sparkle:shortVersionString>0.1</sparkle:shortVersionString>
      <enclosure url="${url}" sparkle:version="${build_number}" length="${length}" type="application/octet-stream" />
    </item>
  </channel>
</rss>
EOF

  if truthy "$sparkle_sign_with_kms"; then
    Scripts/sparkle/sign-appcast-kms.py \
      --appcast "${sparkle_dir}/appcast.xml" \
      --artifact-dir "$sparkle_dir"
  fi
}

notarize_dmg_if_requested() {
  local dmg="$1"
  local key_json
  if ! truthy "$notarize_macos"; then
    return 0
  fi
  if [[ -z "${ASC_API_KEY_ID:-}" || -z "${ASC_API_ISSUER_ID:-}" || -z "${ASC_API_KEY_PATH:-}" ]]; then
    echo "macOS notarization requires ASC_API_KEY_ID, ASC_API_ISSUER_ID, and ASC_API_KEY_PATH" >&2
    exit 1
  fi
  key_json="${apple_root}/notary-api-key.json"
  rcodesign encode-app-store-connect-api-key \
    "$ASC_API_ISSUER_ID" \
    "$ASC_API_KEY_ID" \
    "$ASC_API_KEY_PATH" \
    "$key_json"
  rcodesign notary-submit --api-key-file "$key_json" --wait --staple "$dmg"
  rm -f "$key_json"
}

build_macos_kms_release() {
  local product_dir source_app app ext app_profile ext_profile app_entitlements ext_entitlements dmg
  xcodebuild build \
    "${xcode_common_args[@]}" \
    -destination "platform=macOS" \
    CODE_SIGNING_ALLOWED=NO \
    CODE_SIGNING_REQUIRED=NO \
    CODE_SIGN_IDENTITY=

  product_dir="${derived_data}/Build/Products/Release"
  source_app="${product_dir}/Burrow.app"
  if [[ ! -d "$source_app" ]]; then
    echo "macOS build completed but did not produce Burrow.app" >&2
    exit 1
  fi

  app="${apple_root}/macos-kms/Burrow.app"
  rm -rf "$app"
  mkdir -p "$(dirname "$app")"
  ditto "$source_app" "$app"
  ext="${app}/Contents/PlugIns/BurrowNetworkExtension.appex"

  app_profile="$(profile_for "$bundle_id" "developerid")" || {
    echo "missing macOS Developer ID provisioning profile for ${bundle_id}" >&2
    exit 1
  }
  cp "$app_profile" "${app}/Contents/embedded.provisionprofile"
  app_entitlements="${apple_root}/macos-kms/app-entitlements.plist"
  generate_entitlements_from_profile "$app_profile" "$app_entitlements"

  if [[ -d "$ext" ]]; then
    ext_profile="$(profile_for "$network_extension_bundle_id" "developerid")" || {
      echo "missing macOS Developer ID provisioning profile for ${network_extension_bundle_id}" >&2
      exit 1
    }
    cp "$ext_profile" "${ext}/Contents/embedded.provisionprofile"
    ext_entitlements="${apple_root}/macos-kms/extension-entitlements.plist"
    generate_entitlements_from_profile "$ext_profile" "$ext_entitlements"
    rm -rf "${ext}/Contents/_CodeSignature"
    kms_sign_bundle developer-id "$ext" "$ext_entitlements"
  fi

  rm -rf "${app}/Contents/_CodeSignature"
  kms_sign_bundle developer-id "$app" "$app_entitlements"

  dmg="${apple_root}/Burrow-macOS-${build_number}.dmg"
  rm -f "$dmg"
  hdiutil create -volname "Burrow ${build_number}" -srcfolder "$app" -ov -format UDZO "$dmg"
  kms_sign_file developer-id "$dmg"
  notarize_dmg_if_requested "$dmg"
  sha256_file "$dmg"
  write_sparkle_appcast "$dmg"
}

require_xcodebuild
write_version

case "$platform" in
  all)
    build_ios_release
    build_macos_release
    ;;
  ios)
    build_ios_release
    ;;
  macos)
    build_macos_release
    ;;
  *)
    echo "unknown Apple release platform: $platform" >&2
    exit 64
    ;;
esac

find "$out_root" -type f -print | sort
