#!/usr/bin/env bash

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

set -o errexit
set -o nounset
set -o pipefail

REMOTE_NAME=sync-fork
LOG_FILE=test-wpt.log
CURRENT_DATE=$(date +"%d-%m-%Y")
BRANCH_NAME="wpt_update_${CURRENT_DATE}"

export GIT_AUTHOR_NAME="WPT Sync Bot"
export GIT_AUTHOR_EMAIL="josh+wptsync@joshmatthews.net"
export GIT_COMMITTER_NAME="${GIT_AUTHOR_NAME}"
export GIT_COMMITTER_EMAIL="${GIT_AUTHOR_EMAIL}"

# Retrieve the HEAD commit and extract its hash
function latest_git_commit() {
    git log -1 --oneline | cut -f 1 -d ' '
}

# Create a new branch for this sync, pull down all changes from the upstream
# web-platform-tests repository, and commit the changes.
function unsafe_pull_from_upstream() {
    git checkout -b "${1}" || return 1

    OLD_COMMIT=$(latest_git_commit)

    # Fetch all changes from upstream WPT and automatically transpose them
    # into a single servo commit.
    ./mach update-wpt --sync --no-upstream --patch || return 2

    # If there was no new commit created, there are no changes that need syncing.
    # Skip the remaining steps.
    if [[ "$(latest_git_commit)" == "${OLD_COMMIT}" ]]; then
        return 255
    fi

    # Update the manifest to include the new changes.
    ./mach update-manifest --release || return 3

    # Amend the existing commit with the new changes from updating the manifest.
    git commit -a --amend --no-edit || return 4
}

# Remove all local traces of this sync operation.
function cleanup() {
    git remote rm "${REMOTE_NAME}" || true
    git reset --hard || true
    git checkout master || true
    git branch -D "${BRANCH_NAME}" || true
    ./mach update-wpt --abort || true
}

# Build Servo and run the full WPT testsuite, saving the results to a log file.
function unsafe_run_tests() {
    # Run the full testsuite and record the new test results.
    ./mach test-wpt --release --processes 6 --log-raw "${1}" \
           --always-succeed || return 1
}

# Using an existing log file, update the expected test results and amend the
# last commit with the new results.
function unsafe_update_metadata() {
    ./mach update-wpt "${1}" || return 1
    # Ensure any new directories or ini files are included in these changes.
    git add tests/wpt/metadata tests/wpt/mozilla/meta || return 2
    # Merge all changes with the existing commit.
    git commit -a --amend --no-edit || return 3
}

# Push the branch to a remote branch, then open a PR for the branch
# against servo/servo.
function unsafe_open_pull_request() {
    WPT_SYNC_USER=servo-wpt-sync

    # If the branch doesn't exist, we'll silently exit. This deals with the
    # case where an earlier step either failed or discovered that syncing
    # is unnecessary.
    git checkout "${BRANCH_NAME}" || return 0

    if [[ -z "${WPT_SYNC_TOKEN+set}" ]]; then
        echo "Github auth token missing from WPT_SYNC_TOKEN."
        return 1
    fi

    # Push the changes to a remote branch owned by the bot.
    AUTH="${WPT_SYNC_USER}:${WPT_SYNC_TOKEN}"
    UPSTREAM="https://${AUTH}@github.com/${WPT_SYNC_USER}/servo.git"
    git remote add "${REMOTE_NAME}" "${UPSTREAM}" || return 2
    git push -f "${REMOTE_NAME}" "${BRANCH_NAME}" &>/dev/null || return 3

    # Prepare the pull request metadata.
    BODY=":warning: Do not merge this PR without verifying that it "
    BODY+="is not overwriting local changes to web-platform-tests. :warning:\n\n"
    BODY+="Automated downstream sync of changes from upstream as of "
    BODY+="${CURRENT_DATE}.\n"
    BODY+="[no-wpt-sync]"
    cat <<EOF >prdata.json || return 4
{
  "title": "[WIP] Sync WPT with upstream (${CURRENT_DATE})",
  "head": "${WPT_SYNC_USER}:${BRANCH_NAME}",
  "base": "master",
  "body": "${BODY}",
  "maintainer_can_modify": true
}
EOF

    # Open a pull request using the new branch.
    curl -H "Authorization: token ${WPT_SYNC_TOKEN}" \
         -H "Content-Type: application/json" \
         --data @prdata.json \
         https://api.github.com/repos/servo/servo/pulls || return 5
}

function pull_from_upstream() {
    unsafe_pull_from_upstream "${1}" || { code="${?}"; cleanup; return "${code}"; }
}

function run_tests() {
    unsafe_run_tests "${1}" || { code="${?}"; cleanup; return "${code}"; }
}

function update_metadata() {
    unsafe_update_metadata "${1}" || { code="${?}"; cleanup; return "${code}"; }
}

function open_pull_request() {
    unsafe_open_pull_request || { code="${?}"; cleanup; return "${code}"; }
}

SCRIPT_NAME="${0}"

function update_test_results() {
    run_tests "${LOG_FILE}"
    update_metadata "${LOG_FILE}"
}

function fetch_upstream_changes() {
    pull_from_upstream "${BRANCH_NAME}"
}

function usage() {
    echo "usage: ${SCRIPT_NAME} [cmd]"
    echo "  commands:"
    echo "  - fetch-upstream-changes: create a branch with the latest changes from upstream"
    echo "  - update-test-results: run the tests, update the expected test results, and commit the changes"
    echo "  - fetch-and-update-expectations: combines fetch-upstream-changes and update-test-results"
    echo "  - open-pr: open a pull request with the latest changes"
    echo "  - cleanup: cleanup all traces of an in-progress sync and checkout the master branch"
    exit 1
}

function main() {
    if [[ "${1}" == "fetch-upstream-changes" ]] || [[ "${1}" == "fetch-and-update-expectations" ]]; then
        code=""
        fetch_upstream_changes || code="${?}"
        if [[ "${code}" == "255" ]]; then
            echo "No changes to sync."
            return 0
        elif [[ "${code}" != "" ]]; then
            return "${code}"
        fi
    fi

    if [[ "${1}" == "update-test-results" ]] || [[ "${1}" == "fetch-and-update-expectations" ]]; then
        update_test_results

    elif [[ "${1}" == "open-pr" ]]; then
        open_pull_request

    elif [[ "${1}" == "cleanup" ]]; then
        cleanup

    else
        usage
    fi
}

if [[ "$#" != 1 ]]; then
    usage
fi

# Ensure we clean up after ourselves if this script is interrupted.
trap 'cleanup' SIGINT SIGTERM
main "${1}"
