#!/bin/bash
set -euo pipefail

# MacSync — macOS client installer.
# Builds Swift app, creates app bundle, installs LaunchAgent com.lilith.mac-sync.
#
# Usage (run on plum):
#   ./install.sh [SERVER_URL]
#   SERVER_URL defaults to http://10.0.0.11:3201

APP_NAME="MacSyncApp"
BUNDLE_ID="com.lilith.mac-sync"
DISPLAY_NAME="MacSync"
INSTALL_DIR="$HOME/Applications"
APP_BUNDLE="$INSTALL_DIR/$DISPLAY_NAME.app"
APP_SUPPORT_DIR="$HOME/Library/Application Support/$DISPLAY_NAME"
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
PLIST_FILE="$LAUNCH_AGENTS_DIR/$BUNDLE_ID.plist"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MAC_SYNC_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

print_step()    { echo -e "${GREEN}▸${NC} $1"; }
print_info()    { echo -e "${BLUE}ℹ${NC} $1"; }
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
print_error()   { echo -e "${RED}✗${NC} $1"; }
print_success() { echo -e "${GREEN}✓${NC} $1"; }

echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}  MacSync — Unified macOS Sync Agent Installer${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""

SERVER_URL="${1:-http://10.0.0.11:3201}"

check_macos() {
    if [[ "$OSTYPE" != "darwin"* ]]; then
        print_error "This script is macOS only"
        exit 1
    fi
}

check_swift() {
    if ! command -v swift &>/dev/null; then
        print_error "Swift not found — install Xcode or Command Line Tools: xcode-select --install"
        exit 1
    fi
    print_info "Swift: $(swift --version | head -n1)"
}

stop_existing_agent() {
    print_step "Stopping existing agent (if running)..."
    if [[ -f "$PLIST_FILE" ]]; then
        launchctl unload "$PLIST_FILE" 2>/dev/null || true
    fi
    pkill -x "$APP_NAME" 2>/dev/null || true
    sleep 1
}

build_application() {
    print_step "Building Swift application (release)..."
    cd "$MAC_SYNC_ROOT"
    rm -rf .build
    if swift build -c release; then
        print_success "Build successful"
    else
        print_error "Swift build failed"
        exit 1
    fi
}

install_binary() {
    print_step "Installing app bundle..."

    local binary_src="$MAC_SYNC_ROOT/.build/release/$APP_NAME"
    if [[ ! -f "$binary_src" ]]; then
        print_error "Binary not found: $binary_src"
        exit 1
    fi

    local macos_dir="$APP_BUNDLE/Contents/MacOS"
    local resources_dir="$APP_BUNDLE/Contents/Resources"
    local webapp_dir="$resources_dir/webapp"

    mkdir -p "$macos_dir" "$resources_dir" "$webapp_dir"
    # Clean stale resource bundles from previous installs
    rm -rf "$macos_dir"/*.bundle "$resources_dir"/*.bundle

    cp "$binary_src" "$macos_dir/$APP_NAME"
    chmod +x "$macos_dir/$APP_NAME"

    # Copy SPM resource bundles (e.g. tray-resources_LilithTrayResources.bundle)
    local build_dir="$MAC_SYNC_ROOT/.build/release"
    for bundle in "$build_dir"/*.bundle; do
        [[ -d "$bundle" ]] || continue
        cp -R "$bundle" "$resources_dir/$(basename "$bundle")"
        print_info "Bundled: $(basename "$bundle")"
    done

    # Bundle Vite webapp dist — pre-built by deploy-remote.sh, or build locally
    local web_dist="$MAC_SYNC_ROOT/web/dist"
    if [[ -d "$web_dist" ]]; then
        cp -R "$web_dist/"* "$webapp_dir/"
        print_success "Webapp bundled"
    else
        print_warning "web/dist not found — webapp not bundled (run: cd web && bun run build)"
    fi

    # Write Info.plist from template (substitute BUILD + VERSION from VERSION.json)
    local version="1.0.0"
    local build_num="1"
    local version_file="$MAC_SYNC_ROOT/VERSION.json"
    if [[ -f "$version_file" ]] && command -v python3 &>/dev/null; then
        version=$(python3 -c "import json,sys; d=json.load(open('$version_file')); print(d.get('version','1.0.0'))" 2>/dev/null || echo "1.0.0")
        build_num=$(python3 -c "import json,sys; d=json.load(open('$version_file')); print(d.get('builds',1))" 2>/dev/null || echo "1")
    fi

    local template="$MAC_SYNC_ROOT/src/client/Resources/Info.plist.template"
    if [[ -f "$template" ]]; then
        sed -e "s/{{VERSION}}/$version/g" -e "s/{{BUILD}}/$build_num/g" \
            "$template" > "$APP_BUNDLE/Contents/Info.plist"
    else
        cat > "$APP_BUNDLE/Contents/Info.plist" <<PLIST
<?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>CFBundleExecutable</key>
    <string>$APP_NAME</string>
    <key>CFBundleIdentifier</key>
    <string>$BUNDLE_ID</string>
    <key>CFBundleName</key>
    <string>$DISPLAY_NAME</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleVersion</key>
    <string>$build_num</string>
    <key>CFBundleShortVersionString</key>
    <string>$version</string>
    <key>LSUIElement</key>
    <true/>
    <key>NSHighResolutionCapable</key>
    <true/>
    <key>NSContactsUsageDescription</key>
    <string>MacSync syncs your contacts to your personal server.</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>MacSync reads your photo library to sync metadata and originals to your personal server.</string>
    <key>NSAppleEventsUsageDescription</key>
    <string>MacSync reads Mail.app messages and sends iMessages to sync to your personal server.</string>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsLocalNetworking</key>
        <true/>
    </dict>
</dict>
</plist>
PLIST
    fi

    print_success "Installed to $APP_BUNDLE"
}

# Stable code-signing identity for TCC-grant persistence across rebuilds.
# Preference order:
#   1. $MAC_SYNC_SIGNING_IDENTITY if set
#   2. Self-signed identity with CN="Quinn Norton" if present in keychain
#   3. Auto-create the "Quinn Norton" self-signed identity (requires interactive session)
# Never pick up Apple Development certs automatically — those may carry a
# legacy Apple ID / email we don't want showing up in `codesign -dv`.
SIGNING_IDENTITY_PREFERRED="${MAC_SYNC_SIGNING_IDENTITY:-}"
SIGNING_IDENTITY_FALLBACK="Quinn Norton"
SIGNING_IDENTITY=""
# Dedicated keychain so deployments over SSH don't need the user's login
# password. The password below is embedded intentionally — this keychain only
# ever holds the self-signed code-signing cert and nothing secret.
MAC_SYNC_KEYCHAIN_PW="quinn-unlock"
MAC_SYNC_KEYCHAIN_PATH="$HOME/Library/Keychains/macsync-signing.keychain-db"

ensure_signing_identity() {
    # 1. Prefer an explicit identity passed via $MAC_SYNC_SIGNING_IDENTITY env var.
    if [[ -n "$SIGNING_IDENTITY_PREFERRED" ]] && \
         security find-identity -p codesigning -v 2>/dev/null | grep -q "$SIGNING_IDENTITY_PREFERRED"; then
        SIGNING_IDENTITY="$SIGNING_IDENTITY_PREFERRED"
        print_success "Using preferred signing identity: $SIGNING_IDENTITY"
        return 0
    fi

    # 2. Prefer the self-signed "Quinn Norton" identity.
    SIGNING_IDENTITY="$SIGNING_IDENTITY_FALLBACK"
    # Count how many valid signing identities are in the dedicated keychain.
    local identity_count
    identity_count=$(security find-identity -p codesigning -v "$MAC_SYNC_KEYCHAIN_PATH" 2>/dev/null | grep -c "\"$SIGNING_IDENTITY\"" || true)

    if [[ "$identity_count" -eq 1 ]]; then
        print_success "Code-signing identity '$SIGNING_IDENTITY' already in keychain"
        return 0
    fi

    print_step "Creating self-signed code-signing identity '$SIGNING_IDENTITY'..."

    # Recreate the keychain to remove duplicates or start fresh (identity count was 0 or >1).
    if [[ -f "$MAC_SYNC_KEYCHAIN_PATH" ]]; then
        security delete-keychain "$MAC_SYNC_KEYCHAIN_PATH" 2>/dev/null || true
        rm -f "$MAC_SYNC_KEYCHAIN_PATH"
    fi
    security create-keychain -p "$MAC_SYNC_KEYCHAIN_PW" "macsync-signing.keychain"
    security unlock-keychain -p "$MAC_SYNC_KEYCHAIN_PW" "$MAC_SYNC_KEYCHAIN_PATH"
    # No auto-lock timeout for this keychain (holds only a public self-signed cert).
    # Requires interactive context on some macOS versions — best-effort.
    security set-keychain-settings -t 0 -l "$MAC_SYNC_KEYCHAIN_PATH" 2>/dev/null || \
        print_info "(skipped set-keychain-settings — keychain will auto-lock on timeout)"
    # Add to the user search list so codesign can find the identity.
    local existing_keychains
    existing_keychains=$(security list-keychains -d user | tr -d '"' | xargs)
    if ! echo "$existing_keychains" | grep -q "macsync-signing.keychain"; then
        security list-keychains -d user -s $existing_keychains "$MAC_SYNC_KEYCHAIN_PATH"
    fi

    local tmp_dir
    tmp_dir=$(mktemp -d)
    trap "rm -rf '$tmp_dir'" RETURN
    cat > "$tmp_dir/cert.conf" <<EOF
[req]
distinguished_name = req_dn
prompt = no
x509_extensions = v3_req
[req_dn]
CN = $SIGNING_IDENTITY
O  = $SIGNING_IDENTITY
emailAddress = quinn90210@pm.me
[v3_req]
basicConstraints = CA:false
keyUsage = digitalSignature
extendedKeyUsage = codeSigning
EOF
    openssl req -x509 -newkey rsa:2048 \
        -keyout "$tmp_dir/key.pem" -out "$tmp_dir/cert.pem" \
        -days 3650 -nodes -config "$tmp_dir/cert.conf" 2>&1 | grep -v '\-\-\-\-\-' || true
    # Separate imports — LibreSSL's p12 output fails MAC verification in
    # macOS's `security import`, so we skip pkcs12.
    # -A allows all apps to access without ACL prompts.
    # Order matters: import the PRIVATE KEY first, then the cert. macOS pairs
    # them at cert-import time by matching the cert's public key against
    # already-imported keys. Reversed order leaves "Imported Private Key" with
    # no cert attached → `security find-identity` won't see it as an identity,
    # and codesign fails with errSecInternalComponent.
    # Keep unlocked with long timeout so imports + partition-list run without prompts.
    security unlock-keychain -p "$MAC_SYNC_KEYCHAIN_PW" "$MAC_SYNC_KEYCHAIN_PATH"
    security set-keychain-settings -t 3600 "$MAC_SYNC_KEYCHAIN_PATH" 2>/dev/null || true
    security import "$tmp_dir/key.pem"  -k "$MAC_SYNC_KEYCHAIN_PATH" -A 2>/dev/null || true
    security import "$tmp_dir/cert.pem" -k "$MAC_SYNC_KEYCHAIN_PATH" -A
    # Grant codesign access to the private key — must happen while keychain is unlocked.
    security unlock-keychain -p "$MAC_SYNC_KEYCHAIN_PW" "$MAC_SYNC_KEYCHAIN_PATH"
    security set-key-partition-list \
        -S "apple-tool:,apple:,codesign:" \
        -s -k "$MAC_SYNC_KEYCHAIN_PW" \
        "$MAC_SYNC_KEYCHAIN_PATH" >/dev/null 2>&1 || true
    print_success "Identity '$SIGNING_IDENTITY' created in dedicated keychain"
}

code_sign() {
    ensure_signing_identity
    # Keep the signing keychain unlocked through the codesign call.
    security unlock-keychain -p "$MAC_SYNC_KEYCHAIN_PW" "$MAC_SYNC_KEYCHAIN_PATH" 2>/dev/null || true
    security set-keychain-settings -t 300 "$MAC_SYNC_KEYCHAIN_PATH" 2>/dev/null || true

    print_step "Code signing with entitlements..."
    local entitlements="$MAC_SYNC_ROOT/src/client/Resources/LilithMacSync.entitlements"
    if [[ ! -f "$entitlements" ]]; then
        print_error "Entitlements file not found: $entitlements"
        exit 1
    fi
    # Sign directly using the dedicated keychain (explicitly unlocked above).
    # No launchctl asuser needed — codesign --keychain works over SSH as long
    # as the keychain is unlocked and the partition list grants codesign access.
    if codesign --force --deep --sign "$SIGNING_IDENTITY" \
            --keychain "$MAC_SYNC_KEYCHAIN_PATH" \
            --entitlements "$entitlements" \
            "$APP_BUNDLE" 2>&1; then
        print_success "Signed with '$SIGNING_IDENTITY' (stable identity — TCC grants persist)"
    else
        print_error "Code signing failed — TCC grants will be lost. Check keychain."
        exit 1
    fi
}

configure_app() {
    print_step "Configuring app defaults..."
    mkdir -p "$APP_SUPPORT_DIR"

    if [[ ! "$SERVER_URL" =~ ^https?:// ]]; then
        print_error "SERVER_URL must start with http:// or https://"
        exit 1
    fi

    defaults write "$BUNDLE_ID" serverURL "$SERVER_URL"
    defaults write "$BUNDLE_ID" webServerPort -int 8765
    print_success "serverURL: $SERVER_URL"
    print_success "webServerPort: 8765"

    # Drop a discoverable config file at ~/.config/com.lilith.mac-sync/config.json
    # so users can see + edit settings in plain text. App reads this at launch
    # and merges into UserDefaults (ConfigFile.load()), so this file wins over
    # prior `defaults write` values.
    local config_dir="$HOME/.config/com.lilith.mac-sync"
    local config_file="$config_dir/config.json"
    if [[ ! -f "$config_file" ]]; then
        mkdir -p "$config_dir"
        cat > "$config_file" <<JSON
{
  "serverURL": "$SERVER_URL",
  "webServerPort": 8765
}
JSON
        print_success "config: $config_file"
    else
        print_info "config: $config_file (preserved — edit to change settings)"
    fi
}

create_launch_agent() {
    print_step "Creating LaunchAgent $BUNDLE_ID..."
    mkdir -p "$LAUNCH_AGENTS_DIR"
    cat > "$PLIST_FILE" <<PLIST
<?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>Label</key>
    <string>$BUNDLE_ID</string>
    <key>ProgramArguments</key>
    <array>
        <string>$APP_BUNDLE/Contents/MacOS/$APP_NAME</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key>
        <false/>
        <key>Crashed</key>
        <true/>
    </dict>
    <key>StandardOutPath</key>
    <string>$APP_SUPPORT_DIR/stdout.log</string>
    <key>StandardErrorPath</key>
    <string>$APP_SUPPORT_DIR/stderr.log</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
</dict>
</plist>
PLIST
    launchctl load "$PLIST_FILE"
    print_success "LaunchAgent loaded — auto-starts on login"
}

verify_install() {
    print_step "Verifying installation..."
    local errors=0
    [[ -f "$APP_BUNDLE/Contents/MacOS/$APP_NAME" ]] && print_success "Binary present" || { print_error "Binary missing"; errors=$((errors+1)); }
    [[ -f "$PLIST_FILE" ]] && print_success "LaunchAgent present" || { print_error "LaunchAgent missing"; errors=$((errors+1)); }
    pgrep -x "$APP_NAME" >/dev/null && print_success "Agent running" || print_warning "Agent not yet running (may need TCC permissions)"
    return $errors
}

show_permissions_guide() {
    echo ""
    echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo -e "${YELLOW}  Grant macOS Permissions${NC}"
    echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo ""
    echo "Open System Settings → Privacy & Security and grant $DISPLAY_NAME:"
    echo "  • Full Disk Access  (required for iMessage chat.db)"
    echo "  • Contacts"
    echo "  • Photos"
    echo "  • Automation → Messages + Mail"
    echo ""
    echo "App bundle: $APP_BUNDLE"
    echo ""
    if [[ -t 0 ]]; then
        read -rp "Press Enter to open System Settings → Full Disk Access (or 'n' to skip): " resp
        if [[ "${resp:-}" != "n" && "${resp:-}" != "N" ]]; then
            open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles" 2>/dev/null || \
            open "/System/Applications/System Settings.app"
        fi
    fi
}

print_completion() {
    echo ""
    echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo -e "${GREEN}  Installation complete${NC}"
    echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo ""
    echo "  Logs:    tail -f $APP_SUPPORT_DIR/stderr.log"
    echo "  Restart: launchctl unload $PLIST_FILE && launchctl load $PLIST_FILE"
    echo "  Status:  http://localhost:8765/"
    echo ""
}

main() {
    check_macos
    check_swift

    local IS_FRESH_INSTALL="true"
    [[ -f "$PLIST_FILE" ]] && IS_FRESH_INSTALL="false" && print_info "Updating existing install"

    stop_existing_agent
    build_application
    install_binary
    code_sign
    configure_app
    create_launch_agent

    if verify_install; then
        [[ "$IS_FRESH_INSTALL" == "true" ]] && show_permissions_guide
        print_completion
    else
        echo ""
        print_error "Installation completed with errors — review output above"
        exit 1
    fi
}

main "$@"
