#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

test.suite.level()
{
  level=$1
  case $1 in
      ''|*[!0-9]*) 
        level=$3
        ;;
      *) echo test.suite level=$level ;;
  esac
  if [ -z "$level" ]; then
    level=$LOG_LEVEL
  fi
  if [ -z "$level" ]; then
    level=3
  fi
}

test.suite.level $*

echo -e "[96m test.suite starting: $0 <LOG_LEVEL=$level>[0m"
# if [ "$level" -gt "5" ]; then
#     set -x
# fi

test.suite.discover() # # discovers all tests
{
  test.suite.run.completion
}

test.suite.run.completion() # # returns list of available tests for completion
{
  ls $OOSH_DIR/test/* | sed 's/\/.*\/oosh\/test\/\(interactive.\)*test\.//' | grep "^$1"
}

test.suite.parameter.completion.test() {
  test.suite.run.completion
}

test.suite.parameter.completion.logLevel() {
  echo "1"
  echo "2"
  echo "3"
  echo "4"
  echo "5"
}

private.test.suite.category() # <file> # reads TEST_CATEGORY from a test file, defaults to extended
{
  local file="$1"
  local category
  category=$(grep -m1 '^TEST_CATEGORY=' "$file" 2>/dev/null | cut -d= -f2)
  if [ -z "$category" ]; then
    category="extended"
  fi
  create.result 0 "$category"
}

test.suite.save.results() # # saves test results to config file and prints summary
{
  source $OOSH_DIR/config
  config.save testresult TEST_SUITE

  # Print final summary
  test.suite.summary
}

test.suite.summary() # # prints test summary with pass/fail counts
{
  local total=${TEST_SUITE_EXPECT_COUNTER:-0}
  local passed=${TEST_SUITE_SUCCESS_COUNTER:-0}
  local failed=$((total - passed))

  echo ""
  echo -e "\e[1;37m════════════════════════════════════════════════════════════════════\e[0m"
  echo -e "\e[1;37m                         TEST SUMMARY                               \e[0m"
  echo -e "\e[1;37m════════════════════════════════════════════════════════════════════\e[0m"
  echo ""
  echo -e "  Test Cases:  \e[1;37m$TEST_SUITE_TEST_COUNTER\e[0m"
  echo -e "  Assertions:  \e[1;37m$total\e[0m"
  echo -e "  Passed:      \e[1;32m$passed\e[0m"
  if [ "$failed" -gt 0 ]; then
    echo -e "  Failed:      \e[1;31m$failed\e[0m"
  else
    echo -e "  Failed:      \e[1;32m0\e[0m"
  fi
  echo ""

  if [ "$passed" -eq "$total" ] && [ "$total" -gt 0 ]; then
    echo -e "  \e[1;32m✓ ALL TESTS PASSED\e[0m"
  elif [ "$total" -eq 0 ]; then
    echo -e "  \e[1;33m⚠ NO TESTS RUN\e[0m"
  else
    echo -e "  \e[1;31m✗ SOME TESTS FAILED\e[0m"
  fi
  echo ""
  echo -e "\e[1;37m════════════════════════════════════════════════════════════════════\e[0m"
}

test.suite.init() # # initializes test suite counters and environment
{
  source this
  source log
  log.init.colors


  if [ -z "$TEST_COUNTER" ]; then
    if [ -t 1 ] && [ -w "$(tty 2>/dev/null)" ]; then
      export LOG_DEVICE="$(tty)"
    elif [ -w /proc/self/fd/1 ]; then
      export LOG_DEVICE=/proc/self/fd/1
    else
      export LOG_DEVICE=/dev/null
    fi
    declare -i TEST_SUITE_TEST_COUNTER=0
    declare -i TEST_SUITE_EXPECT_COUNTER=0
    declare -i TEST_SUITE_SUCCESS_COUNTER=0
    export SUITE_RESULT="Suite Result: "
  fi

  # Initialize expected error tracking
  export TEST_EXPECTING_ERROR=0
  export TEST_EXPECTED_ERROR_CODE=0

  if (this.isSourced); then
    debug.log "test.suite sourced"
    if [ "$2" = "clear" ]; then
      clear
    fi
    export PS4='\e[90m  ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO}   - ${BASH_SOURCE[@]##*/} \e[0m'
    if [ "$LOG_LEVEL" -gt "5" ]; then
        set -x
    fi
  else
    #important.log "test.suite started"
    this.start "$@"
  fi
}

test.suite.run() # <test> <?logLevel:1> # runs a test from the test folder
{
  local command=$1
  shift
  local level=$1
  shift
  local clear=$1
  shift

  if [ -z "$level" ]; then
    level=1
  fi

  # Print test header
  echo ""
  echo -e "\e[1;36m╔════════════════════════════════════════════════════════════════════╗\e[0m"
  echo -e "\e[1;36m║  RUNNING TEST: test.$command\e[0m"
  echo -e "\e[1;36m╚════════════════════════════════════════════════════════════════════╝\e[0m"
  echo ""

  if [ "$level" -gt 0 ]; then
    important.log "starting test: $OOSH_DIR/test/test.$command -level:$level- -clear:$clear-"
    $OOSH_DIR/test/test.$command $level $clear
  else
    $OOSH_DIR/test/test.$command $level $clear >/dev/null
  fi

  if check file $CONFIG_PATH/testresult.env exists; then
    source $CONFIG_PATH/testresult.env
  fi

  # Print results
  echo ""
  echo -e "\e[1;37m────────────────────────────────────────────────────────────────────\e[0m"
  echo -e "  Completed: \e[1;37mtest.$command\e[0m"
  echo -e "  Results:   \e[1;32m$TEST_SUITE_SUCCESS_COUNTER\e[0m / $TEST_SUITE_EXPECT_COUNTER assertions passed"
  echo -e "\e[1;37m────────────────────────────────────────────────────────────────────\e[0m"

  if [ "$TEST_SUITE_SUCCESS_COUNTER" -eq "$TEST_SUITE_EXPECT_COUNTER" ]; then
    echo -e "\e[1;32m  ✓ All tests successful\e[0m"
    return 0
  else
    echo -e "\e[1;31m  ✗ Some tests failed\e[0m"
    return 1
  fi
}

private.test.suite.run.filtered() # <level> <clear> <?category> # runs tests matching category filter
{
  local level=$1
  local clear=$2
  local categoryFilter=$3

  # Initialize cumulative counters
  local -i CUMULATIVE_TEST_COUNTER=0
  local -i CUMULATIVE_EXPECT_COUNTER=0
  local -i CUMULATIVE_SUCCESS_COUNTER=0
  local -i FILE_COUNT=0

  # Count total files matching filter
  local -i TOTAL_FILES=0
  for file in $OOSH_DIR/test/test.*; do
    [ -d "$file" ] && continue
    if [ -n "$categoryFilter" ]; then
      private.test.suite.category "$file"
      [ "$RESULT" != "$categoryFilter" ] && continue
    fi
    ((TOTAL_FILES++))
  done

  # Track intentional failures from meta-test (test.test.suite)
  local -i META_TEST_FAILURES=0
  local META_TEST_RAN=0

  # Print suite header
  local headerLabel="ALL TESTS"
  if [ -n "$categoryFilter" ]; then
    headerLabel="${categoryFilter^^} TESTS"
  fi

  echo ""
  echo -e "\e[1;35m╔════════════════════════════════════════════════════════════════════╗\e[0m"
  echo -e "\e[1;35m║                    RUNNING $headerLabel\e[0m"
  echo -e "\e[1;35m║                    Log Level: $level                                       ║\e[0m"
  echo -e "\e[1;35m╚════════════════════════════════════════════════════════════════════╝\e[0m"

  for file in $OOSH_DIR/test/test.*
  do
    # Skip test.data directory
    if [ -d "$file" ]; then
      continue
    fi

    # Apply category filter
    if [ -n "$categoryFilter" ]; then
      private.test.suite.category "$file"
      [ "$RESULT" != "$categoryFilter" ] && continue
    fi

    ((FILE_COUNT++))
    local filename=$(basename "$file")

    # Print test file header
    echo ""
    echo -e "\e[1;36m╔════════════════════════════════════════════════════════════════════╗\e[0m"
    echo -e "\e[1;36m║  [$FILE_COUNT/$TOTAL_FILES] $filename\e[0m"
    echo -e "\e[1;36m╚════════════════════════════════════════════════════════════════════╝\e[0m"

    # Run the test file (subprocess — counters don't propagate back)
    if [ "$level" -gt 0 ]; then
      $file $level $clear
    else
      $file $level $clear >/dev/null
    fi

    # Load per-file results saved by test.suite.save.results in the subprocess
    local FILE_TESTS=0 FILE_EXPECTS=0 FILE_SUCCESS=0
    if check file $CONFIG_PATH/testresult.env exists; then
      source $CONFIG_PATH/testresult.env
      FILE_TESTS=${TEST_SUITE_TEST_COUNTER:-0}
      FILE_EXPECTS=${TEST_SUITE_EXPECT_COUNTER:-0}
      FILE_SUCCESS=${TEST_SUITE_SUCCESS_COUNTER:-0}
      # Reset counters so next subprocess starts fresh
      TEST_SUITE_TEST_COUNTER=0
      TEST_SUITE_EXPECT_COUNTER=0
      TEST_SUITE_SUCCESS_COUNTER=0
    fi
    local FILE_FAILED=$((FILE_EXPECTS - FILE_SUCCESS))

    # Track meta-test (test.test.suite) failures separately
    # This test has 1 INTENTIONAL failure to verify counter logic works
    if [ "$filename" = "test.test.suite" ]; then
      META_TEST_RAN=1
      META_TEST_FAILURES=$FILE_FAILED
    fi

    # Accumulate cumulative counters from per-file results
    ((CUMULATIVE_TEST_COUNTER += FILE_TESTS))
    ((CUMULATIVE_EXPECT_COUNTER += FILE_EXPECTS))
    ((CUMULATIVE_SUCCESS_COUNTER += FILE_SUCCESS))

    # Show per-file summary only if LOG_LEVEL > 1
    if [ "$level" -gt 1 ]; then
      echo ""
      echo -e "\e[1;37m────────────────────────────────────────────────────────────────────\e[0m"
      echo -e "  File: \e[1;37m$filename\e[0m"
      echo -e "  Results: \e[1;32m$FILE_SUCCESS\e[0m / $FILE_EXPECTS assertions"
      if [ "$FILE_SUCCESS" -eq "$FILE_EXPECTS" ] && [ "$FILE_EXPECTS" -gt 0 ]; then
        echo -e "  \e[1;32m✓ PASS\e[0m"
      elif [ "$FILE_EXPECTS" -eq 0 ]; then
        echo -e "  \e[1;33m⚠ NO ASSERTIONS\e[0m"
      elif [ "$filename" = "test.test.suite" ] && [ "$FILE_FAILED" -eq 1 ]; then
        echo -e "  \e[1;32m✓ PASS (1 intentional failure - meta-test working correctly)\e[0m"
      else
        echo -e "  \e[1;31m✗ FAIL ($FILE_FAILED failed)\e[0m"
      fi
      echo -e "\e[1;37m────────────────────────────────────────────────────────────────────\e[0m"
    fi
  done

  # Print final overall summary (always shown)
  local TOTAL_FAILED=$((CUMULATIVE_EXPECT_COUNTER - CUMULATIVE_SUCCESS_COUNTER))

  # Check if the ONLY failure is the intentional one from test.test.suite
  local ONLY_INTENTIONAL_FAILURE=0
  if [ "$META_TEST_RAN" -eq 1 ] && [ "$META_TEST_FAILURES" -eq 1 ] && [ "$TOTAL_FAILED" -eq 1 ]; then
    ONLY_INTENTIONAL_FAILURE=1
  fi

  echo ""
  echo -e "\e[1;35m╔════════════════════════════════════════════════════════════════════╗\e[0m"
  echo -e "\e[1;35m║                    FINAL ${headerLabel} SUMMARY\e[0m"
  echo -e "\e[1;35m╚════════════════════════════════════════════════════════════════════╝\e[0m"
  echo ""
  echo -e "  Test Files:  \e[1;37m$FILE_COUNT\e[0m"
  echo -e "  Test Cases:  \e[1;37m$CUMULATIVE_TEST_COUNTER\e[0m"
  echo -e "  Assertions:  \e[1;37m$CUMULATIVE_EXPECT_COUNTER\e[0m"
  echo -e "  Passed:      \e[1;32m$CUMULATIVE_SUCCESS_COUNTER\e[0m"
  if [ "$ONLY_INTENTIONAL_FAILURE" -eq 1 ]; then
    echo -e "  Failed:      \e[1;32m1 (intentional meta-test)\e[0m"
  elif [ "$TOTAL_FAILED" -gt 0 ]; then
    echo -e "  Failed:      \e[1;31m$TOTAL_FAILED\e[0m"
  else
    echo -e "  Failed:      \e[1;32m0\e[0m"
  fi
  echo ""

  # GitHub Actions Job Summary (only when running in GHA)
  if [ -n "$GITHUB_STEP_SUMMARY" ]; then
    local ghaWho="User"
    [ "$(id -u)" = "0" ] && ghaWho="Root"
    local ghaFailed="$TOTAL_FAILED"
    local ghaStatus="✅ ALL TESTS PASSED"
    if [ "$ONLY_INTENTIONAL_FAILURE" -eq 1 ]; then
      ghaStatus="✅ ALL TESTS PASSED (1 intentional meta-test)"
    elif [ "$TOTAL_FAILED" -gt 0 ]; then
      ghaStatus="❌ $TOTAL_FAILED TESTS FAILED"
    fi
    {
      echo "### ${ghaWho} Tests"
      echo ""
      echo "| Metric | Count |"
      echo "|--------|------:|"
      echo "| Test Files | $FILE_COUNT |"
      echo "| Test Cases | $CUMULATIVE_TEST_COUNTER |"
      echo "| Assertions | $CUMULATIVE_EXPECT_COUNTER |"
      echo "| Passed | $CUMULATIVE_SUCCESS_COUNTER |"
      echo "| Failed | $ghaFailed |"
      echo ""
      echo "> $ghaStatus"
      echo ""
    } >> "$GITHUB_STEP_SUMMARY"
  fi

  # Write fail count for workflow-level summary
  if [ -n "$GITHUB_OUTPUT" ]; then
    if [ "$ONLY_INTENTIONAL_FAILURE" -eq 1 ]; then
      echo "failed=0" >> "$GITHUB_OUTPUT"
    else
      echo "failed=$TOTAL_FAILED" >> "$GITHUB_OUTPUT"
    fi
  fi

  if [ "$CUMULATIVE_SUCCESS_COUNTER" -eq "$CUMULATIVE_EXPECT_COUNTER" ] && [ "$CUMULATIVE_EXPECT_COUNTER" -gt 0 ]; then
    echo -e "  \e[1;32m✓ ALL TESTS PASSED\e[0m"
    return 0
  elif [ "$CUMULATIVE_EXPECT_COUNTER" -eq 0 ]; then
    echo -e "  \e[1;33m⚠ NO TESTS RUN\e[0m"
    return 1
  elif [ "$ONLY_INTENTIONAL_FAILURE" -eq 1 ]; then
    echo -e "  \e[1;32m╔══════════════════════════════════════════════════════════════════╗\e[0m"
    echo -e "  \e[1;32m║  ✓ ALL TESTS PASSED                                              ║\e[0m"
    echo -e "  \e[1;32m║    (1 intentional failure in test.test.suite verifies counters)  ║\e[0m"
    echo -e "  \e[1;32m╚══════════════════════════════════════════════════════════════════╝\e[0m"
    return 0
  else
    echo -e "  \e[1;31m✗ SOME TESTS FAILED\e[0m"
    return 1
  fi
}

test.suite.all() # <?logLevel:1> # runs all tests from the test folder
{
  local level=$1
  if [ -z "$level" ]; then
    level=1
  fi
  shift
  local clear=$1
  shift

  private.test.suite.run.filtered "$level" "$clear" ""
}

test.suite.core() # <?logLevel:1> # runs core infrastructure tests
{
  local level=$1
  if [ -z "$level" ]; then
    level=1
  fi
  shift
  local clear=$1
  shift

  private.test.suite.run.filtered "$level" "$clear" "core"
}

test.suite.extended() # <?logLevel:1> # runs extended feature tests
{
  local level=$1
  if [ -z "$level" ]; then
    level=1
  fi
  shift
  local clear=$1
  shift

  private.test.suite.run.filtered "$level" "$clear" "extended"
}

test.case() # <logLevel:-> <testName> <testFunction> <testArguments> # runs a test case and captures result
{
  ((TEST_SUITE_TEST_COUNTER++))

  local level=$1
  shift
  export testName=$1
  shift
  local testFunction="$1"
  shift
  local testArguments="$@"

  if ! [ "$level" = "-" ]; then
    log.level $level
  fi
  if [ "$LOG_LEVEL" -gt "5" ]; then
      set -x
  fi

  # Print test case header
  echo ""
  echo -e "\e[1;37m┌──────────────────────────────────────────────────────────────────┐\e[0m"
  echo -e "\e[1;37m│ Test $TEST_SUITE_TEST_COUNTER: $testName\e[0m"
  echo -e "\e[1;37m└──────────────────────────────────────────────────────────────────┘\e[0m"

  if [ "$LOG_LEVEL" -gt "2" ]; then
    echo -e "\e[90m  → $testFunction $testArguments\e[0m"
  fi

  # Check if we're expecting an error
  if [ "$TEST_EXPECTING_ERROR" -eq 1 ]; then
    # Capture stderr and suppress error output for expected errors
    {
      $testFunction $testArguments
      RETURN_VALUE=$?
    } 2>/dev/null

    if [ "$RETURN_VALUE" -eq "$TEST_EXPECTED_ERROR_CODE" ]; then
      echo -e "\e[1;32m  ✓ EXPECTED ERROR (code $RETURN_VALUE) - OK\e[0m"
    else
      echo -e "\e[1;33m  ⚠ Expected error code $TEST_EXPECTED_ERROR_CODE but got $RETURN_VALUE\e[0m"
    fi

    # Reset expected error state
    TEST_EXPECTING_ERROR=0
    TEST_EXPECTED_ERROR_CODE=0
  else
    # Normal test execution - suppress stderr to hide internal errors
    # Errors will be shown in assertions
    if [ "$LOG_LEVEL" -gt "2" ]; then
      $testFunction $testArguments
      RETURN_VALUE=$?
    else
      {
        $testFunction $testArguments
        RETURN_VALUE=$?
      } 2>/dev/null
    fi

    # Don't call onError here - let the assertions handle pass/fail reporting
  fi

  if [ "$LOG_LEVEL" -gt "2" ]; then
    echo -e "\e[90m  ← RETURN: $RETURN_VALUE  Result: $RESULT\e[0m"
  fi
}

test.case.expect.error() # <errorCode> <testName> <testFunction> <testArguments> # runs test case expecting specific error
{
  local errorCode=$1
  shift

  # Set expected error state
  export TEST_EXPECTING_ERROR=1
  export TEST_EXPECTED_ERROR_CODE=$errorCode
  export EXPECTED_RETURN_VALUE=$errorCode

  # Run test case with remaining args (level is "-" to preserve log level)
  test.case "-" "$@"
}

expect() # <returnValue> <expectedResult> <?message> # asserts test result matches expected
{
  ((TEST_SUITE_EXPECT_COUNTER++))

  local -i EXPECT_RETURN_CODE=$1
  shift
  local expectedresult="$1"
  shift
  local message="$1"
  shift

  declare -i rv=$RETURN_VALUE
  #console.log "${BOLD_CYAN}starting on testcase: \#$TEST_COUNTER - $testcase${NORMAL}"

  if [ -z "$message" ]; then
    message=$testName
  fi

  if [ "$expectedresult" = "*" ]; then
    expectedresult=$RESULT
  fi

  if [ "$EXPECT_RETURN_CODE" = "*" ]; then
    EXPECT_RETURN_CODE=$RETURN_VALUE
  fi

  if [ "$EXPECT_RETURN_CODE" -eq $rv ] && [ "$RESULT" = "$expectedresult" ]; then
    ((TEST_SUITE_SUCCESS_COUNTER++))
    echo -e "\e[1;32m  ✓ PASS:\e[0m $message"
  else
    echo -e "\e[1;31m  ✗ FAIL:\e[0m $message"
    echo -e "\e[90m    Expected return: $EXPECT_RETURN_CODE, got: $RETURN_VALUE\e[0m"
    if [ "$expectedresult" != "$RESULT" ]; then
      echo -e "\e[90m    Expected result: $expectedresult\e[0m"
      echo -e "\e[90m    Got result:      $RESULT\e[0m"
    fi
  fi

  TEST_SUITE_RESULT="\e[1;32m$TEST_SUITE_SUCCESS_COUNTER\e[0m / $TEST_SUITE_EXPECT_COUNTER expects on $TEST_SUITE_TEST_COUNTER Tests"
}

expect.pass() # <?message> # simple pass assertion with optional message
{
  ((TEST_SUITE_EXPECT_COUNTER++))
  ((TEST_SUITE_SUCCESS_COUNTER++))

  local message="$1"
  if [ -z "$message" ]; then
    message="assertion passed"
  fi
  echo -e "\e[1;32m  ✓ PASS:\e[0m $message"
}

expect.fail() # <?message> # simple fail assertion with optional message
{
  ((TEST_SUITE_EXPECT_COUNTER++))

  local message="$1"
  if [ -z "$message" ]; then
    message="assertion failed"
  fi
  echo -e "\e[1;31m  ✗ FAIL:\e[0m $message"
}

test.suite.usage() # # displays usage information for test.suite
{
  local this=${0##*/}
  echo "You started
$0

  current log level: $LOG_LEVEL

  current file: $CONFIG
   CONFIG_PATH=$CONFIG_PATH
   CONFIG_FILE=$CONFIG_FILE


  Usage:
  $this: command   description and Parameter
      all       tests all tests in $OOSH_DIR/test
                $this all <log level>  <?clear>
      core      tests core infrastructure tests only
                $this core <log level>  <?clear>
      extended  tests extended feature tests only
                $this extended <log level>  <?clear>
      run       runs a single test: $OOSH_DIR/test/test.<command>
                $this run <command> <log level>  <?clear>

  Categories:
      Tests declare TEST_CATEGORY=core at the top of the file.
      Tests without a category marker default to extended.

  Examples
    $this run loop 5
    $this core 1
    $this extended 1 clear
    $this all 1 clear
  "

  #loop.list PATH print '---------------------: '
}

test.suite.init "$@"
