#!/usr/bin/env bash
#
# Code sign a macOS .app bundle for distribution
# Usage: codesign_mac_app.sh <app_path> [entitlements_path]
#
# Environment variables:
#   SIGNING_IDENTITY - The signing identity (default: "Developer ID Application")
#   KEYCHAIN_PASSWORD - Password to unlock the keychain (optional, CI only)
#   KEYCHAIN_NAME - Name of the keychain containing the signing certificate (optional, CI only)
#                   When set, looks for certificate in this specific keychain.
#                   When not set, searches all keychains (local dev mode).

set -euxo pipefail

APP_PATH="$1"
ENTITLEMENTS_PATH="${2:-}"
SIGNING_IDENTITY="${SIGNING_IDENTITY:-Developer ID Application}"
# KEYCHAIN_NAME is set by CI workflow - don't set a default here so we can detect if we're in CI

if [ ! -d "$APP_PATH" ]; then
    echo "ERROR: App not found at $APP_PATH"
    exit 1
fi

# Unlock keychain if password and keychain name are provided (CI environment)
if [ -n "${KEYCHAIN_PASSWORD:-}" ] && [ -n "${KEYCHAIN_NAME:-}" ]; then
    security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME" || true
fi

# Get the SHA-1 hash of the signing certificate
# Using the hash avoids "ambiguous" errors when the same identity exists in multiple keychains
# We look in the build keychain specifically if KEYCHAIN_NAME is set (CI environment)
# Otherwise fall back to searching all keychains (local dev)

if [ -n "${KEYCHAIN_NAME:-}" ]; then
    # CI environment: look for certificate in the build keychain specifically
    KEYCHAIN_PATH="$HOME/Library/Keychains/${KEYCHAIN_NAME}-db"
    echo "Looking for signing identity in build keychain: $KEYCHAIN_PATH"
    echo "Available identities in build keychain:"
    security find-identity -v -p codesigning "$KEYCHAIN_PATH"

    CERT_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep -E '^\s+[0-9]+\)' | head -1 | awk '{print $2}')
else
    # Local dev: search all keychains
    echo "Available codesigning identities:"
    security find-identity -v -p codesigning

    CERT_HASH=$(security find-identity -v -p codesigning | grep -E '^\s+[0-9]+\)' | head -1 | awk '{print $2}')
fi

if [ -z "$CERT_HASH" ]; then
    echo "ERROR: No valid signing identity found"
    exit 1
fi
echo "Using certificate hash: $CERT_HASH"

# Use the hash as the signing identity to avoid ambiguity
SIGNING_IDENTITY="$CERT_HASH"

echo "=== Signing nested components first ==="

# Sign all dylibs
find "$APP_PATH" -name "*.dylib" -print0 | while IFS= read -r -d '' item; do
    echo "Signing dylib: $item"
    codesign --force --verify --verbose --timestamp --options runtime \
        --sign "$SIGNING_IDENTITY" "$item"
done

# Sign all bundles (plugins)
find "$APP_PATH" -name "*.bundle" -print0 | while IFS= read -r -d '' item; do
    echo "Signing bundle: $item"
    codesign --force --verify --verbose --timestamp --options runtime \
        --sign "$SIGNING_IDENTITY" "$item"
done

# Sign XPC services (but skip ones inside Sparkle.framework - they're already signed)
find "$APP_PATH" -name "*.xpc" -print0 | while IFS= read -r -d '' item; do
    if [[ "$item" == *"Sparkle.framework"* ]]; then
        echo "Skipping Sparkle XPC service (pre-signed): $item"
        continue
    fi
    echo "Signing XPC service: $item"
    codesign --force --verify --verbose --timestamp --options runtime \
        --sign "$SIGNING_IDENTITY" "$item"
done

# Sign nested apps (but skip ones inside Sparkle.framework - they're already signed)
find "$APP_PATH" -path "*/Frameworks/*.app" -print0 | while IFS= read -r -d '' item; do
    if [[ "$item" == *"Sparkle.framework"* ]]; then
        echo "Skipping Sparkle nested app (pre-signed): $item"
        continue
    fi
    echo "Signing nested app: $item"
    codesign --force --verify --verbose --timestamp --options runtime \
        --sign "$SIGNING_IDENTITY" "$item"
done

# Sign standalone executables inside frameworks (but skip Sparkle.framework internals)
find "$APP_PATH" -path "*/Frameworks/*/Versions/*/Autoupdate" -type f -print0 | while IFS= read -r -d '' item; do
    if [[ "$item" == *"Sparkle.framework"* ]]; then
        echo "Skipping Sparkle executable (pre-signed): $item"
        continue
    fi
    echo "Signing executable: $item"
    codesign --force --verify --verbose --timestamp --options runtime \
        --sign "$SIGNING_IDENTITY" "$item"
done

# Sign all frameworks (after their contents are signed)
# Use --deep for Sparkle.framework to handle its XPC services
find "$APP_PATH" -name "*.framework" -print0 | while IFS= read -r -d '' item; do
    if [[ "$item" == *"Sparkle.framework" ]]; then
        echo "Signing Sparkle framework with --deep: $item"
        codesign --deep --force --verify --verbose --timestamp --options runtime \
            --sign "$SIGNING_IDENTITY" "$item"
    else
        echo "Signing framework: $item"
        codesign --force --verify --verbose --timestamp --options runtime \
            --sign "$SIGNING_IDENTITY" "$item"
    fi
done

echo "=== Signing main app bundle ==="

if [ -n "$ENTITLEMENTS_PATH" ] && [ -f "$ENTITLEMENTS_PATH" ]; then
    echo "Using entitlements: $ENTITLEMENTS_PATH"
    codesign --force --verify --verbose --timestamp --options runtime \
        --entitlements "$ENTITLEMENTS_PATH" \
        --sign "$SIGNING_IDENTITY" "$APP_PATH"
else
    codesign --force --verify --verbose --timestamp --options runtime \
        --sign "$SIGNING_IDENTITY" "$APP_PATH"
fi

echo "=== Verifying signature ==="
codesign --verify --verbose=4 "$APP_PATH"

echo "=== Checking Gatekeeper assessment ==="
spctl --assess --type exec -v "$APP_PATH" || echo "Note: Gatekeeper may reject until notarized"

echo "Code signing complete: $APP_PATH"
