#!/usr/bin/bash
# shellcheck disable=SC2317,SC2329

VDK_TEST__BUILDDIR=${VDK_TEST__BUILDDIR:-}

VDK_TEST__OPEN_SHELL=${VDK_TEST__OPEN_SHELL:-never}

VDK_TEST__DEBUG=${VDK_TEST__DEBUG:-false}

VDK_TEST__KIT=${VDK_TEST__KIT:-}

VDK_TEST__PYLIB_MODNAME=${VDK_TEST__PYLIB_MODNAME:-}

usage() {
    local err=$1
    echo >&2 "usage: $Self $(list_suites | sed 's/^[^:]*://' | paste -sd'|') [args]"
    test -n "$err" && {
        echo
        echo "$err"
    }
    exit 2
}

list_suites() {
    echo "_:bin_t"
    echo "_:shell"
    echo "_:shellcheck"
    echo "_:tfkit"
    echo "pylib:doctest"
    echo "pylib:mypy"
    echo "pylib:pdb"
    echo "pylib:pytest"
    echo "pylib:unittest"
    echo "pylib:venv"
}

debug() {
    local msg
    test "$VDK_TEST__DEBUG" = true || return 0
    for msg in "$@"; do
        echo >&2 "$Self: debug: $msg"
    done
}

warn() {
    local msg
    for msg in "$@"; do
        echo >&2 "$Self: $msg"
    done
}

die() {
    local msg
    for msg in "$@"; do
        warn "$Self: fatal: $msg"
    done
    exit 3
}

maybe_open_shell() {
    #
    # Open shell if needed based on $SUITE_ES and $VDK_TEST__OPEN_SHELL
    #
    local suffix
    test -n "$VDK_TEST__PYLIB_MODNAME" && suffix=":$VDK_TEST__PYLIB_MODNAME"
    case $SUITE_ES:$VDK_TEST__OPEN_SHELL in
        0:onfail)   return 0 ;;
        2:onfail)   return 0 ;;
        *:onfail)   true ;;
        *:always)   true ;;
        *:never)    return 0 ;;
        *)  warn "ignoring invalid option value (not onfail|always|never): VDK_TEST__OPEN_SHELL=$VDK_TEST__OPEN_SHELL" ;;
    esac
    warn "dropping to interactive shell: SUITE_ES=$SUITE_ES VDK_TEST__OPEN_SHELL=$VDK_TEST__OPEN_SHELL"
    BASHUM_CTXSYM="vdk_test:$Suite$suffix SUITE_ES=$?" \
        bash
}

sandbox_fn() {
    local fn=$1; shift
    local FnArgs=("$@")
    debug "running in sandbox: fn=$fn"
    (
        BOXDIR="$Self/$fn"
        mkdir -p "$BOXDIR"
        ln -sr "$BuildDir" "$BOXDIR"
        cd "$BOXDIR" || exit 5
        export VDK_TEST__S_SUITE=$Suite
        export VDK_TEST__S_PYLIB_MODNAME=$VDK_TEST__PYLIB_MODNAME
        export VDK_TEST__S_SRCDIR=$SrcDir
        export PYTHONPATH="$PWD"
        eval "$fn"; SUITE_ES=$?
        maybe_open_shell
        exit "$SUITE_ES"
    )
}

drive_bin_t() {
    #
    # Try running simple test binaries
    #
    (
        FINAL_ES=0
        T_ES=0
        TT_ES=0
        test ${#FnArgs[*]} -gt 0 || {
            warn "drive_bin_t(): no bin_t's provided: FnArgs=${FnArgs[*]}"
            return 2
        }
        for BIN_T in "${FnArgs[@]}"; do
            warn "drive_bin_t(): running...: $BIN_T"
            "$SrcDir/$BIN_T"; T_ES=$?
            case $T_ES in
                0)  warn "drive_bin_t(): ... passed: $BIN_T -> $T_ES"; TT_ES=0 ;;
                1)  warn "drive_bin_t(): .!. failed: $BIN_T -> $T_ES"; TT_ES=1 ;;
                2)  warn "drive_bin_t(): .. skipped: $BIN_T -> $T_ES"; TT_ES=0 ;;
                *)  warn "drive_bin_t(): !!! exited with invalid status: $T_ES is not 0-2"; TT_ES=5 ;;
            esac
            test "$TT_ES" -gt "$FINAL_ES" && FINAL_ES=$TT_ES
        done
        exit "$FINAL_ES"
    )
}

drive_doctest() {
    #
    # Try running doctest
    #
    {
        echo 'import sys'
        echo 'import doctest'
        echo ''
        echo "import $VDK_TEST__PYLIB_MODNAME"
        echo ''
        echo "MODNAME = '$VDK_TEST__PYLIB_MODNAME'"
        echo ''
        echo 'FLAGS = ('
        echo '    doctest.IGNORE_EXCEPTION_DETAIL'
        echo '    | doctest.REPORT_NDIFF'
        echo '    | doctest.DONT_ACCEPT_TRUE_FOR_1'
        echo ')'
        echo ''
        echo ''
        echo 'def is_ours(module_name):'
        echo '    return any(['
        echo '        module_name == MODNAME,'
        echo '        module_name.startswith(MODNAME + "."),'
        echo '    ])'
        echo ''
        echo ''
        echo 'def main():'
        echo '    modules_togo = ['
        echo '        sys.modules[mn]'
        echo '        for mn in sys.modules.keys()'
        echo '        if is_ours(mn)'
        echo '    ]'
        echo '    '
        echo '    total_fails = 0'
        echo '    '
        echo '    for module in modules_togo:'
        echo '        fails, total = doctest.testmod(m=module, optionflags=FLAGS)'
        echo "        print(f'$Self:drive_doctest: {module.__name__}: {fails} failures / {total} total', file=sys.stderr)"
        echo '        total_fails += fails'
        echo '    '
        echo '    if total_fails:'
        echo '        sys.exit(1)'
        echo '    else:'
        echo '        sys.exit(0)'
        echo ''
        echo ''
        echo 'if __name__ == "__main__":'
        echo '    main()'
    } >run_doctests.py
    test ${#FnArgs[*]} -gt 0 \
     && warn "ignoring extra args for doctest; not supported: ${FnArgs[*]}"
    python3 run_doctests.py
}

drive_mypy() {
    #
    # Try running mypy
    #
    command -v mypy >/dev/null || {
        warn "drive_mypy(): skipping; mypy is not available"
        return 2
    }
    (
        export MYPY_CACHE_DIR=/dev/null
        eval "mypy ${FnArgs[*]} $VDK_TEST__PYLIB_MODNAME"
    )
}

validate_cmd() {
    #
    # Validate command $1
    #
    # Command can be path to binary or plain command;
    # each requires different validation.
    #
    local cmd=$1
    case $cmd in
        "")     return 1 ;;
        */*)    test -x "$cmd" ;;
        *)      command -v "${cmd%% *}" >/dev/null ;;
    esac
}

drive_pdb() {
    #
    # Try opening pdb
    #
    (
        PDBCMD="${FnArgs[0]}"
        SCRIPT="${FnArgs[1]}"
        validate_cmd "$PDBCMD" \
         || die "invalid pdb command: ${FnArgs[*]} (should be 'CMD SCRIPT')"
        test -n "$SCRIPT" \
         || die "missing script argument: ${FnArgs[*]} (should be 'CMD SCRIPT')"
        test -f "$SrcDir/$SCRIPT" \
         || die "no such script under source dir: $SCRIPT"
        "$PDBCMD" "$SrcDir/$SCRIPT"
    )
}

drive_venv() {
    #
    # Try opening venv
    #
    (
        VENVSCRIPT="$SrcDir/${FnArgs[0]}"
        VENVDIR=${FnArgs[1]}
        VENVCMD=${FnArgs[2]}
        validate_cmd "$VENVCMD" \
         || die "invalid command: $VENVCMD"
        test -f "$VENVSCRIPT" \
         || die "no such script: $VENVSCRIPT"
        ln -sr "$SrcDir/dist" .
        test -n "$VENVCMD" \
         || VENVCMD=bash
        bash "$VENVSCRIPT" \
         || die "mkvenv script failed"
        #shellcheck disable=SC1090,SC1091
        . "$VENVDIR/bin/activate"
        "$VENVCMD"
    )
}

drive_shell() {
    #
    # Try opening interactive shell
    #
    (
        SUFFIX=""
        test -n "$VDK_TEST__PYLIB_MODNAME" && SUFFIX=":$VDK_TEST__PYLIB_MODNAME"
        CMD=${FnArgs[0]}
        export BASHUM_CTXSYM="vdk_test:shell$SUFFIX"
        "$CMD"
    )
}

validate_tf_dir() {
    test -x "$1/runtests"
}

drive_tfkit() {
    #
    # Try running tfkit
    #
    (
        EXPLICIT=$TF_DIR
        DEFAULT="$SrcDir/utils/tfkit"
        if test -n "$EXPLICIT"; then
            validate_tf_dir "$EXPLICIT" || {
                warn "drive_tfkit(): could not find TFKit runner: TF_DIR=$TF_DIR"
                return 3
            }
        else
            validate_tf_dir "$DEFAULT" || {
                warn "drive_tfkit(): skipping; no TFKit runner at default path: $DEFAULT"
                return 2
            }
        fi
        export TF_DIR=${EXPLICIT:-$DEFAULT}
        ln -sr "$SrcDir/tests" .
        eval "$TF_DIR/runtests ${FnArgs[*]}"
    )
}

drive_unittest() {
    #
    # Try running unittest
    #
    test -d "$SrcDir/tests" || {
        warn "drive_unittest(): skipping; no 'tests' dir"
        return 2
    }
    ln -sr "$SrcDir/tests" .
    touch tests/__init__.py
    eval "python3 -m unittest ${FnArgs[*]}"
}

drive_pytest() {
    #
    # Try running pytest
    #
    local code
    python3 -m pytest -V >/dev/null || {
        warn "drive_pytest(): skipping; could not initiate module"
        return 2
    }
    ln -sr "$SrcDir/tests" .
    python3 -m pytest --collect-only | grep -qxF 'collected 0 items' \
     && {
        warn "drive_pytest(): skipping; no tests to run"
        return 2
    }
    code="python3 -m pytest -vv ${FnArgs[*]}"
    warn "drive_pytest(): running code=$code"
    eval "$code"
}

drive_shellcheck() {
    #
    # Try running pytest
    #
    command -v shellcheck >/dev/null || {
        warn "drive_shellcheck(): skipping; shellcheck is not available"
        return 2
    }
    warn "drive_shellcheck(): ShellCheck version: $(shellcheck --version | grep '^version:' | cut -d' ' -f2)"
    grep -nxFe '#!/bin/bash' -r src \
      | grep :1: \
      | cut -d: -f1 \
      > files.lst
    warn "drive_shellcheck(): found files: $(wc -l <files.lst)"
    (
        ES=0
        while read -r FILE; do
            if eval "shellcheck ${FnArgs[*]} $FILE" >& errors; then
                warn "drive_shellcheck():   $FILE .. OK"
            else
                warn "drive_shellcheck():   $FILE .. FAIL"
                echo "FAIL"
                sed "s/^/drive_shellcheck(): .. errors:|/" errors >&2
                ES=1
            fi
        done <files.lst
        exit "$ES"
    )
}


sort_by_length() {
  while read -r finding; do
      echo "${#finding} $finding"
  done \
    | sort -n \
    | cut -d' ' -f2-
}

find_builddir_debstuff() {
    test -d "$PWD/.pybuild" || return 0
    find "$PWD/.pybuild/" -name "$VDK_TEST__PYLIB_MODNAME" -type d
}

find_builddir_rpmstuff() {
    test -d "$PWD/build" || return 0
    find "build" -name "$VDK_TEST__PYLIB_MODNAME" -type d
}

find_builddir_pylib() {
    { find_builddir_debstuff; find_builddir_rpmstuff; } \
      | grep -e "$VDK_TEST__PYLIB_MODNAME$" \
      | sort_by_length \
      | head -1
}

find_builddir() {
    case $Kit in
        pylib) find_builddir_pylib ;;
        *)     echo .
    esac
}

detect_kit() {
    test -n "$VDK_TEST__KIT" && echo "$VDK_TEST__KIT" && return
    local guess
    guess=$(list_suites | grep -E ":$Suite" | cut -d: -f1)
    echo "$guess"
}

require_kit_vars() {
    case $Kit in
        pylib)
            test -n "$VDK_TEST__PYLIB_MODNAME" \
             || die "unknown module name; specify VDK_TEST__PYLIB_MODNAME when running $Suite suite"
            ;;
    esac
}

wrap_suite() {
    local driver_fn="drive_$Suite"
    sandbox_fn "$driver_fn" "$@"; SES=$?
    case $SES in
        0)  warn "suite succeeded: $Suite"; exit 0 ;;
        1)  warn "suite failed: $Suite"; exit 1 ;;
        2)  warn "suite skipped: $Suite"; exit 0 ;;
        *)  warn "suite driver exited with invalid status: $driver_fn(): $SES is not 0-2"; exit 5 ;;
    esac
}

main() {
    local Self=${0##*/}
    local BuildDir=$VDK_TEST__BUILDDIR
    local SrcDir=$PWD
    local Kit
    local Suite=$1; shift
    test -n "$Suite" || usage "no SUITE?"
    Kit=$(detect_kit)
    test -n "$Kit" || usage "unknown SUITE: $Suite"
    require_kit_vars
    test -n "$BuildDir" || BuildDir=$(find_builddir)
    test -n "$BuildDir" || die "failed to find build directory (try setting VDK_TEST__BUILDDIR envvar)"
    debug "Kit=$Kit"
    debug "Suite=$Suite"
    debug "BuildDir=$BuildDir"
    debug "SrcDir=$SrcDir"
    debug "VDK_TEST__PYLIB_MODNAME=$VDK_TEST__PYLIB_MODNAME"
    debug "VDK_TEST__OPEN_SHELL=$VDK_TEST__OPEN_SHELL"
    debug "*=$*"
    case $Suite in
        bin_t)      wrap_suite "$@" ;;
        doctest)    wrap_suite "$@" ;;
        mypy)       wrap_suite "$@" ;;
        pdb)        wrap_suite "$@" ;;
        pytest)     wrap_suite "$@" ;;
        shell)      wrap_suite "$@" ;;
        shellcheck) wrap_suite "$@" ;;
        tfkit)      wrap_suite "$@" ;;
        unittest)   wrap_suite "$@" ;;
        venv)       wrap_suite "$@" ;;
        *) usage "unknown SUITE: $Suite" ;;
    esac
}

main "$@"
