#!/usr/bin/env bash
#
# Runs each *Test.php file in its own process so standalone WordPress stub
# functions cannot redeclare across files. tests/unit is a mixed corpus:
# some files declare a class extending PHPUnit\Framework\TestCase and run
# through vendor/bin/phpunit; the rest are procedural scripts (inline stubs,
# assert-by-throwing RuntimeException) that run through php directly.
# Dispatch is decided per file by tokenizing its real PHP code, not by
# filename or a text-substring match, so a TestCase class embedded in a
# fixture heredoc string never causes a false match.

set -euo pipefail

if [[ $# -eq 0 ]]; then
    echo "Usage: $0 <file-or-dir> [file-or-dir ...]" >&2
    exit 1
fi

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
plugin_root="$(cd "${script_dir}/.." && pwd)"
phpunit_bin="${plugin_root}/vendor/bin/phpunit"

if [[ ! -x "${phpunit_bin}" ]]; then
    echo "run-unit-tests: vendor/bin/phpunit not found or not executable at ${phpunit_bin}" >&2
    exit 1
fi

php_bin="$(command -v php || true)"
if [[ -z "${php_bin}" ]]; then
    echo "run-unit-tests: php binary not found on PATH" >&2
    exit 1
fi

declare -a test_files=()

for path in "$@"; do
    if [[ -f "${path}" ]]; then
        test_files+=("${path}")
    elif [[ -d "${path}" ]]; then
        while IFS= read -r -d '' found; do
            test_files+=("${found}")
        done < <(find "${path}" -type f -name '*Test.php' -print0 | sort -z)
    else
        echo "run-unit-tests: path not found: ${path}" >&2
        exit 1
    fi
done

if [[ ${#test_files[@]} -eq 0 ]]; then
    echo "run-unit-tests: no *Test.php files discovered" >&2
    exit 1
fi

classifier="$(cat <<'PHP_CLASSIFIER'
$file = $argv[1] ?? null;
if ($file === null || !is_file($file)) {
    exit(2);
}
$tokens = token_get_all(file_get_contents($file));
$isTestCase = false;
$count = count($tokens);
for ($i = 0; $i < $count; $i++) {
    $tok = $tokens[$i];
    if (!is_array($tok) || $tok[0] !== T_CLASS) {
        continue;
    }
    for ($j = $i + 1; $j < $count; $j++) {
        $t = $tokens[$j];
        if ($t === '{') {
            break;
        }
        if (is_array($t) && $t[0] === T_EXTENDS) {
            $name = '';
            for ($k = $j + 1; $k < $count; $k++) {
                $tt = $tokens[$k];
                if ($tt === '{') {
                    break;
                }
                if (is_array($tt) && ($tt[0] === T_STRING || $tt[0] === T_NS_SEPARATOR)) {
                    $name .= $tt[1];
                    continue;
                }
                if (is_array($tt) && $tt[0] === T_WHITESPACE) {
                    continue;
                }
                break;
            }
            $parts = explode('\\', $name);
            if (end($parts) === 'TestCase') {
                $isTestCase = true;
            }
            break;
        }
    }
    if ($isTestCase) {
        break;
    }
}
exit($isTestCase ? 0 : 1);
PHP_CLASSIFIER
)"

is_phpunit_testcase() {
    "${php_bin}" -r "${classifier}" -- "$1"
}

is_wordpress_integration_test() {
    local relative="${1#"${plugin_root}/"}"
    relative="${relative#./}"
    case "${relative}" in
        tests/integration/AutoPublishStatusTest.php|tests/integration/Core/ContentManagerTest.php|tests/integration/SettingsIdempotencyTest.php) return 0 ;;
        *) return 1 ;;
    esac
}

baseline_file="${plugin_root}/tests/known-failing.txt"
declare -A baseline=()
if [[ -f "${baseline_file}" ]]; then
    while IFS= read -r line; do
        line="${line%%#*}"
        line="${line#"${line%%[![:space:]]*}"}"
        line="${line%"${line##*[![:space:]]}"}"
        [[ -n "${line}" ]] && baseline["${line}"]=1
    done < "${baseline_file}"
fi

overall_exit=0
phpunit_files=0
procedural_files=0
phpunit_executed=0
declare -a unexpected_failures=()
declare -a unexpected_passes=()

for file in "${test_files[@]}"; do
    file_exit=0
    if is_wordpress_integration_test "${file}"; then
        echo "run-unit-tests: live WordPress test must run through tests/run-wordpress-integration-tests.sh: ${file}" >&2
        unexpected_failures+=("${file#"${plugin_root}/"}")
        overall_exit=1
        continue
    fi
    if is_phpunit_testcase "${file}"; then
        ((phpunit_files+=1))
        echo "=== ${file} [phpunit] ==="
        output_file="$(mktemp)"
        "${phpunit_bin}" --no-configuration --do-not-cache-result "${file}" >"${output_file}" 2>&1 || file_exit=$?
        cat "${output_file}"
        # PHPUnit exits zero for a discovered class with no executable tests.
        # Treat that as a harness failure rather than silently counting it green.
        if [[ ${file_exit} -eq 0 ]] && ! grep -Eq 'OK \([1-9][0-9]* (test|tests)' "${output_file}"; then
            echo "run-unit-tests: PHPUnit-classified file executed zero tests: ${file}" >&2
            file_exit=1
        elif [[ ${file_exit} -eq 0 ]]; then
            ((phpunit_executed+=1))
        fi
        rm -f "${output_file}"
    else
        ((procedural_files+=1))
        echo "=== ${file} [php] ==="
        "${php_bin}" "${file}" || file_exit=$?
    fi
    echo "--- exit ${file_exit}: ${file} ---"
    relative="${file#"${plugin_root}/"}"
    relative="${relative#./}"
    if [[ -n "${baseline[${relative}]:-}" ]]; then
        if [[ ${file_exit} -eq 0 ]]; then
            unexpected_passes+=("${relative}")
        fi
    elif [[ ${file_exit} -ne 0 ]]; then
        unexpected_failures+=("${relative}")
        if [[ ${overall_exit} -eq 0 ]]; then
            overall_exit=${file_exit}
        fi
    fi
done

echo "run-unit-tests: discovered=${#test_files[@]} phpunit=${phpunit_files} procedural=${procedural_files} phpunit-executed=${phpunit_executed} known-failing=${#baseline[@]}"

for entry in "${unexpected_failures[@]}"; do
    echo "run-unit-tests: unexpected failure: ${entry}" >&2
done
for entry in "${unexpected_passes[@]}"; do
    echo "run-unit-tests: ${entry} now passes; remove it from tests/known-failing.txt" >&2
done
if [[ ${#unexpected_passes[@]} -gt 0 && ${overall_exit} -eq 0 ]]; then
    overall_exit=1
fi

exit "${overall_exit}"
