#!/usr/bin/env bash
#
# exp-update-tester.sh - Test suite for exp-update
#
set -euo pipefail

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'

TESTS_PASSED=0
TESTS_FAILED=0
TEST_DIR=""
ORIGINAL_DIR="$PWD"

# Helper functions
log_test() {
  echo -e "${BLUE}[TEST]${NC} $1"
}

log_pass() {
  echo -e "${GREEN}[PASS]${NC} $1"
  ((TESTS_PASSED++))
}

log_fail() {
  echo -e "${RED}[FAIL]${NC} $1"
  ((TESTS_FAILED++))
}

log_error() {
  echo -e "${RED}[ERROR]${NC} $1"
}

# Setup test environment
setup_test_env() {
  local temp_dir
  temp_dir=$(mktemp -d -t dotfiles-test.XXXXXX)

  cd "$temp_dir" || { echo "Failed to cd to test directory"; return 1; }
  git init -q
  git config user.email "test@example.com"
  git config user.name "Test User"

  git commit --allow-empty -m "Initial commit" -q

  echo "$temp_dir"
}

# Cleanup test environment
cleanup_test_env() {
  if [[ -n "${TEST_DIR:-}" && -d "$TEST_DIR" ]]; then
    rm -rf "$TEST_DIR"
    TEST_DIR=""
  fi
}

# Run a test and handle cleanup
run_test() {
  local test_name="$1"
  local test_func="$2"

  # Cleanup before test
  cleanup_test_env

  # Run the test
  if $test_func; then
    echo "✓ $test_name passed"
    return 0
  else
    echo "✗ $test_name failed"
    return 1
  fi
}

# Test 2: Script has no syntax errors
test_syntax() {
  log_test "Checking script syntax"

  if bash -n setup; then
    log_pass "No syntax errors found"
    return 0
  else
    log_fail "Syntax errors detected"
    return 1
  fi
}

# Test 3: Help option works
test_help_option() {
  log_test "Testing --help option"

  if ./setup exp-update --help 2>&1 | grep -qiE "(Syntax|Options|exp-update)"; then
    log_pass "Help option works"
    return 0
  else
    log_fail "Help option failed"
    return 1
  fi
}

# Test 4: Test repository structure detection (dots/ prefix)
test_dots_structure() {
  log_test "Testing dots/ prefix structure detection"

  local test_repo
  test_repo=$(setup_test_env)
  TEST_DIR="$test_repo"

  cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }

  mkdir -p dots/.config/test-app
  mkdir -p dots/.local/bin
  echo "test config" > dots/.config/test-app/config.conf

  git add .
  git commit -m "Add dots structure" -q

  cat > test_detection.sh << EOF
#!/bin/bash
# Mock logging and style functions/variables
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; }
STY_CYAN="" STY_RST="" STY_YELLOW=""

# Set required environment variables for exp-update/0.run.sh
SKIP_NOTICE=true
REPO_ROOT="\$1"
CHECK_PACKAGES=false
DRY_RUN=false
FORCE_CHECK=false
VERBOSE=false
NON_INTERACTIVE=true
SOURCE_ONLY=true

source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh"
detected_dirs=\$(detect_repo_structure)
if [[ -n "\$detected_dirs" ]]; then
  read -ra MONITOR_DIRS <<<"\$detected_dirs"
fi
echo "Structure: \${MONITOR_DIRS[*]}"
EOF

  chmod +x test_detection.sh
  result=$(./test_detection.sh "$test_repo")

  if [[ "$result" == *"dots/.config"* ]]; then
    log_pass "Dots structure detected correctly"
    cd "$ORIGINAL_DIR"
    return 0
  else
    log_fail "Failed to detect dots structure. Got: $result"
    cd "$ORIGINAL_DIR"
    return 1
  fi
}

# Test 5: Test flat structure detection
test_flat_structure() {
  log_test "Testing flat structure detection"

  local test_repo
  test_repo=$(setup_test_env)
  TEST_DIR="$test_repo"

  cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }

  mkdir -p .config/test-app
  mkdir -p .local/bin
  echo "test config" > .config/test-app/config.conf

  git add .
  git commit -m "Add flat structure" -q

  cat > test_detection.sh << EOF
#!/bin/bash
# Mock logging and style functions/variables
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1"; exit 1; }

# Set required environment variables for exp-update
SKIP_NOTICE=true
REPO_ROOT="\$1"
CHECK_PACKAGES=false
DRY_RUN=false
FORCE_CHECK=false
VERBOSE=false
NON_INTERACTIVE=true
SOURCE_ONLY=true

source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh"
detected_dirs=\$(detect_repo_structure)
if [[ -n "\$detected_dirs" ]]; then
  read -ra MONITOR_DIRS <<<"\$detected_dirs"
fi
echo "Structure: \${MONITOR_DIRS[*]}"
EOF

  chmod +x test_detection.sh
  result=$(./test_detection.sh "$test_repo")

  if [[ "$result" == *".config"* ]] && [[ "$result" != *"dots/"* ]]; then
    log_pass "Flat structure detected correctly"
    cd "$ORIGINAL_DIR"
    return 0
  else
    log_fail "Failed to detect flat structure. Got: $result"
    cd "$ORIGINAL_DIR"
    return 1
  fi
}

# Test 6: Test dots prefix mapping to home directory
test_dots_mapping() {
  log_test "Testing dots/ prefix home directory mapping"
  
  dir_name="dots/.config"
  if [[ "$dir_name" == dots/* ]]; then
    home_subdir="${dir_name#dots/}"
    home_dir_path="${HOME}/${home_subdir}"
  else
    home_dir_path="${HOME}/${dir_name}"
  fi
  
  expected_path="${HOME}/.config"
  if [[ "$home_dir_path" == "$expected_path" ]]; then
    log_pass "Dots prefix mapping correct: $dir_name → $home_dir_path"
    return 0
  else
    log_fail "Dots prefix mapping failed: $dir_name → $home_dir_path (expected: $expected_path)"
    return 1
  fi
}

# Test 7: Test ignore file patterns - FIXED
test_ignore_patterns() {
  log_test "Testing ignore file pattern matching"
  
  local test_repo
  test_repo=$(setup_test_env)
  TEST_DIR="$test_repo"
  
  cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
  
  cat > .updateignore << 'EOF'
*.log
secrets/
.config/private*
*backup*
EOF
  
  mkdir -p .config
  mkdir -p secrets
  
  cat > test_ignore.sh << EOF
#!/bin/bash
# Suppress all output from sourced script
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1" >&2; exit 1; }

# FIXED: Set REPO_ROOT before sourcing exp-update
REPO_ROOT="\$1"
export REPO_ROOT

# Set other required environment variables
SKIP_NOTICE=true
CHECK_PACKAGES=false
DRY_RUN=false
FORCE_CHECK=false
VERBOSE=false
NON_INTERACTIVE=true

UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore"
HOME_UPDATE_IGNORE_FILE="/dev/null"

# Source the production script to use the real should_ignore function
# Redirect all unwanted output to stderr, then to /dev/null
source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null

test_cases=(
  "\$REPO_ROOT/app.log:0"
  "\$REPO_ROOT/secrets/key.txt:0" 
  "\$REPO_ROOT/.config/private-config:0"
  "\$REPO_ROOT/.config/backup-file:0"
  "\$REPO_ROOT/normal-config:1"
)

all_passed=true
for test_case in "\${test_cases[@]}"; do
  IFS=":" read -r file expected <<< "\$test_case"
  mkdir -p "\$(dirname "\$file")"
  touch "\$file"
  
  if should_ignore "\$file"; then
    result=0
  else
    result=1
  fi
  
  if [[ \$result -ne \$expected ]]; then
    echo "FAIL: \$file (expected: \$expected, got: \$result)"
    all_passed=false
  fi
done

if [[ "\$all_passed" == true ]]; then
  echo "PASS"
else
  echo "FAIL"
fi
EOF
  
  chmod +x test_ignore.sh
  result=$(./test_ignore.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)")
  
  if [[ "$result" == "PASS" ]]; then
    log_pass "All ignore pattern tests passed"
    cd "$ORIGINAL_DIR"
    return 0
  else
    log_fail "Some ignore pattern tests failed"
    echo "$result"
    cd "$ORIGINAL_DIR"
    return 1
  fi
}

# Test 8: Test safe_read security - FIXED
test_safe_read_security() {
  log_test "Testing safe_read uses secure assignment (printf -v)"

  local safe_read_function
  safe_read_function=$(awk '/^safe_read\(\) \{/,/^\}/' "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh")

  if [[ -z "$safe_read_function" ]]; then
    log_fail "Could not find safe_read function"
    return 1
  fi

  # FIXED: Remove comments before checking for eval
  # The function has a comment mentioning eval, which shouldn't count
  local function_without_comments
  function_without_comments=$(echo "$safe_read_function" | sed 's/#.*$//')
  
  local has_printf_v=false
  local has_eval=false
  
  if echo "$safe_read_function" | grep -F 'printf -v' > /dev/null; then
    has_printf_v=true
  fi
  
  # Check for eval in actual code (not comments)
  if echo "$function_without_comments" | grep -w 'eval' > /dev/null; then
    has_eval=true
  fi

  if [[ "$has_printf_v" == true ]] && [[ "$has_eval" == false ]]; then
    log_pass "safe_read uses secure printf -v assignment and no eval"
    return 0
  else
    log_fail "safe_read does not use secure assignment or contains eval (has_printf_v=$has_printf_v, has_eval=$has_eval)"
    echo "Function content:"
    echo "$safe_read_function"
    return 1
  fi
}

# Test 9: Test dry-run mode - FIXED
test_dry_run() {
  log_test "Testing dry-run mode"

  local test_repo
  test_repo=$(setup_test_env)
  TEST_DIR="$test_repo"

  cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }

  # Copy necessary files for setup to run
  cp "$ORIGINAL_DIR/setup" .
  cp -r "$ORIGINAL_DIR/sdata" .
  cp -r "$ORIGINAL_DIR/dots" .
  chmod +x setup

  # Create a test config file in repo
  mkdir -p dots/.config/test-app
  echo "test config" > dots/.config/test-app/config.conf

  git add .
  git commit -m "Add test config" -q

  # FIXED: Clean up any existing test files before running test
  rm -rf "${HOME}/.config/test-app" 2>/dev/null || true

  # Use non-interactive mode and check for DRY-RUN marker
  ./setup exp-update -n --skip-notice --non-interactive 2>&1 | tee dry_run_output.txt

  if grep -q "DRY-RUN" dry_run_output.txt; then
    log_pass "Dry-run mode detected in output"
  else
    log_fail "Dry-run mode not properly indicated"
    cd "$ORIGINAL_DIR"
    return 1
  fi

  # FIXED: Check if files were created (they shouldn't be in dry-run)
  if [[ -f "${HOME}/.config/test-app/config.conf" ]]; then
    log_fail "Files were created in home during dry-run"
    rm -rf "${HOME}/.config/test-app"
    cd "$ORIGINAL_DIR"
    return 1
  else
    log_pass "No files created in home during dry-run"
  fi

  cd "$ORIGINAL_DIR"
  return 0
}

# Test 10: Test command-line flags
test_flags() {
  log_test "Testing command-line flags"

  # Only test non-interactive flags
  local flags=("-h" "--help")
  local all_passed=true

  for flag in "${flags[@]}"; do
    if ./setup exp-update "$flag" 2>&1 | grep -qiE "(Syntax|Options|exp-update)"; then
      log_test "  ✓ $flag recognized"
    else
      log_test "  ✗ $flag not recognized"
      all_passed=false
    fi
  done

  if [[ "$all_passed" == true ]]; then
    log_pass "Help flags recognized correctly"
    return 0
  else
    log_fail "Some flags not recognized properly"
    return 1
  fi
}

# Test 11: Check for shellcheck
test_shellcheck() {
  log_test "Running shellcheck (if available)"
  
  if ! command -v shellcheck &>/dev/null; then
    log_test "shellcheck not found, skipping static analysis"
    return 0
  fi
  
  if shellcheck -e SC1090,SC1091,SC2148,SC2034,SC2155,SC2164 setup then
    log_pass "shellcheck passed"
    return 0
  else
    log_fail "shellcheck found issues"
    return 1
  fi
}

# Test 12: Test lock file mechanism
test_lock_file() {
  log_test "Testing lock file mechanism"
  
  local test_repo
  test_repo=$(setup_test_env)
  TEST_DIR="$test_repo"
  
  cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
  
  # Copy necessary files
  cp "$ORIGINAL_DIR/setup" .
  cp -r "$ORIGINAL_DIR/sdata" .
  mkdir -p dots/.config
  chmod +x setup
  
  git add .
  git commit -m "Add files" -q
  
  # Create a fake lock file
  echo "99999" > .update-lock
  
  # Try to run update - should fail due to lock
  if ./setup exp-update --skip-notice --non-interactive > lock_test_output.txt 2>&1; then
    if grep -q "stale lock" lock_test_output.txt; then
      log_pass "Lock file mechanism works (detected stale lock)"
      cd "$ORIGINAL_DIR"
      return 0
    fi
  fi
  log_fail "Lock file mechanism did not work as expected"
  cat lock_test_output.txt  # Show output for debugging
  cd "$ORIGINAL_DIR"
  return 1
}

# Test 13: Test ** substring ignore patterns - FIXED
test_substring_ignore_patterns() {
  log_test "Testing ** substring ignore pattern matching"

  local test_repo
  test_repo=$(setup_test_env)
  TEST_DIR="$test_repo"

  cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }

  cat > .updateignore << 'EOF'
**temp**
**backup**
**testfile**
EOF

  mkdir -p .config/test-app
  mkdir -p temp-backup-dir
  mkdir -p .local/share/test-temp
  mkdir -p .config/temp-file

  cat > test_substring_ignore.sh << EOF
#!/bin/bash
# Suppress all output from sourced script
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1" >&2; exit 1; }

# FIXED: Set REPO_ROOT before sourcing exp-update
REPO_ROOT="\$1"
export REPO_ROOT

# Set other required environment variables
SKIP_NOTICE=true
CHECK_PACKAGES=false
DRY_RUN=false
FORCE_CHECK=false
VERBOSE=false
NON_INTERACTIVE=true

UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore"
HOME_UPDATE_IGNORE_FILE="/dev/null"

# Source the production script to use the real should_ignore function
source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null

# Load patterns into cache
load_ignore_patterns

test_cases=(
  "\$REPO_ROOT/temp-backup-dir/file:0"
  "\$REPO_ROOT/.config/test-app/temp.conf:0"
  "\$REPO_ROOT/.local/share/test-temp/data:0"
  "\$REPO_ROOT/.config/temp-file/config:0"
  "\$REPO_ROOT/normal-config:1"
  "\$REPO_ROOT/.config/my-testfile.conf:0"
)

all_passed=true
for test_case in "\${test_cases[@]}"; do
  IFS=":" read -r file expected <<< "\$test_case"
  mkdir -p "\$(dirname "\$file")"
  touch "\$file"

  if should_ignore "\$file"; then
    result=0
  else
    result=1
  fi

  if [[ \$result -ne \$expected ]]; then
    echo "FAIL: \$file (expected: \$expected, got: \$result)"
    all_passed=false
  fi
done

if [[ "\$all_passed" == true ]]; then
  echo "PASS"
else
  echo "FAIL"
fi
EOF

  chmod +x test_substring_ignore.sh
  result=$(./test_substring_ignore.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)")

  if [[ "$result" == "PASS" ]]; then
    log_pass "** substring ignore patterns work correctly"
    cd "$ORIGINAL_DIR"
    return 0
  else
    log_fail "** substring ignore patterns failed"
    echo "$result"
    cd "$ORIGINAL_DIR"
    return 1
  fi
}

# Test 14: Test ensure_directory caching
test_directory_caching() {
  log_test "Testing directory creation caching"
  
  local test_repo
  test_repo=$(setup_test_env)
  TEST_DIR="$test_repo"
  
  cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
  
  cat > test_dir_cache.sh << EOF
#!/bin/bash
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
log_info() { :; }
log_warning() { :; }
log_error() { :; }
log_success() { :; }
log_header() { :; }
log_die() { echo "ERROR: \$1" >&2; exit 1; }

REPO_ROOT="\$1"
export REPO_ROOT

SKIP_NOTICE=true
CHECK_PACKAGES=false
DRY_RUN=false
FORCE_CHECK=false
VERBOSE=false
NON_INTERACTIVE=true
SOURCE_ONLY=true

source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null

test_dir="/tmp/test-ensure-dir-\$\$"

# First call should create
ensure_directory "\$test_dir"
result1=\$?

# Second call should use cache
ensure_directory "\$test_dir"
result2=\$?

# Check if CREATED_DIRS has the entry
if [[ -n "\${CREATED_DIRS[\$test_dir]:-}" ]] && [[ \$result1 -eq 0 ]] && [[ \$result2 -eq 0 ]]; then
  echo "PASS"
  rm -rf "\$test_dir"
else
  echo "FAIL"
fi
EOF
  
  chmod +x test_dir_cache.sh
  result=$(./test_dir_cache.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)")
  
  if [[ "$result" == "PASS" ]]; then
    log_pass "Directory creation caching works"
    cd "$ORIGINAL_DIR"
    return 0
  else
    log_fail "Directory creation caching failed"
    cd "$ORIGINAL_DIR"
    return 1
  fi
}

# Test 15: Test enhanced safe_read with non-interactive mode
test_safe_read_noninteractive() {
  log_test "Testing safe_read in non-interactive mode"
  
  cat > test_safe_read.sh << 'EOF'
#!/bin/bash
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
log_warning() { :; }
log_error() { :; }

# Simulate the enhanced safe_read function
safe_read() {
  local prompt="$1"
  local varname="$2"
  local default="${3:-}"
  local input_value=""

  # In non-interactive mode, use default immediately
  if [[ "$NON_INTERACTIVE" == true ]]; then
    if [[ -n "$default" ]]; then
      printf -v "$varname" '%s' "$default"
      return 0
    else
      log_error "Non-interactive mode requires default value for: $prompt"
      return 1
    fi
  fi
  
  # Regular read logic...
  printf -v "$varname" '%s' "$default"
  return 0
}

# Test 1: With default in non-interactive mode
NON_INTERACTIVE=true
if safe_read "Test: " result "default_value"; then
  if [[ "$result" == "default_value" ]]; then
    echo "TEST1: PASS"
  else
    echo "TEST1: FAIL - got '$result'"
  fi
else
  echo "TEST1: FAIL - returned error"
fi

# Test 2: Without default in non-interactive mode (should fail)
if safe_read "Test: " result ""; then
  echo "TEST2: FAIL - should have failed"
else
  echo "TEST2: PASS - correctly failed"
fi
EOF
  
  chmod +x test_safe_read.sh
  result=$(./test_safe_read.sh 2>&1)
  
  if echo "$result" | grep -q "TEST1: PASS" && echo "$result" | grep -q "TEST2: PASS"; then
    log_pass "Enhanced safe_read handles non-interactive mode correctly"
    rm -f test_safe_read.sh
    return 0
  else
    log_fail "Enhanced safe_read non-interactive mode failed"
    echo "$result"
    rm -f test_safe_read.sh
    return 1
  fi
}

# Main test runner
main() {
  echo -e "${BLUE}================================${NC}"
  echo -e "${BLUE}  Update.sh Test Suite (Enhanced)${NC}"
  echo -e "${BLUE}================================${NC}\n"

  if [[ ! -f "setup" ]]; then
    log_error "Please run this test from the directory containing setup"
    exit 1
  fi

  chmod +x setup 2>/dev/null || true

  # Define tests
  tests=(
    "test_syntax"
    "test_help_option"
    "test_dots_structure"
    "test_flat_structure"
    "test_dots_mapping"
    "test_ignore_patterns"
    "test_substring_ignore_patterns"
    "test_safe_read_security"
    "test_dry_run"
    "test_flags"
    "test_shellcheck"
    "test_lock_file"
    "test_directory_caching"
    "test_safe_read_noninteractive"
  )

  # Run tests
  for test in "${tests[@]}"; do
    if $test; then
      echo "✓ $test passed"
    else
      echo "✗ $test failed"
    fi
    echo
  done

  # Summary
  echo -e "${BLUE}================================${NC}"
  echo -e "${BLUE}  Test Summary${NC}"
  echo -e "${BLUE}================================${NC}"
  echo -e "${GREEN}Passed: $TESTS_PASSED${NC}"
  echo -e "${RED}Failed: $TESTS_FAILED${NC}"
  echo -e "${BLUE}Total:  ${#tests[@]}${NC}\n"

  if [[ $TESTS_FAILED -eq 0 ]]; then
    echo -e "${GREEN}All tests passed! 🎉${NC}\n"
    exit 0
  else
    echo -e "${RED}Some tests failed! ❌${NC}\n"
    exit 1
  fi
}

# Global cleanup
cleanup() {
  echo "Cleaning up test files..."
  cleanup_test_env
  rm -f test_detection.sh test_ignore.sh test_safe_read.sh test_fresh_clone.sh test_substring_ignore.sh dry_run_output.txt 2>/dev/null || true
  rm -f test_caching.sh test_dir_cache.sh 2>/dev/null || true
  rm -f lock_test_output.txt 2>/dev/null || true
  rm -rf "${HOME}/.config/test-app" 2>/dev/null || true
}

trap cleanup EXIT INT TERM

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  main "$@"
fi
