# shellcheck shell=bash

_setup_env() {
    FUNCS_DIR=$(cd "$(dirname "$0")" && pwd)/../..
    APP_RELEASE=$(grep "^APP_RELEASE" "${FUNCS_DIR}/web/version.py" | cut -d"=" -f2 | sed 's/ //g')
    APP_REVISION=$(grep "^APP_REVISION" "${FUNCS_DIR}/web/version.py" | cut -d"=" -f2 | sed 's/ //g')
    APP_NAME=$(grep "^APP_NAME" "${FUNCS_DIR}/web/branding.py" | cut -d"=" -f2 | sed "s/'//g" | sed 's/^ //')
    APP_LONG_VERSION=${APP_RELEASE}.${APP_REVISION}
    APP_SUFFIX=$(grep "^APP_SUFFIX" "${FUNCS_DIR}/web/version.py" | cut -d"=" -f2 | sed 's/ //g' | sed "s/'//g")
    if [ -n "${APP_SUFFIX}" ]; then
        APP_LONG_VERSION=${APP_LONG_VERSION}-${APP_SUFFIX}
    fi
    BUNDLE_DIR="${BUILD_ROOT}/${APP_NAME}.app"
    DMG_NAME="${DIST_ROOT}"/$(echo "${APP_NAME}" | sed 's/ //g' | awk '{print tolower($0)}')-"${APP_LONG_VERSION}-$(uname -m).dmg"
    PYTHON_OS_VERSION="11"
}

_cleanup() {
    echo Cleaning up the old environment and app bundle...
    rm -rf "${BUILD_ROOT}"
    rm -rf "${TEMP_DIR}"
    rm -f "${DIST_ROOT}"/*.dmg
}

_build_runtime() {
    echo "Assembling the runtime environment..."

    test -d "${BUILD_ROOT}" || mkdir "${BUILD_ROOT}"
    # Get a fresh copy of electron
    # uname -m returns "x86_64" on Intel, but we need "x64"
    ELECTRON_ARCH="x64"
    if [ "$(uname -m)" == "arm64" ]; then
      ELECTRON_ARCH="arm64"
    fi

    ELECTRON_VERSION="$(npm info electron version)"

    pushd "${BUILD_ROOT}" > /dev/null || exit
        while true;do
            wget "https://github.com/electron/electron/releases/download/v${ELECTRON_VERSION}/electron-v${ELECTRON_VERSION}-darwin-${ELECTRON_ARCH}.zip" && break
            rm "electron-v${ELECTRON_VERSION}-darwin-${ELECTRON_ARCH}.zip"
        done
        unzip "electron-v${ELECTRON_VERSION}-darwin-${ELECTRON_ARCH}.zip"
    popd > /dev/null || exit
    # WGET END


    mv "${BUILD_ROOT}/Electron.app" "${BUNDLE_DIR}"
    find "${BUNDLE_DIR}" -exec touch {} \;

    # Copy in the runtime code
    mkdir "${BUNDLE_DIR}/Contents/Resources/app/"
    cp -R "${SOURCE_DIR}/runtime/assets" "${BUNDLE_DIR}/Contents/Resources/app/"
    cp -R "${SOURCE_DIR}/runtime/src" "${BUNDLE_DIR}/Contents/Resources/app/"
    cp "${SOURCE_DIR}/runtime/package.json" "${BUNDLE_DIR}/Contents/Resources/app/"
    cp "${SOURCE_DIR}/runtime/.yarnrc.yml" "${BUNDLE_DIR}/Contents/Resources/app/"

    # Install the runtime node_modules, then replace the package.json
    pushd "${BUNDLE_DIR}/Contents/Resources/app/" > /dev/null || exit
        yarn set version berry
        yarn set version 4
        yarn workspaces focus --production

        # remove the yarn cache
        rm -rf .yarn .yarn*
    popd > /dev/null || exit
}

_create_python_env() {
    echo "Creating the Python environment..."
    PATH=${PGADMIN_POSTGRES_DIR}/bin:${PATH}
    LD_LIBRARY_PATH=${PGADMIN_POSTGRES_DIR}/lib:${LD_LIBRARY_PATH}

    # Figure out what python to use
    if which python3 > /dev/null 2>&1
    then
        SYSTEM_PYTHON_EXE="python3"
    else
        SYSTEM_PYTHON_EXE="python"
    fi

    git clone https://github.com/gregneagle/relocatable-python.git "${BUILD_ROOT}/relocatable_python"
    "${SYSTEM_PYTHON_EXE}" \
        "${BUILD_ROOT}/relocatable_python/make_relocatable_python_framework.py" \
        --python-version "${PGADMIN_PYTHON_VERSION}" \
        --os-version "${PYTHON_OS_VERSION}" \
        --destination "${BUNDLE_DIR}/Contents/Frameworks/"

    "${BUNDLE_DIR}/Contents/Frameworks/Python.framework/Versions/Current/bin/python3" -m ensurepip --upgrade || exit 1
    "${BUNDLE_DIR}/Contents/Frameworks/Python.framework/Versions/Current/bin/pip3" install -r "${SOURCE_DIR}/requirements.txt" || exit 1

    # Make sure all the .so's in the Python env have the executable bit set
    # so they get properly signed later
    OLD_IFS=${IFS}
    IFS=$'\n'
    for i in $(find . -type f -name '*.so' -exec file "{}" \; | grep -v "(for architecture" | grep -E "Mach-O executable|Mach-O 64-bit executable|Mach-O 64-bit bundle|Mach-O 64-bit dynamically linked shared library" | awk -F":" '{print $1}' | uniq)
    do
        chmod +x "${i}"
    done
    IFS=${OLD_IFS}

    # Fixup shebangs
    cd "${BUNDLE_DIR}/Contents/Frameworks/Python.framework/Versions/Current/bin" || exit
    # shellcheck disable=SC2016
    grep -RiIl 'mac-build' ./* | xargs sed -i '' 's/\/.*\/python3\./\$(dirname \"$0\")\/python3./g'

    # Remove some things we don't need
    cd "${BUNDLE_DIR}/Contents/Frameworks/Python.framework" || exit
    find . -name test -type d -print0 | xargs -0 rm -rf
    find . -name tkinter -type d -print0 | xargs -0 rm -rf
    find . -name turtle.py -type f -print0 | xargs -0 rm -rf
    find . -name turtledemo -type d -print0 | xargs -0 rm -rf
    find . -name "tcl*" -type d -print0 | xargs -0 rm -rf
    find . -name "tk*" -type d -print0 | xargs -0 rm -rf
    find . -name "tdbc*" -type d -print0 | xargs -0 rm -rf
    find . -name "itcl*" -type d -print0 | xargs -0 rm -rf
    rm -f Versions/Current/lib/Tk.*
    rm -f Versions/Current/lib/libtcl*.dylib
    rm -f Versions/Current/lib/libtk*.dylib
    rm -f Versions/Current/lib/libtcl*.a
    rm -f Versions/Current/lib/libtk*.a
    rm -f Versions/Current/lib/tcl*.sh
    rm -f Versions/Current/lib/tk*.sh
    rm -rf Versions/Current/lib/pkgconfig*
    rm -rf Versions/Current/lib/sqlite*
    rm -rf Versions/Current/lib/thread*
    rm -rf Versions/Current/share
}

_build_docs() {
    echo "Building the docs..."
    # Create a temporary venv for the doc build, so we don't contaminate the one
    # that we're going to ship.
    "${BUNDLE_DIR}/Contents/Frameworks/Python.framework/Versions/Current/bin/python3" -m venv "${BUILD_ROOT}/venv"
    # shellcheck disable=SC1091
    source "${BUILD_ROOT}/venv/bin/activate"
    pip3 install --upgrade pip
    pip3 install -r "${SOURCE_DIR}/requirements.txt"
    pip3 install sphinx==7.4.7
    pip3 install sphinxcontrib-youtube

    cd "${SOURCE_DIR}" || exit
    make docs

    cd "${SOURCE_DIR}/docs/en_US" || exit
    test -d "${BUNDLE_DIR}/Contents/Resources/docs/en_US" || mkdir -p "${BUNDLE_DIR}/Contents/Resources/docs/en_US"
    cp -r _build/html "${BUNDLE_DIR}/Contents/Resources/docs/en_US/"

    # Remove some things we don't need
    rm -rf "${BUNDLE_DIR}/Contents/Resources/docs/en_US/html/_sources"
    rm -f "${BUNDLE_DIR}/Contents/Resources/docs/en_US/html/_static"/*.png
}

_fixup_imports() {
    local TODO TODO_OLD FW_RELPATH LIB LIB_BN

    echo "Fixing imports on the core appbundle..."
    pushd "$1" > /dev/null || exit

    # Find all the files that may need tweaks
    TODO=$(find . -perm +0111 -type f -exec file "{}" \; | \
        grep -v "Frameworks/Python.framework" | \
        grep -E "Mach-O 64-bit" | \
        awk -F ':| ' '{ORS=" "; print $1}' | \
        uniq)

    # Add anything in the site-packages Python directory
    TODO+=$(find ./Contents/Frameworks/Python.framework/Versions/Current/lib/python*/site-packages -perm +0111 -type f -exec file "{}" \; | \
        grep -E "Mach-O 64-bit" | \
        awk -F ':| ' '{ORS=" "; print $1}' | \
        uniq)

    echo "Found executables: ${TODO}"
    while test "${TODO}" != ""; do
        TODO_OLD=${TODO} ;
        TODO="" ;
        for TODO_OBJ in ${TODO_OLD}; do
            echo "Post-processing: ${TODO_OBJ}"

            # The Rust interface in the Python Cryptography module contains
            # a reference to a .so that won't exist. See:
            # https://github.com/PyO3/setuptools-rust/issues/106
            if [[ "${TODO_OBJ}" =~ cryptography/hazmat/bindings/\_rust\.abi3\.so$ ]]; then
                echo "Skipping because of https://github.com/PyO3/setuptools-rust/issues/106."
                continue
            fi

            # Figure out the relative path from ${TODO_OBJ} to Contents/Frameworks
            FW_RELPATH=$(echo "${TODO_OBJ}" | \
                sed -n 's|^\(\.//*\)\(\([^/][^/]*/\)*\)[^/][^/]*$|\2|gp' | \
                sed -n 's|[^/][^/]*/|../|gp' \
                )"Contents/Frameworks"

            # Find all libraries ${TODO_OBJ} depends on, but skip system libraries
            for LIB in $(
                otool -L "${TODO_OBJ}" | \
                sed -n 's|^.*[[:space:]]\([^[:space:]]*\.dylib\).*$|\1|p' | \
                grep -E -v '^(/usr/lib)|(/System)|@executable_path|@loader_path|/DLC/PIL/' \
            ); do
                # Copy in any required dependencies
                LIB_BN="$(basename "${LIB}")" ;
                if ! test -f "Contents/Frameworks/${LIB_BN}"; then
                    TARGET_FILE=""
                    TARGET_PATH=""
                    echo "Adding symlink: ${LIB_BN} (because of: ${TODO_OBJ})"
                    cp -R "${LIB}" "Contents/Frameworks/${LIB_BN}"
                    if ! test -L "Contents/Frameworks/${LIB_BN}"; then
                        chmod 755 "Contents/Frameworks/${LIB_BN}"
                    else
                        TARGET_FILE=$(readlink "${LIB}")
                        TARGET_PATH=$(dirname "${LIB}")/${TARGET_FILE}
                        echo "Adding symlink target: ${TARGET_PATH}"
                        cp "${TARGET_PATH}" "Contents/Frameworks/${TARGET_FILE}"
                        chmod 755 "Contents/Frameworks/${TARGET_FILE}"
                    fi
                    echo "Rewriting ID in Contents/Frameworks/${LIB_BN} to ${LIB_BN}"
                    install_name_tool \
                        -id "${LIB_BN}" \
                        "Contents/Frameworks/${LIB_BN}" || exit 1
                    TODO="${TODO} ./Contents/Frameworks/${LIB_BN}"
                fi

                # Rewrite the dependency paths
                echo "Rewriting library ${LIB} to @loader_path/${FW_RELPATH}/${LIB_BN} in ${TODO_OBJ}"
                install_name_tool -change \
                    "${LIB}" \
                    "@loader_path/${FW_RELPATH}/${LIB_BN}" \
                    "${TODO_OBJ}" || exit 1
                install_name_tool -change \
                    "${TARGET_PATH}" \
                    "@loader_path/${FW_RELPATH}/${TARGET_FILE}" \
                    "${TODO_OBJ}" || exit 1
            done
        done
    done

    echo "Imports updated on the core appbundle."
    popd > /dev/null || exit
}

_complete_bundle() {
    echo "Completing the appbundle..."
    cd "${SCRIPT_DIR}" || exit

    # Copy the binary utilities into place
    mkdir -p "${BUNDLE_DIR}/Contents/SharedSupport/"
    cp "${PGADMIN_POSTGRES_DIR}/bin/pg_dump" "${BUNDLE_DIR}/Contents/SharedSupport/"
    cp "${PGADMIN_POSTGRES_DIR}/bin/pg_dumpall" "${BUNDLE_DIR}/Contents/SharedSupport/"
    cp "${PGADMIN_POSTGRES_DIR}/bin/pg_restore" "${BUNDLE_DIR}/Contents/SharedSupport/"
    cp "${PGADMIN_POSTGRES_DIR}/bin/psql" "${BUNDLE_DIR}/Contents/SharedSupport/"

    # Update the plist
    cp Info.plist.in "${BUNDLE_DIR}/Contents/Info.plist"
    sed -i '' "s/%APPNAME%/${APP_NAME}/g" "${BUNDLE_DIR}/Contents/Info.plist"
    sed -i '' "s/%APPVER%/${APP_LONG_VERSION}/g" "${BUNDLE_DIR}/Contents/Info.plist"
    sed -i '' "s/%APPID%/org.pgadmin.pgadmin4/g" "${BUNDLE_DIR}/Contents/Info.plist"

    # Rename helper execs and Update the plist
    for helper_exec in "Electron Helper" "Electron Helper (Renderer)" "Electron Helper (Plugin)" "Electron Helper (GPU)"
    do
        pgadmin_exec=${helper_exec//Electron/pgAdmin 4}
        mv "${BUNDLE_DIR}/Contents/Frameworks/${helper_exec}.app/Contents/MacOS/${helper_exec}" "${BUNDLE_DIR}/Contents/Frameworks/${helper_exec}.app/Contents/MacOS/${pgadmin_exec}"
        mv "${BUNDLE_DIR}/Contents/Frameworks/${helper_exec}.app" "${BUNDLE_DIR}/Contents/Frameworks/${pgadmin_exec}.app"

        info_plist="${BUNDLE_DIR}/Contents/Frameworks/${pgadmin_exec}.app/Contents/Info.plist"
        cp Info.plist-helper.in "${info_plist}"
        sed -i '' "s/%APPNAME%/${pgadmin_exec}/g" "${info_plist}"
        sed -i '' "s/%APPVER%/${APP_LONG_VERSION}/g" "${info_plist}"
        sed -i '' "s/%APPID%/org.pgadmin.pgadmin4.helper/g" "${info_plist}"
    done

    # PkgInfo
    echo APPLPGA4 > "${BUNDLE_DIR}/Contents/PkgInfo"

    # Icon
    cp pgAdmin4.icns "${BUNDLE_DIR}/Contents/Resources/app.icns"

    # Rename the executable
    mv "${BUNDLE_DIR}/Contents/MacOS/Electron" "${BUNDLE_DIR}/Contents/MacOS/${APP_NAME}"

    # Rename the app in package.json so the menu looks as it should
    sed -i '' "s/\"name\": \"pgadmin4\"/\"name\": \"${APP_NAME}\"/g" "${BUNDLE_DIR}/Contents/Resources/app/package.json"

    # Import the dependencies, and rewrite any library references
        _fixup_imports "${BUNDLE_DIR}"

    # Build node modules
    pushd "${SOURCE_DIR}/web" > /dev/null || exit
        yarn set version berry
        yarn set version 4
        yarn install
        yarn run bundle

        curl https://curl.se/ca/cacert.pem -o cacert.pem -s
    popd > /dev/null || exit

    # copy the web directory to the bundle as it is required by runtime
    cp -r "${SOURCE_DIR}/web" "${BUNDLE_DIR}/Contents/Resources/"
    cd "${BUNDLE_DIR}/Contents/Resources/web" || exit
    rm -f pgadmin4.db config_local.*
    rm -rf jest.config.js babel.* package.json .yarn* yarn* .editorconfig .eslint* node_modules/ regression/ tools/ pgadmin/static/js/generated/.cache
    find . -name "tests" -type d -print0 | xargs -0 rm -rf
    find . -name "feature_tests" -type d -print0 | xargs -0 rm -rf
    find . -name "__pycache__" -type d -print0 | xargs -0 rm -rf
    find . -name ".DS_Store" -print0 | xargs -0 rm -f

    {
        echo "SERVER_MODE = False"
        echo "HELP_PATH = '../../../docs/en_US/html/'"
        echo "DEFAULT_BINARY_PATHS = {"
        echo "    'pg':   '\$DIR/../../SharedSupport',"
        echo "    'ppas': ''"
        echo "}"
    } > config_distro.py

    # License files
    cp -r "${SOURCE_DIR}/LICENSE" "${BUNDLE_DIR}/Contents/"

    # Remove the .pyc files if any
    find "${BUNDLE_DIR}" -name "*.pyc" -print0 | xargs -0 rm -f

    # Update permissions to make sure all users can access installed pgadmin.
    chmod -R og=u "${BUNDLE_DIR}"
    chmod -R og-w "${BUNDLE_DIR}"
}

_generate_sbom() {
   echo "Generating SBOM..."
   syft "${BUNDLE_DIR}/Contents/" -o cyclonedx-json > "${BUNDLE_DIR}/Contents/sbom.json"
}

_codesign_binaries() {
    if [ "${CODESIGN}" -eq 0 ]; then
        return
    fi

    if [ -z "${DEVELOPER_ID}" ] ; then
        echo "Developer ID Application not found in codesign.conf" >&2
        exit 1
    fi

    # Create the entitlements file
    cp "${SCRIPT_DIR}/entitlements.plist.in" "${BUILD_ROOT}/entitlements.plist"
    TEAM_ID=$(echo "${DEVELOPER_ID}" | awk -F"[()]" '{print $2}')
    sed -i '' "s/%TEAMID%/${TEAM_ID}/g" "${BUILD_ROOT}/entitlements.plist"

    echo Signing "${BUNDLE_DIR}" binaries...
    IFS=$'\n'
    for i in $(find "${BUNDLE_DIR}" -type f -perm +111 -exec file "{}" \; | \
               grep -v "(for architecture" | \
               grep -v -E "^- Mach-O" | \
               grep -E "Mach-O executable|Mach-O 64-bit executable|Mach-O 64-bit bundle|Mach-O 64-bit dynamically linked shared library" | \
               awk -F":" '{print $1}' | \
               uniq)
    do
        codesign --deep --force --verify --verbose --timestamp \
                 --options runtime \
                 --entitlements "${BUILD_ROOT}/entitlements.plist" \
                 -i org.pgadmin.pgadmin4 \
                 --sign "${DEVELOPER_ID}" \
                 "$i"
    done

    echo Signing "${BUNDLE_DIR}" libraries...
    find "${BUNDLE_DIR}" -type f -name "*.dylib*" -exec \
        codesign --deep --force --verify --verbose --timestamp \
                 --options runtime \
                 --entitlements "${BUILD_ROOT}/entitlements.plist" \
                 -i org.pgadmin.pgadmin4 \
                 --sign "${DEVELOPER_ID}" \
                 {} \;
}

_codesign_bundle() {
    if [ "${CODESIGN}" -eq 0 ]; then
        return
    fi

    # Sign the .app
    echo Signing "${BUNDLE_DIR}"...
    codesign --deep --force --verify --verbose --timestamp \
             --options runtime \
             --entitlements "${BUILD_ROOT}/entitlements.plist" \
             -i org.pgadmin.pgadmin4 \
             --sign "${DEVELOPER_ID}" \
             "${BUNDLE_DIR}"

    echo "Verifying the signature from bundle dir..."
    codesign --verify --deep --verbose=4 "${BUNDLE_DIR}"
}

_create_zip() {
    ZIP_NAME="${DMG_NAME%.dmg}.zip"
    echo "ZIP_NAME: ${ZIP_NAME}"

    echo "Compressing pgAdmin 4.app in bundle dir into ${ZIP_NAME}..."
    ditto -c -k --sequesterRsrc --keepParent "${BUNDLE_DIR}" "${ZIP_NAME}"

    if [ $? -ne 0 ]; then
        echo "Failed to create the ZIP file. Exiting."
        exit 1
    fi

    echo "Successfully created ZIP file: ${ZIP_NAME}"
}

_create_dmg() {
    # move to the directory where we want to create the DMG
    test -d "${DIST_ROOT}" || mkdir "${DIST_ROOT}"

    echo "Checking out create-dmg..."
    git clone https://github.com/create-dmg/create-dmg.git "${BUILD_ROOT}/create-dmg"

    "${BUILD_ROOT}/create-dmg/create-dmg" \
        --volname "${APP_NAME}" \
        --volicon "${SCRIPT_DIR}/dmg-icon.icns" \
        --eula "${SCRIPT_DIR}/licence.rtf" \
        --background "${SCRIPT_DIR}/dmg-background.png" \
        --app-drop-link 600 220 \
        --icon "${APP_NAME}.app" 200 220 \
        --window-pos 200 120 \
        --window-size 800 400 \
        --hide-extension "${APP_NAME}.app" \
        --add-file .DS_Store "${SCRIPT_DIR}/dmg.DS_Store" 5 5 \
        --format UDBZ \
        --skip-jenkins \
        --no-internet-enable \
        "${DMG_NAME}" \
        "${BUNDLE_DIR}"
}

_codesign_dmg() {
    if [ "${CODESIGN}" -eq 0 ]; then
        return
    fi

    # Sign the .app
    echo Signing disk image...
    codesign --force --verify --verbose --timestamp \
             --options runtime \
             -i org.pgadmin.pgadmin4 \
             --sign "${DEVELOPER_ID}" \
             "${DMG_NAME}"
}

_notarize_pkg() {
    local FILE_NAME="$1"
    local STAPLE_TARGET="$2"
    local FILE_LABEL="$3"

    if [ "${CODESIGN}" -eq 0 ]; then
        return
    fi

    echo "Uploading ${FILE_LABEL} for Notarization ..."
    STATUS=$(xcrun notarytool submit "${FILE_NAME}" \
        --team-id "${DEVELOPER_TEAM_ID}" \
        --apple-id "${DEVELOPER_USER}" \
        --password "${DEVELOPER_ASP}" 2>&1)

    echo "${STATUS}"

    # Get the submission ID
    SUBMISSION_ID=$(echo "${STATUS}" | awk -F ': ' '/id:/ { print $2; exit; }')
    echo "Notarization submission ID: ${SUBMISSION_ID}"

    echo "Waiting for Notarization to be completed ..."
    xcrun notarytool wait "${SUBMISSION_ID}" \
        --team-id "${DEVELOPER_TEAM_ID}" \
        --apple-id "${DEVELOPER_USER}" \
        --password "${DEVELOPER_ASP}"

    # Print status information
    REQUEST_STATUS=$(xcrun notarytool info "${SUBMISSION_ID}" \
        --team-id "${DEVELOPER_TEAM_ID}" \
        --apple-id "${DEVELOPER_USER}" \
        --password "${DEVELOPER_ASP}" 2>&1 | \
        awk -F ': ' '/status:/ { print $2; }')

    if [[ "${REQUEST_STATUS}" != "Accepted" ]]; then
        echo "Notarization failed."
        exit 1
    fi

    # Staple the notarization
    echo "Stapling the notarization to the ${FILE_LABEL}..."
    if ! xcrun stapler staple "${STAPLE_TARGET}"; then
        echo "Stapling failed."
        exit 1
    fi

    # For ZIP, recreate the zip after stapling
    if [[ "${FILE_LABEL}" == "ZIP" ]]; then
        ditto -c -k --keepParent "${BUNDLE_DIR}" "${ZIP_NAME}"
        if [ $? != 0 ]; then
            echo "ERROR: could not staple ${ZIP_NAME}"
            exit 1
        fi
    fi

    echo "Notarization completed successfully."
}

_notarize_zip() {
    _notarize_pkg "${ZIP_NAME}" "${BUNDLE_DIR}" "ZIP"
}

_notarize_dmg() {
    _notarize_pkg "${DMG_NAME}" "${DMG_NAME}" "DMG"
}
