#!/bin/bash

DEBUG_MODE=false  # Set to true for troubleshooting only

TEST_MODE=false

# TEST TOGGLE: set to true to force a fake error report through on every
# run, so the hunter.sh -> PHP -> Django reporting path can be tested
# without needing to trigger a real failure. Set back to false before
# going live.
CREATE_ERROR=false


# $PASS authorizes the sudo cal
# $SQUID_PASS is a third, entirely separate proxy secret.
# $PROTECTION_ADMIN_PASS is the new account's future login password

SERVICE="Wi-Fi"

# Logs go to Application Support, not Downloads - avoids TCC prompts entirely.
# Must happen before the admin check below - it redirects stderr to $LOG.
LOG_DIR="$HOME/Protect"
mkdir -p "$LOG_DIR" 2>/dev/null

if $DEBUG_MODE; then
    LOG="$LOG_DIR/hunter_log.txt"
else
    LOG="/dev/null"
fi

# Bail out immediately if the current console user isn't an admin. This is
# unconditional - independent of any server-side flag - so a demoted user
# is blocked from getting any further than this on every future run, even
# offline, before the email prompt, before contacting the server, before
# anything else runs.
CURRENT_USER=$(stat -f%Su /dev/console)
if dscl . -read /Groups/admin GroupMembership 2>>"$LOG" | grep -q "$CURRENT_USER"; then
    CURRENT_USER_IS_ADMIN=true
else
    CURRENT_USER_IS_ADMIN=false
fi

if [ "$CURRENT_USER_IS_ADMIN" = false ]; then
    osascript -e 'display dialog "Please contact us to configure." buttons {"OK"} default button "OK"'
    exit 1
fi


# When run inside a Platypus .app, the script lives at:
#   AppName.app/Contents/Resources/script
# and any bundled files (like the cert) sit alongside it in Resources.
# Resources are NOT a TCC-protected location, so reading from here never
# triggers a Downloads/Documents/Desktop permission prompt for the user.
SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
CERT_PATH="$SCRIPT_DIR/SafeAccessSolution8.crt"


if $TEST_MODE; then
    REG_URL="http://192.168.20.6:8080/mac_reg.php"
else
    REG_URL="https://www.gamblock.com/php/macos/mac_reg/3/mac_reg.php"
fi


# Get serial and check whitelist
SERIAL=$(ioreg -c IOPlatformExpertDevice -d 2 | awk -F'"' '/IOPlatformSerialNumber/{print $4}')

# Reports a hunter.sh failure back to Django (via the same PHP relay) so it
# shows up on the device's lastErrorFound field. Best-effort: silenced and
# time-capped so it never blocks/hangs the actual install flow.
report_error() {
    local code="$1"
    local detail="$2"
    curl -s -m 5 -X POST "$REG_URL" \
         --data-urlencode "serial=$SERIAL" \
         --data-urlencode "lastErrorFound=[Hunter] $code: $detail" > /dev/null 2>&1
}


if $CREATE_ERROR; then
    report_error "Code TEST" "forced test error from hunter.sh (CREATE_ERROR=true)"
fi

EMAIL=$(osascript -e 'display dialog "Please enter your email address:" default answer "" buttons {"OK"} default button "OK"' -e 'text returned of result' 2>>"$LOG")
if [[ $? -ne 0 ]]; then
    report_error "Code Z" "EMAIL prompt (osascript) failed or was cancelled"
    osascript -e 'display dialog "Code Z - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

RESPONSE=$(curl -s -X POST "$REG_URL" \
     --data-urlencode "serial=$SERIAL" \
     --data-urlencode "email=$EMAIL")

# One-way support message: show it immediately, before anything else in the
# script acts on the response (before logging, before installState branching).
SUPPORT_MESSAGE=$(echo "$RESPONSE" | grep -o '"supportMessage":"[^"]*"' | sed 's/"supportMessage":"//;s/"$//')
if [ -n "$SUPPORT_MESSAGE" ]; then
    # Escape backslashes and double quotes so the message text can't break
    # out of the double-quoted AppleScript string.
    ESCAPED_SUPPORT_MESSAGE=$(printf '%s' "$SUPPORT_MESSAGE" | sed 's/\\/\\\\/g; s/"/\\"/g')
    osascript -e "display dialog \"$ESCAPED_SUPPORT_MESSAGE\" buttons {\"OK\"} default button \"OK\""
fi

# Support can remotely enable logging for this device (e.g. for troubleshooting
# without needing physical access) by setting createLocalLog to "On" in Django.
# This must run before any of the log writes below so this run's own log lines
# aren't lost if logging was just switched on.
CREATE_LOCAL_LOG=$(echo "$RESPONSE" | grep -o '"createLocalLog":"[^"]*"' | sed 's/"createLocalLog":"//;s/"$//')
if [ "$CREATE_LOCAL_LOG" = "On" ]; then
    DEBUG_MODE=true
    LOG="$LOG_DIR/hunter_log.txt"
fi

echo "Serial: $SERIAL" >> "$LOG"
echo "Email: $EMAIL" >> "$LOG"
echo "Response: $RESPONSE" >> "$LOG"

echo "Server response: $RESPONSE" >> "$LOG"

if [ -z "$RESPONSE" ]; then
    echo "WARNING: empty response from REG_URL - Apache/Django may be unreachable" >> "$LOG"
    report_error "Code E" "Empty response from $REG_URL - server unreachable, falling back to hardcoded defaults"
fi

INSTALL_STATE=$(echo "$RESPONSE" | grep -o '"installState":"[^"]*"' | sed 's/"installState":"//;s/"$//')
if [ -z "$INSTALL_STATE" ]; then
    INSTALL_STATE="install_proxy"
fi
echo "Install State: $INSTALL_STATE" >> "$LOG"

if [ "$INSTALL_STATE" = "disable_proxy" ]; then
    echo "inner disable_proxy" >> "$LOG"
    networksetup -setwebproxy "$SERVICE" "" ""
    networksetup -setsecurewebproxy "$SERVICE" "" ""
    networksetup -setwebproxystate "$SERVICE" off
    networksetup -setsecurewebproxystate "$SERVICE" off
    sudo killall com.apple.WebKit.Networking
    sudo killall mDNSResponder
    osascript -e 'display dialog "Hunter removed successfully." buttons {"OK"} default button "OK"'
    exit 0
fi

# Check cert exists
if [[ ! -f "$CERT_PATH" ]]; then
    report_error "Code A" "certificate file not found at CERT_PATH"
    osascript -e 'display dialog "Code A - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

# Prompt for password once upfront
PASS=$(osascript -e 'display dialog "Hunter requires your Mac password to install:" default answer "" with hidden answer buttons {"OK"} default button "OK"' -e 'text returned of result' 2>>"$LOG")
if [[ $? -ne 0 ]]; then
    report_error "Code B" "password prompt (osascript) failed or was cancelled"
    osascript -e 'display dialog "Code B - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

# Install and trust cert
echo "$PASS" | sudo -S security add-trusted-cert -d -r trustRoot \
    -k /Library/Keychains/System.keychain "$CERT_PATH" >> "$LOG" 2>&1
if [[ $? -ne 0 ]]; then
    report_error "Code C" "security add-trusted-cert failed"
    osascript -e 'display dialog "Code C - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

# Verify cert in keychain
security find-certificate -c "SafeAccessSolution8" \
    /Library/Keychains/System.keychain >> "$LOG" 2>&1
if [[ $? -ne 0 ]]; then
    report_error "Code D" "security find-certificate verification failed"
    osascript -e 'display dialog "Code D - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

# ------------------------------------------------------------------
# Deploy the bundled Status.app to the current user's Desktop, same
# pattern as SafeAccessSolution8.crt already being bundled in Resources.
# Status.app is a small, non-root, standalone companion that persists
# independently of Hunter - it re-checks in periodically and can inform
# a locked-down (non-admin) user once removal has been authorized
# server-side, without itself having any privilege to act on it.
#
# Copied WITHOUT sudo - this point in the script still runs as the
# normal console user (not yet demoted, if account protection is even
# active for this device), so the user already owns their Desktop and
# has write access to it. Using sudo here would leave a root-owned copy
# sitting inside a normal user's home folder, which is both unnecessary
# and likely to cause its own permission oddities later.
#
# Note: Desktop is a TCC-protected location on modern macOS. If this
# Mac hasn't already granted the relevant permission (or Hunter doesn't
# have Full Disk Access), this copy may be silently blocked or trigger
# a one-time system permission prompt the first time it runs.
#
# Soft-fail: unlike the steps above, a failure here doesn't abort the
# install - Status.app is supplementary, not core to what this run
# needs to accomplish. Logged and reported, but not fatal.
# ------------------------------------------------------------------
STATUS_APP_SOURCE="$SCRIPT_DIR/Status.app"
STATUS_APP_DEST="$HOME/Desktop/Status.app"

if [[ -d "$STATUS_APP_SOURCE" ]]; then
    rm -rf "$STATUS_APP_DEST" >> "$LOG" 2>&1
    ditto "$STATUS_APP_SOURCE" "$STATUS_APP_DEST" >> "$LOG" 2>&1
    if [[ $? -ne 0 ]]; then
        report_error "Code Q" "failed to deploy Status.app to Desktop"
        echo "WARNING: Status.app deployment to Desktop failed - continuing anyway" >> "$LOG"
    else
        echo "Status.app deployed to $STATUS_APP_DEST" >> "$LOG"
    fi
else
    echo "WARNING: Status.app not found in Resources at $STATUS_APP_SOURCE - skipping deployment" >> "$LOG"
    report_error "Code Q" "Status.app missing from Resources - skipped deployment"
fi


# Set proxy
PROXY_SERVER=$(echo "$RESPONSE" | grep -o '"proxyIp":"[^"]*"' | sed 's/"proxyIp":"//;s/"$//')
if [ -z "$PROXY_SERVER" ]; then
    PROXY_SERVER="32.196.193.218"
fi

HTTP_PORT=$(echo "$RESPONSE" | grep -o '"proxyPort":"[^"]*"' | sed 's/"proxyPort":"//;s/"$//')
if [ -z "$HTTP_PORT" ]; then
    HTTP_PORT="52841"
fi
HTTPS_PORT="$HTTP_PORT"

SQUID_USER=$(echo "$RESPONSE" | grep -o '"proxyIndividualUser":"[^"]*"' | sed 's/"proxyIndividualUser":"//;s/"$//')
SQUID_PASS=$(echo "$RESPONSE" | grep -o '"proxyIndividualPass":"[^"]*"' | sed 's/"proxyIndividualPass":"//;s/"$//')

echo "Proxy Server: $PROXY_SERVER" >> "$LOG"
echo "Proxy Port: $HTTP_PORT" >> "$LOG"
echo "Proxy User: $SQUID_USER" >> "$LOG"

# Remove any stale keychain entries for this proxy server from earlier
# test runs / re-registrations. Without this, macOS keychain accumulates
# a separate entry per account ever used, and networksetup can fail with
# error -25299 (duplicate item) when trying to add a new one. Loop since
# each call only removes one matching item; keep going until none remain.
for i in 1 2 3 4 5 6 7 8 9 10; do
    security delete-internet-password -s "$PROXY_SERVER" >> "$LOG" 2>&1 || break
done

if [ -n "$SQUID_USER" ]; then
    echo "$PASS" | sudo -S networksetup -setwebproxy "$SERVICE" "$PROXY_SERVER" "$HTTP_PORT" on "$SQUID_USER" "$SQUID_PASS" >> "$LOG" 2>&1
else
    echo "$PASS" | sudo -S networksetup -setwebproxy "$SERVICE" "$PROXY_SERVER" "$HTTP_PORT" >> "$LOG" 2>&1
fi
if [[ $? -ne 0 ]]; then
    report_error "Code F" "networksetup -setwebproxy failed"
    osascript -e 'display dialog "Code F - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

if [ -n "$SQUID_USER" ]; then
    echo "$PASS" | sudo -S networksetup -setsecurewebproxy "$SERVICE" "$PROXY_SERVER" "$HTTPS_PORT" on "$SQUID_USER" "$SQUID_PASS" >> "$LOG" 2>&1
else
    echo "$PASS" | sudo -S networksetup -setsecurewebproxy "$SERVICE" "$PROXY_SERVER" "$HTTPS_PORT" >> "$LOG" 2>&1
fi
if [[ $? -ne 0 ]]; then
    report_error "Code G" "networksetup -setsecurewebproxy failed"
    osascript -e 'display dialog "Code G - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

echo "$PASS" | sudo -S networksetup -setwebproxystate "$SERVICE" on >> "$LOG" 2>&1
if [[ $? -ne 0 ]]; then
    report_error "Code H" "networksetup -setwebproxystate failed"
    osascript -e 'display dialog "Code H - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

echo "$PASS" | sudo -S networksetup -setsecurewebproxystate "$SERVICE" on >> "$LOG" 2>&1
if [[ $? -ne 0 ]]; then
    report_error "Code I" "networksetup -setsecurewebproxystate failed"
    osascript -e 'display dialog "Code I - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

# Bypass proxy for streaming services (avoids AWS data transfer costs)
echo "$PASS" | sudo -S networksetup -setproxybypassdomains "$SERVICE" \
    "*.spotify.com" \
    "*.scdn.co" \
    "*.spotifycdn.com" \
    "apresolve.spotify.com" \
    "*.netflix.com" \
    "*.nflximg.net" \
    "*.nflxvideo.net" \
    "*.nflxso.net" \
    "fast.com" \
    "*.youtube.com" \
    "*.googlevideo.com" \
    "*.ytimg.com" >> "$LOG" 2>&1
if [[ $? -ne 0 ]]; then
    report_error "Code J" "networksetup -setproxybypassdomains failed"
    osascript -e 'display dialog "Code J - Please screenshot and send to support" buttons {"OK"} default button "OK"'
    exit 1
fi

# ------------------------------------------------------------------
# Optional: create a dedicated "Protection" admin account and demote
# the current console user's account to standard, so the customer can
# no longer remove Hunter using their own admin rights. Only runs when
# Django has makeAccountNonAdmin = "On" for this device. Reuses $PASS
# (already prompted above) rather than asking for the password again.
# ------------------------------------------------------------------
MAKE_ACCOUNT_NON_ADMIN=$(echo "$RESPONSE" | grep -o '"makeAccountNonAdmin":"[^"]*"' | sed 's/"makeAccountNonAdmin":"//;s/"$//')
echo "Make Account Non Admin: $MAKE_ACCOUNT_NON_ADMIN" >> "$LOG"

if [ "$MAKE_ACCOUNT_NON_ADMIN" = "On" ]; then

    PROTECTION_ADMIN_PASS=$(echo "$RESPONSE" | grep -o '"protectionAdminPass":"[^"]*"' | sed 's/"protectionAdminPass":"//;s/"$//')

    if [ -z "$PROTECTION_ADMIN_PASS" ]; then
        report_error "Code K" "protectionAdminPass missing from server response - skipped account protection"
        osascript -e 'display dialog "Code K - Please screenshot and send to support" buttons {"OK"} default button "OK"'
        exit 1
    fi

    NEW_ADMIN_NAME="Protection"
    NEW_ADMIN_SHORTNAME="protection"

    CURRENT_USER=$(stat -f%Su /dev/console)
    echo "Current console user detected: $CURRENT_USER" >> "$LOG"

    if [ "$CURRENT_USER" = "$NEW_ADMIN_SHORTNAME" ]; then
        report_error "Code L" "current user matches new admin shortname, aborting to avoid conflict"
        osascript -e 'display dialog "Code L - Please screenshot and send to support" buttons {"OK"} default button "OK"'
        exit 1
    fi

    # Only create the account if it doesn't already exist - avoids
    # re-running this (and generating a fresh -25299-style conflict)
    # on every check-in once already provisioned.
    if ! dscl . -read /Users/"$NEW_ADMIN_SHORTNAME" >> "$LOG" 2>&1; then

        echo "$PASS" | sudo -S sysadminctl -addUser "$NEW_ADMIN_SHORTNAME" \
            -fullName "$NEW_ADMIN_NAME" \
            -password "$PROTECTION_ADMIN_PASS" \
            -admin >> "$LOG" 2>&1

        if [[ $? -ne 0 ]]; then
            report_error "Code M" "failed to create new admin account"
            osascript -e 'display dialog "Code M - Please screenshot and send to support" buttons {"OK"} default button "OK"'
            exit 1
        fi

        if ! dscl . -read /Groups/admin GroupMembership 2>>"$LOG" | grep -q "$NEW_ADMIN_SHORTNAME"; then
            report_error "Code N" "new account not found in admin group after creation"
            osascript -e 'display dialog "Code N - Please screenshot and send to support" buttons {"OK"} default button "OK"'
            exit 1
        fi

        echo "New admin account created and verified" >> "$LOG"
    else
        echo "Protection admin account already exists - skipping creation" >> "$LOG"
    fi

    # Clear from memory as soon as it's no longer needed
    PROTECTION_ADMIN_PASS=""

    # Only demote if the current user is still an admin - avoids
    # re-demoting (and erroring) on repeat check-ins.
    if dscl . -read /Groups/admin GroupMembership 2>>"$LOG" | grep -q "$CURRENT_USER"; then

        echo "$PASS" | sudo -S dseditgroup -o edit -d "$CURRENT_USER" -t user admin >> "$LOG" 2>&1

        if [[ $? -ne 0 ]]; then
            report_error "Code O" "failed to remove $CURRENT_USER from admin group"
            osascript -e 'display dialog "Code O - Please screenshot and send to support" buttons {"OK"} default button "OK"'
            exit 1
        fi

        if dscl . -read /Groups/admin GroupMembership 2>>"$LOG" | grep -q "$CURRENT_USER"; then
            report_error "Code P" "$CURRENT_USER still appears in admin group after removal attempt"
            osascript -e 'display dialog "Code P - Please screenshot and send to support" buttons {"OK"} default button "OK"'
            exit 1
        fi

        echo "$CURRENT_USER demoted to standard account" >> "$LOG"
    else
        echo "$CURRENT_USER already non-admin - skipping demotion" >> "$LOG"
    fi

fi

osascript -e 'display dialog "Hunter Installed Successfully" buttons {"OK"} default button "OK"'


if [ "$DEBUG_MODE" = "true" ]; then
    open "$LOG_DIR"
fi
