#!/usr/bin/env bash
# Stub WP-CLI for the test bench.
#
# It behaves like the real thing where it matters for these tests:
#   - global flags (--path=, --allow-root, --quiet, --format=, --fields=,
#     --update=available) may appear anywhere, so the subcommand is the first
#     two NON-flag arguments;
#   - `core check-update` exits 1 when there is nothing to do, like the real one;
#   - `plugin list --format=json` emits a flat JSON array, `--format=csv` a
#     comma separated table, `--format=count` a single number;
#   - `db size`, `db import`, `db check` answer like the real subcommands.
#
# It records its argv, cwd, user, the environment contract and whether a
# licence arrived, WITHOUT ever writing a secret value to the log.
LOG="${FAKE_WP_LOG:-/tmp/wp_invocations.log}"

LIC_STATE='<unset>'
if [ -n "${WP_CLI_LICENCE:-}" ]; then
    LIC_STATE="<set,len=${#WP_CLI_LICENCE},cksum=$(printf '%s' "$WP_CLI_LICENCE" | cksum | cut -d' ' -f1)>"
fi
# One record, built in a variable and appended with a single write under an exclusive
# lock. The previous version emitted the line in seven separate printf calls inside a
# redirected group: with -j N the workers interleaved their writes and two records could
# land on one line, so a test that counts invocations saw four where five ran. The
# product was fine; the recorder was not.
argv_line="ARGV:"
for a in "$@"; do argv_line="${argv_line} [${a}]"; done
argv_line="${argv_line} | CWD=${PWD} USER=$(id -un)"
argv_line="${argv_line} | ASTRFAIL=${ASTRA_FAIL_SLUG:-<unset>}"
argv_line="${argv_line} | DOCUMENT_ROOT=${DOCUMENT_ROOT:-<unset>} HOMEDIR=${HOMEDIR:-<unset>} HTTP_HOST=${HTTP_HOST:-<unset>} DOCUMENT_URI=${DOCUMENT_URI:-<unset>} HOME=${HOME:-<unset>} LOGNAME=${LOGNAME:-<unset>}"
argv_line="${argv_line} | LICENCE=${LIC_STATE}"
if command -v flock >/dev/null 2>&1; then
    (
        flock 9
        printf '%s\n' "$argv_line" >&9
    ) 9>>"$LOG"
else
    printf '%s\n' "$argv_line" >>"$LOG"
fi

fmt='table' fields='' only_update='' size_fmt=''
positional=()
for a in "$@"; do
    case "$a" in
        --quiet) ;;            # honoured implicitly: the stub is quiet anyway
        --format=*) fmt="${a#--format=}" ;;
        --fields=*) fields="${a#--fields=}" ;;
        --update=*) only_update="${a#--update=}" ;;
        --size-format=* | --size_format=*) size_fmt="${a#*=}" ;;
        --path=*) SITE_PATH="${a#--path=}" ;;
        --*) ;;                       # any other global flag: ignore
        *) positional+=("$a") ;;
    esac
done
cmd="${positional[0]-}"
sub="${positional[1]-}"
third="${positional[2]-}"

PLUGINS=(
    "Akismet Anti-Spam|active|none|5.3|akismet|Akismet Anti-Spam|"
    "WooCommerce|inactive|available|8.1.0|woocommerce|WooCommerce|9.0.0"
    "All in One SEO|active|none|4.4.0|all-in-one-seo-pack|All in One SEO (AIOSEO)|"
    "Jetpack|active|available|13.0|jetpack|Jetpack|13.5"
    "Old Plugin|inactive|available|1.0|old-plugin|Old Plugin|1.1"
)

THEMES=(
        "Twenty Twenty-Four|active|none|1.1|twentytwentyfour|Twenty Twenty-Four|"
        "Astra|active|available|4.6.0|astra|Astra|4.7.0"
)

rows_for() { # kind -> ref to the array via name
    case "$1" in
        theme) printf '%s\n' "${THEMES[@]}" ;;
        *) printf '%s\n' "${PLUGINS[@]}" ;;
    esac
}

emit_rows() { # kind: plugin|theme
    local kind="$1" r n st up v sl ti uv out f v_ emitted=0 count=0
    local -a want=()
    [ -n "$fields" ] && IFS=',' read -r -a want <<<"$fields" || want=(name status update version)
    # A header is printed even when nothing matches, like the real WP-CLI: a
    # consumer that counts rows relies on the header being one of them.
    case "$fmt" in
        count) ;;
        json) printf '[' ;;
        csv) printf '%s\n' "$(printf '%s' "${want[*]}" | tr ' ' ',')" ;;
        *)
            out=''
            for f in "${want[@]}"; do out+="${out:+$'\t'}${f}"; done
            printf '%s\n' "$out"
            ;;
    esac
    while IFS= read -r r; do
        [ -n "$r" ] || continue
        IFS='|' read -r n st up v sl ti uv <<<"$r"
        # FAKE_VERSION_BUMP=1: after a successful `plugin update` the reported
        # version becomes the update version, so the change log can be tested.
        if [ "${FAKE_VERSION_BUMP:-0}" = '1' ] && [ -f "${LOG}.bumped" ] && [ -n "$uv" ]; then
            v="$uv"; up='none'
        fi
        # FAKE_NO_UPDATES=1: nothing is pending, whatever the fixture says.
        if [ "${FAKE_NO_UPDATES:-0}" = '1' ] && [ -n "$only_update" ]; then
            continue
        fi
        if [ -n "$only_update" ] && [ "$up" != "$only_update" ]; then continue; fi
        count=$((count + 1))
        case "$fmt" in
            count) continue ;;
            json)
                if ((emitted)); then printf ','; fi
                emitted=1
                printf '{"name":"%s","status":"%s","update":"%s","version":"%s","slug":"%s","title":"%s","update_version":"%s"}' \
                    "$n" "$st" "$up" "$v" "$sl" "$ti" "$uv"
                ;;
            csv)
                out=''
                for f in "${want[@]}"; do
                    case "$f" in
                        name) v_="$n" ;; status) v_="$st" ;; update) v_="$up" ;;
                        version) v_="$v" ;; slug) v_="$sl" ;; title) v_="$ti" ;;
                        update_version) v_="$uv" ;; *) v_='' ;;
                    esac
                    out+="${out:+,}${v_}"
                done
                printf '%s\n' "$out"
                ;;
            *)
                out=''
                for f in "${want[@]}"; do
                    case "$f" in
                        name) v_="$n" ;; status) v_="$st" ;; update) v_="$up" ;;
                        version) v_="$v" ;; slug) v_="$sl" ;; title) v_="$ti" ;;
                        update_version) v_="$uv" ;; *) v_='' ;;
                    esac
                    out+="${out:+$'\t'}${v_}"
                done
                printf '%s\n' "$out"
                ;;
        esac
    done < <(rows_for "$kind")
    case "$fmt" in
        count) printf '%s\n' "$count" ;;
        json) printf ']\n' ;;
    esac
}

# fail_once: FAKE_FAIL_ONCE=<slug> makes the first update of that slug fail and
# every later one succeed, so the --retry path can be exercised for real.
fail_once() { # SLUG ACTUAL_SLUG
    [ -n "${FAKE_FAIL_ONCE:-}" ] || return 1
    [ "${FAKE_FAIL_ONCE}" = "$1" ] || [ "${FAKE_FAIL_ONCE}" = "$2" ] || return 1
    local marker="${LOG}.failed-once"
    if [ -f "$marker" ] && grep -Fxq -- "$2" "$marker" 2>/dev/null; then
        return 1
    fi
    printf '%s\n' "$2" >>"$marker" 2>/dev/null
    return 0
}

case "$cmd" in
    --version | version | '')
        # `wp --version` has no subcommand; the real binary answers it directly.
        if [ "$cmd" = '--version' ] || [ "$cmd" = 'version' ] || [ -z "$cmd" ]; then
            case "$sub" in '' | --*) echo 'WP-CLI 2.10.0'; exit 0 ;; esac
        fi
        ;;
esac

case "$cmd $sub" in
    'core version') echo '6.4.3' ;;
    'core check-update')
        if [ "${FAKE_CORE_UPDATE:-0}" = '1' ]; then
            echo '+---------------------+-------------+'
            echo '| Version             | 6.5.1       |'
            echo '+---------------------+-------------+'
            exit 0
        fi
        echo 'Success: WordPress is at the latest version.'
        exit 1 ;;                                  # like the real WP-CLI
    'core update') echo 'Success: WordPress updated successfully.' ;;
    'core update-db') echo 'Success: WordPress database upgraded successfully.' ;;
    'plugin list') emit_rows plugin ;;
    'theme list') emit_rows theme ;;
    'plugin status') echo "Plugin ${third:-unknown} is active" ;;
    'plugin activate') echo 'Success: Activated plugin.' ;;
    'plugin deactivate') echo 'Success: Deactivated plugin.' ;;
    'plugin delete') echo 'Success: Deleted plugin.' ;;
    'plugin update')
        # Test hooks: one slug can be made to fail, or to fail exactly once.
        if [ -n "${FAKE_FAIL_ALWAYS:-}" ] && [ "$third" = "${FAKE_FAIL_ALWAYS}" ]; then
            echo 'Error: simulated permanent failure.' >&2
            exit 1
        fi
        if [ -n "${ASTRA_FAIL_SLUG:-}" ] && [ "$third" = "${ASTRA_FAIL_SLUG}" ]; then
            echo 'Error: The plugin download failed.' >&2
            exit 1
        fi
        if fail_once "${FAKE_FAIL_ONCE:-}" "$third"; then
            echo 'Error: could not copy file (simulated transient failure).' >&2
            exit 1
        fi
        printf '%s\n' "updated" >>"${LOG}.bumped" 2>/dev/null
        echo 'Success: Updated 2 of 2 plugins.' ;;
    'theme update') echo 'Success: Updated 1 of 1 theme.' ;;
    'db export')
        # write something to the requested path, like the real one does
        out="${positional[2]:-}"
        if [ -n "$out" ] && [ "$out" != '/dev/null' ]; then
            printf -- '-- MySQL dump (stub)\n-- site: %s\nSELECT 1;\n' "${SITE_PATH:-unknown}" >"$out" 2>/dev/null || {
                echo 'Error: cannot write the dump' >&2; exit 1; }
        fi
        echo 'Success: Exported to the given path.' ;;
    'db import')
        out="${positional[2]:-}"
        if [ ! -s "$out" ]; then
            echo 'Error: the file to import does not exist or is empty.' >&2
            exit 1
        fi
        echo 'Success: Imported from the given file.' ;;
    'db size')
        case "$size_fmt" in
            mb) echo '12.5 MB' ;;
            gb) echo '0.01 GB' ;;
            *) echo '13107200' ;;
        esac ;;
    'db check') echo 'Success: Your tables are fine.' ;;
    'db optimize') echo 'Success: Database optimized.' ;;
    'db repair') echo 'Success: Database repaired.' ;;
    'cron event') echo 'Success: Executed a total of 3 cron events.' ;;
    'cli version') echo 'WP-CLI 2.10.0' ;;
    'core verify-checksums') echo 'Success: WordPress install verifies against checksums.' ;;
    'plugin verify-checksums') echo 'Success: Plugin verifies against checksums.' ;;
    'brainstormforce license')
        if [ -z "${WP_CLI_LICENCE:-}" ]; then
            echo 'Error: the licence value arrived empty' >&2
            exit 1
        fi
        echo "Success: licence accepted (${LIC_STATE})" ;;
    *) echo "Success: ${cmd} ${sub} ok" ;;
esac
exit 0
