#!/usr/bin/env bash
# Comprehensive tests for the state tool
# Tests state machine creation, transitions, and PDCA cycle implementation

#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x
TEST_CATEGORY=core

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

info.log "starting: ${BASH_SOURCE[@]##*/} <LOG_LEVEL=$1>"
source test.suite
source state

log.level $level

# Test machine name for isolation
TEST_MACHINE="TEST_STATE_$$"
PDCA_MACHINE="PDCA_TEST_$$"

# Save the original current machine before any test operations
ORIG_STATE_MACHINE=""
if [ -f "$CONFIG_PATH/current.state.machine.env" ]; then
  source $CONFIG_PATH/current.state.machine.env
  ORIG_STATE_MACHINE="$machine"
fi

# Cleanup function - use state.machine.delete to properly clear ghost refs
cleanup_test_machines() {
  state.machine.exists "$TEST_MACHINE" 2>/dev/null && state.machine.delete "$TEST_MACHINE" 2>/dev/null
  state.machine.exists "$PDCA_MACHINE" 2>/dev/null && state.machine.delete "$PDCA_MACHINE" 2>/dev/null

  # Restore original machine if it still exists
  if [ -n "$ORIG_STATE_MACHINE" ] && \
     state.machine.exists "$ORIG_STATE_MACHINE" 2>/dev/null; then
    state of "$ORIG_STATE_MACHINE" >/dev/null 2>&1
  fi
}

# Cleanup before tests
cleanup_test_machines

# ============================================================================
# T1: Test state machine creation
# ============================================================================
test.case - "T1: Create state machine" \
  state machine.create $TEST_MACHINE
if state machine.exists $TEST_MACHINE; then
  create.result 0 "Machine created"
else
  create.result 1 "Machine not created"
fi
expect 0 "Machine created" "state.machine.create creates machine"

# ============================================================================
# T2: Test adding custom states
# ============================================================================
test.case - "T2: Add custom state 'step.one'" \
  state of $TEST_MACHINE add step.one silent
# Find the state we just added
if state find $TEST_MACHINE step.one id >/dev/null 2>&1; then
  create.result 0 "State added"
else
  create.result 1 "State not found"
fi
expect 0 "State added" "state.add adds custom state"

# ============================================================================
# T3: Test state.set - set state directly
# ============================================================================
test.case - "T3: Set state to 'setup' (state 2)" \
  state of $TEST_MACHINE set 2
source $CONFIG_PATH/current.state.machine.env
if [ "$state" = "2" ]; then
  create.result 0 "State set to 2"
else
  create.result 1 "State is $state, expected 2"
fi
expect 0 "State set to 2" "state.set changes current state"

# ============================================================================
# T4: Test state.find by name
# ============================================================================
test.case - "T4: Find state by name 'initialized'" \
  state find $TEST_MACHINE initialized id
FOUND_ID=$(state find $TEST_MACHINE initialized id 2>/dev/null)
if [ "$FOUND_ID" = "1" ]; then
  create.result 0 "Found at ID 1"
else
  create.result 1 "Found at ID $FOUND_ID, expected 1"
fi
expect 0 "Found at ID 1" "state.find returns correct ID"

# ============================================================================
# T5: Test state transitions (find by name returns correct value)
# ============================================================================
# Find the custom state 'step.one' we added in T2 by its name
test.case - "T5: Transition states (number values)" \
  state find $TEST_MACHINE step.one id
TRANSITION_RESULT=$(state find $TEST_MACHINE step.one id 2>/dev/null | head -1)
if [ -n "$TRANSITION_RESULT" ] && [[ "$TRANSITION_RESULT" =~ ^[0-9]+$ ]]; then
  create.result 0 "Transition detected"
else
  create.result 1 "No transition: $TRANSITION_RESULT"
fi
expect 0 "Transition detected" "Numeric states transition correctly"

# ============================================================================
# T6: Test machine deletion
# ============================================================================
test.case - "T6: Delete state machine" \
  state machine.delete $TEST_MACHINE
if ! state machine.exists $TEST_MACHINE 2>/dev/null; then
  create.result 0 "Machine deleted"
else
  create.result 1 "Machine still exists"
fi
expect 0 "Machine deleted" "state.machine.delete removes machine"

# ============================================================================
# PDCA State Machine Tests (T7-T10)
# ============================================================================

# PDCA test variables
export TEST_PDCA_ERRORS=0
export TEST_PDCA_ITERATION=0
export MAX_ITERATIONS=5

# PDCA private.check implementations
private.check.planning() {
  # Plan phase always succeeds, go to doing
  create.result 0 "Planning complete"
  return 0
}

private.check.doing() {
  # Do phase always succeeds, go to checking
  create.result 0 "Implementation complete"
  return 0
}

private.check.checking() {
  # Check phase: if errors exist, go to acting; else finished
  ((TEST_PDCA_ITERATION++))

  if [ "$TEST_PDCA_ITERATION" -ge "$MAX_ITERATIONS" ]; then
    create.result 0 "error.max.iterations"
    return 0
  fi

  if [ "$TEST_PDCA_ERRORS" -gt 0 ]; then
    create.result 0 "acting"  # Go to act phase
    return 0
  fi

  create.result 0 "finished"  # No errors, done!
  return 0
}

private.check.acting() {
  # Act phase: fix one error, then back to checking
  if [ "$TEST_PDCA_ERRORS" -gt 0 ]; then
    ((TEST_PDCA_ERRORS--))
  fi
  create.result 0 "checking"  # Always go back to check
  return 0
}

private.check.finished() {
  create.result 0 "PDCA cycle complete"
  return 0
}

private.check.error.max.iterations() {
  create.result 0 "Stopped at max iterations"
  return 0
}

# ============================================================================
# T7: PDCA happy path (no errors) - P→D→C→finished
# ============================================================================
cleanup_test_machines
TEST_PDCA_ERRORS=0
TEST_PDCA_ITERATION=0

test.case - "T7: PDCA happy path (no errors)" \
  state machine.create $PDCA_MACHINE

# Add PDCA states
state of $PDCA_MACHINE add planning silent
state of $PDCA_MACHINE add doing silent
state of $PDCA_MACHINE add checking silent
state of $PDCA_MACHINE add acting silent
state of $PDCA_MACHINE add finished silent
state of $PDCA_MACHINE add error.max.iterations silent

# Simulate P→D→C→finished
PDCA_PATH=""

# Planning
private.check.planning
PDCA_PATH="${PDCA_PATH}P"

# Doing
private.check.doing
PDCA_PATH="${PDCA_PATH}→D"

# Checking (no errors, should go to finished)
private.check.checking
PDCA_PATH="${PDCA_PATH}→C"

if [ "$RESULT" = "finished" ]; then
  PDCA_PATH="${PDCA_PATH}→finished"
  create.result 0 "P→D→C→finished"
else
  create.result 1 "Path: $PDCA_PATH, Result: $RESULT"
fi
expect 0 "P→D→C→finished" "PDCA with no errors goes directly to finished"

# ============================================================================
# T8: PDCA with 1 error - P→D→C→A→C→finished
# ============================================================================
TEST_PDCA_ERRORS=1
TEST_PDCA_ITERATION=0

test.case - "T8: PDCA with 1 error (C→A→C loop)" \
  echo "Starting PDCA with 1 error"

PDCA_PATH="P→D"

# Check finds error
private.check.checking
PDCA_PATH="${PDCA_PATH}→C"

if [ "$RESULT" = "acting" ]; then
  PDCA_PATH="${PDCA_PATH}→A"
  private.check.acting  # Fixes error, goes back to check

  private.check.checking  # Should find no errors now
  PDCA_PATH="${PDCA_PATH}→C"

  if [ "$RESULT" = "finished" ]; then
    PDCA_PATH="${PDCA_PATH}→finished"
    create.result 0 "$PDCA_PATH"
  else
    create.result 1 "Expected finished, got $RESULT"
  fi
else
  create.result 1 "Expected acting, got $RESULT"
fi
expect 0 "P→D→C→A→C→finished" "PDCA with 1 error: C→A→C→finished"

# ============================================================================
# T9: PDCA with 3 errors - P→D→C→A→C→A→C→A→C→finished
# ============================================================================
TEST_PDCA_ERRORS=3
TEST_PDCA_ITERATION=0

test.case - "T9: PDCA with 3 errors (multiple C→A loops)" \
  echo "Starting PDCA with 3 errors"

PDCA_PATH="P→D"
LOOP_COUNT=0

# Simulate the C→A loop
while true; do
  private.check.checking
  PDCA_PATH="${PDCA_PATH}→C"

  if [ "$RESULT" = "acting" ]; then
    private.check.acting
    PDCA_PATH="${PDCA_PATH}→A"
    LOOP_COUNT=$((LOOP_COUNT + 1))
  elif [ "$RESULT" = "finished" ]; then
    PDCA_PATH="${PDCA_PATH}→finished"
    break
  else
    break
  fi

  # Safety limit
  if [ "$LOOP_COUNT" -gt 10 ]; then
    break
  fi
done

if [ "$LOOP_COUNT" -eq 3 ] && echo "$PDCA_PATH" | grep -q "finished"; then
  create.result 0 "3 C→A loops completed"
else
  create.result 1 "Loops: $LOOP_COUNT, Path: $PDCA_PATH"
fi
expect 0 "3 C→A loops completed" "PDCA with 3 errors cycles 3 times"

# ============================================================================
# T10: PDCA max iterations guard
# ============================================================================
TEST_PDCA_ERRORS=100  # Many errors - should hit max iterations
TEST_PDCA_ITERATION=0

test.case - "T10: PDCA max iterations guard (prevents infinite loop)" \
  echo "Starting PDCA with 100 errors (should stop at $MAX_ITERATIONS)"

PDCA_PATH="P→D"
LOOP_COUNT=0
HIT_MAX=0

while true; do
  private.check.checking
  PDCA_PATH="${PDCA_PATH}→C"

  if [ "$RESULT" = "error.max.iterations" ]; then
    HIT_MAX=1
    PDCA_PATH="${PDCA_PATH}→MAX"
    break
  elif [ "$RESULT" = "acting" ]; then
    private.check.acting
    PDCA_PATH="${PDCA_PATH}→A"
    LOOP_COUNT=$((LOOP_COUNT + 1))
  elif [ "$RESULT" = "finished" ]; then
    break
  fi

  # Ultimate safety
  if [ "$LOOP_COUNT" -gt 20 ]; then
    break
  fi
done

if [ "$HIT_MAX" -eq 1 ]; then
  create.result 0 "Max iterations guard triggered"
else
  create.result 1 "Did not hit max: loops=$LOOP_COUNT"
fi
expect 0 "Max iterations guard triggered" "PDCA stops at max iterations"

# ============================================================================
# T11: Forward branching test — check function returning a numeric state ID
#      causes the machine to jump to that state (simulates what transition.check
#      does when private.check.* returns a number)
# ============================================================================
cleanup_test_machines

BRANCH_MACHINE="BRANCH_TEST_$$"

state machine.create $BRANCH_MACHINE state
state of $BRANCH_MACHINE add step.alpha silent     # [11]
state of $BRANCH_MACHINE add step.beta  silent     # [12]
state of $BRANCH_MACHINE add step.gamma silent     # [13]

# Simulate the branching that transition.check performs:
# 1. A check function returns a numeric RESULT (e.g., 13)
# 2. transition.check overrides stateFound with RESULT
# 3. state.set advances to that state
# Here we replicate steps 2-3 directly.
state of $BRANCH_MACHINE
state.set $BRANCH_MACHINE 11
source $CONFIG_PATH/current.state.machine.env

test.case - "T11: Forward branching (set state to 13 = step.gamma, skipping step.beta)" \
  state.set $BRANCH_MACHINE 13

source $CONFIG_PATH/current.state.machine.env
T11_STATE_NAME="${BRANCH_MACHINE}_STATES[${state}]"
T11_STATE_VALUE="${!T11_STATE_NAME}"

if [ "$state" -eq 13 ] && [ "$T11_STATE_VALUE" = "step.gamma" ]; then
  create.result 0 "Branched to [13]=step.gamma"
else
  create.result 1 "Expected [13]=step.gamma, got [$state]=$T11_STATE_VALUE"
fi
expect 0 "Branched to [13]=step.gamma" "Forward branching sets state correctly"

# Cleanup branch test
state.machine.exists "$BRANCH_MACHINE" 2>/dev/null && state.machine.delete "$BRANCH_MACHINE" 2>/dev/null

# ============================================================================
# T12: State machine advancement loop — advance through all states to [99]=finished
#      via state.set (simulates what transition.check does on each step)
# ============================================================================
LOOP_MACHINE="LOOP_TEST_$$"

state machine.create $LOOP_MACHINE state
state of $LOOP_MACHINE add phase.one   silent   # [11]
state of $LOOP_MACHINE add phase.two   silent   # [12]
state of $LOOP_MACHINE add phase.three silent   # [13]
state of $LOOP_MACHINE add 99          silent   # [14]=99 transition to [99]=finished

test.case - "T12: Advancement loop reaches [99]=finished" \
  echo "Starting advancement loop"

# Start at first custom state
state.set $LOOP_MACHINE 11
source $CONFIG_PATH/current.state.machine.env

# Advance through all states
T12_STEPS=0
while [ "$state" -lt 99 ] && [ "$T12_STEPS" -lt 10 ]; do
  T12_NEXT=$((state + 1))
  # Look up next state value — if it's a number, follow the transition
  T12_NEXT_NAME="${LOOP_MACHINE}_STATES[${T12_NEXT}]"
  T12_NEXT_VALUE="${!T12_NEXT_NAME}"
  if [[ "$T12_NEXT_VALUE" =~ ^[0-9]+$ ]]; then
    # Transition state — follow the jump (e.g., [14]=99 → jump to 99)
    state.set $LOOP_MACHINE "$T12_NEXT_VALUE"
  else
    state.set $LOOP_MACHINE "$T12_NEXT"
  fi
  source $CONFIG_PATH/current.state.machine.env
  ((T12_STEPS++))
done

if [ "$state" -ge 99 ]; then
  create.result 0 "Reached finished in $T12_STEPS steps"
else
  create.result 1 "Stuck at state $state after $T12_STEPS steps"
fi
expect 0 "Reached finished in $T12_STEPS steps" "Advancement loop reaches [99]=finished"

# Cleanup loop test
state.machine.exists "$LOOP_MACHINE" 2>/dev/null && state.machine.delete "$LOOP_MACHINE" 2>/dev/null

# ============================================================================
# T13: current.state.machine.env is written with LITERAL $CONFIG_PATH (not
#      pre-expanded) so the shared file is safe across users.
# ============================================================================
LITERAL_MACHINE="LITERAL_TEST_$$"

state machine.create $LITERAL_MACHINE state
state of $LITERAL_MACHINE add step.lit silent
state.set $LITERAL_MACHINE 11

test.case - "T13: current.state.machine.env contains literal \$CONFIG_PATH" \
  grep -E '^stateFile=\\\$CONFIG_PATH/stateMachines/' "$CONFIG_PATH/current.state.machine.env"

if grep -qE '^stateFile=\$CONFIG_PATH/stateMachines/' "$CONFIG_PATH/current.state.machine.env" \
   && grep -qE '^source \$CONFIG_PATH/stateMachines/' "$CONFIG_PATH/current.state.machine.env"; then
  create.result 0 "literal-CONFIG_PATH"
else
  create.result 1 "pre-expanded: $(grep -E '^(stateFile|source)' "$CONFIG_PATH/current.state.machine.env" | tr '\n' '|')"
fi
expect 0 "literal-CONFIG_PATH" "current.state.machine.env keeps \$CONFIG_PATH literal"

# ============================================================================
# T14: Sourcing current.state.machine.env under a different CONFIG_PATH
#      resolves $stateFile against that path (per-user resolution).
# ============================================================================
test.case - "T14: stateFile= resolves per-user under fake CONFIG_PATH" \
  echo "checking per-user resolution"

T14_RESULT=$(
  STATEFILE_LINE=$(grep '^stateFile=' "$CONFIG_PATH/current.state.machine.env")
  CONFIG_PATH=/tmp/fake-config-$$
  eval "$STATEFILE_LINE"
  echo "$stateFile"
)

if [[ "$T14_RESULT" == /tmp/fake-config-$$/stateMachines/* ]]; then
  create.result 0 "per-user-ok"
else
  create.result 1 "got: $T14_RESULT"
fi
expect 0 "per-user-ok" "stateFile resolves against the running user's CONFIG_PATH"

# Cleanup literal test
state.machine.exists "$LITERAL_MACHINE" 2>/dev/null && state.machine.delete "$LITERAL_MACHINE" 2>/dev/null

# ============================================================================
# Cleanup
# ============================================================================
cleanup_test_machines

# ============================================================================
# Completion function tests
# ============================================================================

test.case $level "state.parameter.completion.machine exists" \
  type -t state.parameter.completion.machine
if type -t state.parameter.completion.machine &>/dev/null; then
  expect.pass "state.parameter.completion.machine function exists"
else
  expect.fail "state.parameter.completion.machine should exist"
fi

test.case $level "state.parameter.completion.nameFilter exists" \
  type -t state.parameter.completion.nameFilter
if type -t state.parameter.completion.nameFilter &>/dev/null; then
  expect.pass "state.parameter.completion.nameFilter function exists"
else
  expect.fail "state.parameter.completion.nameFilter should exist"
fi

test.case $level "state.parameter.completion.listOption returns values" \
  state.parameter.completion.listOption
COMP_OUTPUT=$(state.parameter.completion.listOption 2>/dev/null)
if echo "$COMP_OUTPUT" | grep -q "all"; then
  expect.pass "listOption completion returns 'all'"
else
  expect.fail "listOption completion should return 'all'"
fi

test.case $level "state.parameter.completion.state exists" \
  type -t state.parameter.completion.state
if type -t state.parameter.completion.state &>/dev/null; then
  expect.pass "state.parameter.completion.state function exists"
else
  expect.fail "state.parameter.completion.state should exist"
fi

test.case $level "state.parameter.completion.method exists" \
  type -t state.parameter.completion.method
if type -t state.parameter.completion.method &>/dev/null; then
  expect.pass "state.parameter.completion.method function exists"
else
  expect.fail "state.parameter.completion.method should exist"
fi

test.case $level "state.parameter.completion.print returns 'silent'" \
  state.parameter.completion.print
COMP_OUTPUT=$(state.parameter.completion.print 2>/dev/null)
if echo "$COMP_OUTPUT" | grep -q "silent"; then
  expect.pass "print completion returns 'silent'"
else
  expect.fail "print completion should return 'silent'"
fi

test.case $level "state.parameter.completion.newStateName returns values" \
  state.parameter.completion.newStateName
COMP_OUTPUT=$(state.parameter.completion.newStateName 2>/dev/null)
if [ -n "$COMP_OUTPUT" ]; then
  expect.pass "newStateName completion returns values"
else
  expect.fail "newStateName completion should return values"
fi

test.case $level "state.parameter.completion.script exists" \
  type -t state.parameter.completion.script
if type -t state.parameter.completion.script &>/dev/null; then
  expect.pass "state.parameter.completion.script function exists"
else
  expect.fail "state.parameter.completion.script should exist"
fi

test.case $level "state.parameter.completion.allStates exists" \
  type -t state.parameter.completion.allStates
if type -t state.parameter.completion.allStates &>/dev/null; then
  expect.pass "state.parameter.completion.allStates function exists"
else
  expect.fail "state.parameter.completion.allStates should exist"
fi

# ============================================================================
# T-STATE-OF-SILENT / T-STATE-FIND-SILENT:
#   Regression coverage for the stdout leaks fixed in `state.of` (line 66) and
#   the numeric branch of `state.find` (line 578). A fresh isolated machine
#   is used because earlier sections of this file delete TEST_MACHINE.
# ============================================================================
QUIET_MACHINE="QUIET_STATE_$$"
state.machine.exists "$QUIET_MACHINE" 2>/dev/null && state.machine.delete "$QUIET_MACHINE" 2>/dev/null
state machine.create $QUIET_MACHINE >/dev/null 2>&1
state of $QUIET_MACHINE add quiet.step silent >/dev/null 2>&1
state of $QUIET_MACHINE set 1 >/dev/null 2>&1

test.case $level "T-STATE-OF-SILENT-1: state.of <m> emits no stdout" \
  echo "(capturing stdout of: state.of $QUIET_MACHINE)"
# Use dotted (sourced) form: $RESULT contract requires same-process call
# per this:414–426. Subshell capture proves no stdout side-effect.
_STATE_OF_STDOUT=$(state.of $QUIET_MACHINE 2>/dev/null)
if [ -z "$_STATE_OF_STDOUT" ]; then
  expect.pass "state.of (no-method) produced no stdout"
else
  expect.fail "state.of leaked stdout: '$_STATE_OF_STDOUT'"
fi

test.case $level "T-STATE-OF-SILENT-2: state.of sets numeric \$RESULT" \
  echo "(checking \$RESULT after: state.of $QUIET_MACHINE)"
state.of $QUIET_MACHINE >/dev/null 2>&1
if [[ "$RESULT" =~ ^[0-9]+$ ]]; then
  expect.pass "RESULT is numeric: $RESULT"
else
  expect.fail "RESULT not numeric after state.of: '$RESULT'"
fi

# state.find numeric trace must be silenced at LOG_LEVEL 1
# (was: echo "<name>: <id>"; now: silent.log gated at level >= 3)
_OLD_LOG_LEVEL=$LOG_LEVEL
log.level 1 >/dev/null 2>&1
test.case 1 "T-STATE-FIND-SILENT: state.find numeric form produces no stdout at LOG_LEVEL 1" \
  echo "(capturing stdout of: state find $QUIET_MACHINE 1)"
_STATE_FIND_STDOUT=$(state find $QUIET_MACHINE 1 2>/dev/null)
if [ -z "$_STATE_FIND_STDOUT" ]; then
  expect.pass "state.find numeric form silent at LOG_LEVEL 1"
else
  expect.fail "state.find leaked at LOG_LEVEL 1: '$_STATE_FIND_STDOUT'"
fi
log.level $_OLD_LOG_LEVEL >/dev/null 2>&1

state.machine.exists "$QUIET_MACHINE" 2>/dev/null && state.machine.delete "$QUIET_MACHINE" 2>/dev/null
unset _OLD_LOG_LEVEL _STATE_OF_STDOUT _STATE_FIND_STDOUT QUIET_MACHINE

test.suite.save.results
