#!/usr/bin/env bash
#clear
#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
else
  shift
fi
info.log "starting: ${BASH_SOURCE[@]##*/} <LOG_LEVEL=$level>"

source this
source test.suite

log.level $level

source oo
source $OOSH_DIR/ossh

_FIXTURE_HOST="TEST_FIXTURE.cert.update"
_FIXTURE_CFG_DIR="$OOSH_DIR/etc/ossh/hosts/$_FIXTURE_HOST"
_FIXTURE_CFG="$_FIXTURE_CFG_DIR/certificates.update.conf"
_FIXTURE_MACHINE="CERT_UPDATE_TEST_FIXTURE_cert_update"

# Save any developer machine state under the fixture name (defensive — should not exist)
_FIXTURE_MACHINE_BACKUP=""
if [ -f "$CONFIG_PATH/stateMachines/$_FIXTURE_MACHINE.states.env" ]; then
  _FIXTURE_MACHINE_BACKUP=$(cat "$CONFIG_PATH/stateMachines/$_FIXTURE_MACHINE.states.env")
fi

# ============================================================================
# T-CERT-1: public methods are defined
# ============================================================================
test.case $level "ossh.certificates.update function is defined" \
  type ossh.certificates.update
if type ossh.certificates.update >/dev/null 2>&1; then
  expect.pass "ossh.certificates.update defined"
else
  expect.fail "ossh.certificates.update should be defined"
fi

test.case $level "ossh.certificates.status function is defined" \
  type ossh.certificates.status
if type ossh.certificates.status >/dev/null 2>&1; then
  expect.pass "ossh.certificates.status defined"
else
  expect.fail "ossh.certificates.status should be defined"
fi

# ============================================================================
# T-CERT-2: completion delegates to sshConfigHost
# ============================================================================
test.case $level "ossh.certificates.update.completion.sshConfigHost is defined" \
  type ossh.certificates.update.completion.sshConfigHost
if type ossh.certificates.update.completion.sshConfigHost >/dev/null 2>&1; then
  expect.pass "completion fn defined"
else
  expect.fail "ossh.certificates.update.completion.sshConfigHost should be defined"
fi

# Doctrine guard: completion must NOT call create.result (per
# project_create_result_breaks_c2_completion). Inspect the body.
test.case $level "completion fn does not invoke create.result" \
  bash -c "type ossh.certificates.update.completion.sshConfigHost | grep -v 'create.result' >/dev/null"
if type ossh.certificates.update.completion.sshConfigHost 2>/dev/null | grep -q 'create.result'; then
  expect.fail "completion must not call create.result — breaks c2 completion (see project_create_result_breaks_c2_completion)"
else
  expect.pass "completion clean of create.result"
fi

# ============================================================================
# T-CERT-3: machine.name sanitizes dots/dashes to underscores
# ============================================================================
RESULT=""
private.certificates.update.machine.name "WODA.test"
test.case $level "machine.name dots-to-underscores" echo "$RESULT"
if [ "$RESULT" = "CERT_UPDATE_WODA_test" ]; then
  expect.pass "WODA.test -> CERT_UPDATE_WODA_test"
else
  expect.fail "expected CERT_UPDATE_WODA_test, got: $RESULT"
fi

RESULT=""
private.certificates.update.machine.name "edge-case.host"
test.case $level "machine.name handles dashes too" echo "$RESULT"
if [ "$RESULT" = "CERT_UPDATE_edge_case_host" ]; then
  expect.pass "edge-case.host -> CERT_UPDATE_edge_case_host"
else
  expect.fail "expected CERT_UPDATE_edge_case_host, got: $RESULT"
fi

# ============================================================================
# T-CERT-4: config loader rejects missing file
# ============================================================================
test.case $level "config.load fails when no per-host config exists" \
  private.certificates.update.config.load "no.such.host.ever"
if private.certificates.update.config.load "no.such.host.ever" 2>/dev/null; then
  expect.fail "should fail when config file does not exist"
else
  expect.pass "rejects missing config file"
fi

# ============================================================================
# T-CERT-5: config loader rejects file with missing required keys
# ============================================================================
mkdir -p "$_FIXTURE_CFG_DIR"
cat > "$_FIXTURE_CFG" <<'EOF'
domain=fixture.example
# missing: workspace, certScenario, appScenario, dataDir
EOF

test.case $level "config.load fails when required keys missing" \
  private.certificates.update.config.load "$_FIXTURE_HOST"
if private.certificates.update.config.load "$_FIXTURE_HOST" 2>/dev/null; then
  expect.fail "should fail when required keys missing"
else
  expect.pass "rejects partial config"
fi

# ============================================================================
# T-CERT-6: config loader round-trip with full keys, defaults applied
# ============================================================================
cat > "$_FIXTURE_CFG" <<'EOF'
domain=fixture.example
workspace=/tmp/fixture/workspace
certScenario=certbot
appScenario=structr
dataDir=WODA-current
EOF

CERT_UPDATE_HOST="" CERT_UPDATE_DOMAIN="" CERT_UPDATE_WORKSPACE=""
CERT_UPDATE_CERT_SCENARIO="" CERT_UPDATE_APP_SCENARIO="" CERT_UPDATE_DATA_DIR=""
CERT_UPDATE_BACKUP_BASE="" CERT_UPDATE_VERIFY_MIN_DAYS=""

private.certificates.update.config.load "$_FIXTURE_HOST"

test.case $level "config.load sets CERT_UPDATE_DOMAIN" echo "$CERT_UPDATE_DOMAIN"
if [ "$CERT_UPDATE_DOMAIN" = "fixture.example" ]; then
  expect.pass "CERT_UPDATE_DOMAIN populated"
else
  expect.fail "expected fixture.example, got: $CERT_UPDATE_DOMAIN"
fi

test.case $level "config.load applies default backupBase=/var/backups" echo "$CERT_UPDATE_BACKUP_BASE"
if [ "$CERT_UPDATE_BACKUP_BASE" = "/var/backups" ]; then
  expect.pass "default backupBase applied"
else
  expect.fail "expected /var/backups, got: $CERT_UPDATE_BACKUP_BASE"
fi

test.case $level "config.load applies default verifyMinDaysLeft=30" echo "$CERT_UPDATE_VERIFY_MIN_DAYS"
if [ "$CERT_UPDATE_VERIFY_MIN_DAYS" = "30" ]; then
  expect.pass "default verifyMinDaysLeft applied"
else
  expect.fail "expected 30, got: $CERT_UPDATE_VERIFY_MIN_DAYS"
fi

# ============================================================================
# T-CERT-7: state machine init registers all 9 working states
# ============================================================================
source state
state.machine.exists "$_FIXTURE_MACHINE" 2>/dev/null && state.machine.delete "$_FIXTURE_MACHINE" >/dev/null 2>&1
private.certificates.update.state.machine.init "$_FIXTURE_MACHINE" >/dev/null 2>&1
source "$CONFIG_PATH/stateMachines/$_FIXTURE_MACHINE.states.env" 2>/dev/null

_expectedStates=(
  certificates.update.stop.certbot
  certificates.update.stop.structr
  certificates.update.archive.workspace
  certificates.update.create.certbot
  certificates.update.create.structr
  certificates.update.rotate.empty
  certificates.update.restore.workspace
  certificates.update.start.structr
  certificates.update.verify.https
)

_allStatesPresent=true
_i=11
for _expectedName in "${_expectedStates[@]}"; do
  _slot="${_FIXTURE_MACHINE}_STATES[${_i}]"
  _actual="${!_slot}"
  if [ "$_actual" != "$_expectedName" ]; then
    expect.fail "state [$_i] expected $_expectedName, got: $_actual"
    _allStatesPresent=false
  fi
  ((_i++))
done

if [ "$_allStatesPresent" = true ]; then
  expect.pass "all 9 working states registered in order [11]..[19]"
fi

# ============================================================================
# T-CERT-8: terminator slot [20] = "99"
# ============================================================================
_slot="${_FIXTURE_MACHINE}_STATES[20]"
test.case $level "state [20] is the 99 terminator" echo "${!_slot}"
if [ "${!_slot}" = "99" ]; then
  expect.pass "terminator slot present"
else
  expect.fail "expected [20]=99, got: ${!_slot}"
fi

# ============================================================================
# T-CERT-9: all required private.check.certificates.update.* functions defined
# ============================================================================
_allChecksDefined=true
for _stateName in "${_expectedStates[@]}"; do
  if ! type "private.check.$_stateName" >/dev/null 2>&1; then
    expect.fail "private.check.$_stateName not defined"
    _allChecksDefined=false
  fi
done

if [ "$_allChecksDefined" = true ]; then
  expect.pass "all 9 private.check.certificates.update.* functions defined"
fi
unset _allChecksDefined _stateName _expectedStates _expectedName _slot _actual _i _allStatesPresent

# ============================================================================
# T-CERT-10: ssh.run honours dry-run (does not invoke real SSH)
# ============================================================================
CERT_UPDATE_DRYRUN=yes
test.case $level "ssh.run --dry-run returns 0 without invoking ssh" \
  private.certificates.update.ssh.run no.real.host "echo nope"
private.certificates.update.ssh.run no.real.host "echo nope" >/dev/null 2>&1
if [ $? -eq 0 ]; then
  expect.pass "ssh.run dry-run returned 0 (no network attempt)"
else
  expect.fail "ssh.run dry-run should return 0"
fi
CERT_UPDATE_DRYRUN=""

# ============================================================================
# T-CERT-11: stuck-state failure block uses plain echo (per status-command-output idiom)
# ============================================================================
test.case $level "stuck-state failure block uses plain echo" \
  bash -c "type ossh.certificates.update | grep 'Cannot update certificates' | grep -qE '^[[:space:]]*echo '"
if type ossh.certificates.update 2>/dev/null | grep 'Cannot update certificates' | grep -qE '^[[:space:]]*echo '; then
  expect.pass "stuck failure uses bare echo — survives every LOG_LEVEL / LOG_DEVICE"
else
  expect.fail "'Cannot update certificates' line must use bare echo (not log primitive) per feedback_status_command_output_idiom"
fi

# ============================================================================
# T-CERT-12: usage / argless invocation returns non-zero
# (expect.error 2 quiets OOSH's onError ERR-trap diagnostic for the
# intentional non-zero return; the assertion below remains real.)
# ============================================================================
expect.error 2
test.case $level "ossh.certificates.update with no args returns non-zero" \
  ossh.certificates.update
expect.error 2
ossh.certificates.update >/dev/null 2>&1
if [ $? -ne 0 ]; then
  expect.pass "rejects missing host argument"
else
  expect.fail "should fail when no host provided"
fi

# ============================================================================
# T-CERT-REAL-WODA-1: real-env check — WODA.test config file exists and parses
# (Per feedback_real_env_tests_when_fixtures_miss: surface diagnostic + recovery
# command via expect.pass/.fail directly.)
# ============================================================================
_wodaCfg="$OOSH_DIR/etc/ossh/hosts/WODA.test/certificates.update.conf"
if [ ! -f "$_wodaCfg" ]; then
  expect.fail "WODA.test per-host config missing: $_wodaCfg
    Recover: ensure the file exists with keys domain/workspace/certScenario/appScenario/dataDir"
else
  CERT_UPDATE_HOST="" CERT_UPDATE_DOMAIN="" CERT_UPDATE_WORKSPACE=""
  CERT_UPDATE_CERT_SCENARIO="" CERT_UPDATE_APP_SCENARIO="" CERT_UPDATE_DATA_DIR=""
  CERT_UPDATE_BACKUP_BASE="" CERT_UPDATE_VERIFY_MIN_DAYS=""

  if private.certificates.update.config.load "WODA.test" 2>/dev/null; then
    if [ "$CERT_UPDATE_DOMAIN" = "test.wo-da.de" ] \
       && [ "$CERT_UPDATE_CERT_SCENARIO" = "certbot" ] \
       && [ "$CERT_UPDATE_APP_SCENARIO" = "structr" ] \
       && [ "$CERT_UPDATE_DATA_DIR" = "WODA-current" ]; then
      expect.pass "WODA.test config loads with expected core values"
    else
      expect.fail "WODA.test config loaded but core values unexpected — domain=$CERT_UPDATE_DOMAIN certScenario=$CERT_UPDATE_CERT_SCENARIO appScenario=$CERT_UPDATE_APP_SCENARIO dataDir=$CERT_UPDATE_DATA_DIR
    Recover: review $_wodaCfg against plan ~/.claude/plans/i-have-the-conversation-happy-widget.md"
    fi
  else
    expect.fail "WODA.test config exists but failed to load (missing required keys?)
    Recover: cat $_wodaCfg and ensure all 5 required keys present (domain, workspace, certScenario, appScenario, dataDir)"
  fi
fi
unset _wodaCfg

# ============================================================================
# Cleanup
# ============================================================================
# Drop the fixture machine
state.machine.exists "$_FIXTURE_MACHINE" 2>/dev/null && state.machine.delete "$_FIXTURE_MACHINE" >/dev/null 2>&1
# If the current-machine pointer is the fixture, clear it
if grep -q "machine=$_FIXTURE_MACHINE" "$CONFIG_PATH/current.state.machine.env" 2>/dev/null; then
  rm -f "$CONFIG_PATH/current.state.machine.env" 2>/dev/null
fi
# Wipe persisted ossh.env for the fixture
rm -f "$CONFIG_PATH/stateMachines/$_FIXTURE_MACHINE.ossh.env" 2>/dev/null

# Restore any pre-existing fixture machine (should not exist but defensive)
if [ -n "$_FIXTURE_MACHINE_BACKUP" ]; then
  echo "$_FIXTURE_MACHINE_BACKUP" > "$CONFIG_PATH/stateMachines/$_FIXTURE_MACHINE.states.env"
fi

# Drop the fixture config dir
rm -rf "$_FIXTURE_CFG_DIR" 2>/dev/null

unset _FIXTURE_HOST _FIXTURE_CFG_DIR _FIXTURE_CFG _FIXTURE_MACHINE _FIXTURE_MACHINE_BACKUP
unset CERT_UPDATE_HOST CERT_UPDATE_DOMAIN CERT_UPDATE_WORKSPACE
unset CERT_UPDATE_CERT_SCENARIO CERT_UPDATE_APP_SCENARIO CERT_UPDATE_DATA_DIR
unset CERT_UPDATE_BACKUP_BASE CERT_UPDATE_VERIFY_MIN_DAYS
unset CERT_UPDATE_FORCE CERT_UPDATE_DRYRUN CERT_UPDATE_STAMP

test.suite.save.results
