#!/usr/bin/env bash
# =============================================================================
# Pre-Commit Hook for Agent-Generated Code Validation
# =============================================================================
#
# PURPOSE:
#   This hook runs automatically before every commit. It catches common issues
#   in agent-generated code before they enter the repository. When a check
#   fails, the error message tells the agent exactly how to fix the problem.
#
# INSTALLATION:
#   Option 1 -- Direct install:
#     cp templates/hooks/pre-commit-example.sh .git/hooks/pre-commit
#     chmod +x .git/hooks/pre-commit
#
#   Option 2 -- Via git config (shared across team):
#     mkdir -p .githooks
#     cp templates/hooks/pre-commit-example.sh .githooks/pre-commit
#     chmod +x .githooks/pre-commit
#     git config core.hooksPath .githooks
#
# CUSTOMIZATION:
#   - Enable/disable checks by setting the variables below to "true"/"false"
#   - Add project-specific checks at the bottom of this file
#   - Modify file patterns to match your project structure
#
# =============================================================================

set -euo pipefail

# ---------------------------------------------------------------------------
# Configuration -- toggle checks on/off
# ---------------------------------------------------------------------------
CHECK_SECRETS="true"           # Scan for hardcoded secrets
CHECK_DEBUG_CODE="true"        # Scan for debug statements left behind
CHECK_LARGE_FILES="true"       # Reject files over size threshold
CHECK_TYPES="true"             # Run type checker [CUSTOMIZE: set to false if no type system]
CHECK_LINT="true"              # Run linter
CHECK_TESTS="true"             # Run tests for changed files
CHECK_FORBIDDEN_PATTERNS="true" # Check for project-specific forbidden patterns

LARGE_FILE_LIMIT_KB=500        # Max file size in KB

# [CUSTOMIZE] Set your commands
TYPECHECK_CMD="npx tsc --noEmit"           # e.g., "npx tsc --noEmit" or "go vet ./..."
LINT_CMD="npx eslint --quiet"              # e.g., "npx eslint" or "golangci-lint run"
TEST_CMD="npx vitest run --reporter=verbose" # e.g., "npx vitest run" or "go test"

# ---------------------------------------------------------------------------
# Colors for output
# ---------------------------------------------------------------------------
RED='\033[0;31m'
YELLOW='\033[0;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

errors_found=0

fail() {
  echo -e "${RED}BLOCKED${NC}: $1"
  echo ""
  echo -e "  ${YELLOW}FIX${NC}: $2"
  echo ""
  errors_found=$((errors_found + 1))
}

pass() {
  echo -e "  ${GREEN}PASS${NC}: $1"
}

echo "Running pre-commit checks..."
echo ""

# ---------------------------------------------------------------------------
# Get list of staged files
# ---------------------------------------------------------------------------
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)

if [ -z "$STAGED_FILES" ]; then
  echo "No staged files. Skipping checks."
  exit 0
fi

# ---------------------------------------------------------------------------
# Check 1: Secrets Detection
# ---------------------------------------------------------------------------
if [ "$CHECK_SECRETS" = "true" ]; then
  # [CUSTOMIZE] Add patterns specific to your project
  SECRET_PATTERNS=(
    'AKIA[0-9A-Z]{16}'                    # AWS Access Key
    'sk-[a-zA-Z0-9]{20,}'                 # OpenAI / Stripe secret key
    'ghp_[a-zA-Z0-9]{36}'                 # GitHub personal access token
    'password\s*=\s*["\x27][^"\x27]+'     # Hardcoded passwords
    'secret\s*=\s*["\x27][^"\x27]+'       # Hardcoded secrets
    'PRIVATE KEY-----'                     # Private keys
    'mongodb\+srv://[^@]+@'               # MongoDB connection strings with creds
    'postgres://[^@]+@'                    # PostgreSQL connection strings with creds
  )

  secrets_found=false
  for file in $STAGED_FILES; do
    [ ! -f "$file" ] && continue
    for pattern in "${SECRET_PATTERNS[@]}"; do
      if grep -qEn "$pattern" "$file" 2>/dev/null; then
        echo -e "  ${RED}Potential secret in${NC}: $file"
        grep -En "$pattern" "$file" 2>/dev/null | head -3
        secrets_found=true
      fi
    done
  done

  if [ "$secrets_found" = true ]; then
    fail "Potential secrets or credentials detected in staged files." \
         "Remove hardcoded secrets and use environment variables instead.
    1. Move secrets to .env (which is in .gitignore)
    2. Access via process.env.VARIABLE_NAME (or your config module)
    3. If this is a false positive, add the pattern to the allowlist in this hook.
    4. See: docs/security.md for secret management guidelines."
  else
    pass "No secrets detected"
  fi
fi

# ---------------------------------------------------------------------------
# Check 2: Debug Code Detection
# ---------------------------------------------------------------------------
if [ "$CHECK_DEBUG_CODE" = "true" ]; then
  # [CUSTOMIZE] Add debug patterns common in your codebase
  DEBUG_PATTERNS=(
    'console\.log'
    'debugger;'
    'binding\.pry'
    'fmt\.Println.*DEBUG'
    'print(.*# ?debug'
    'TODO.*REMOVE'
    'HACK'
    'FIXME.*before.*commit'
  )

  debug_found=false
  for file in $STAGED_FILES; do
    [ ! -f "$file" ] && continue
    # Skip test files -- debug statements are acceptable there
    # [CUSTOMIZE] Adjust the test file pattern for your project
    if echo "$file" | grep -qE '\.(test|spec)\.(ts|js|tsx|jsx)$'; then
      continue
    fi
    for pattern in "${DEBUG_PATTERNS[@]}"; do
      if grep -qEn "$pattern" "$file" 2>/dev/null; then
        echo -e "  ${RED}Debug code in${NC}: $file"
        grep -En "$pattern" "$file" 2>/dev/null | head -3
        debug_found=true
      fi
    done
  done

  if [ "$debug_found" = true ]; then
    fail "Debug code or console.log statements found in non-test files." \
         "Remove debug statements before committing.
    1. Replace console.log with your structured logger (e.g., logger.info())
    2. Remove debugger statements
    3. If the console.log is intentional, use console.info() or console.warn() instead."
  else
    pass "No debug code detected"
  fi
fi

# ---------------------------------------------------------------------------
# Check 3: Large File Detection
# ---------------------------------------------------------------------------
if [ "$CHECK_LARGE_FILES" = "true" ]; then
  large_files_found=false
  for file in $STAGED_FILES; do
    [ ! -f "$file" ] && continue
    file_size_kb=$(du -k "$file" | cut -f1)
    if [ "$file_size_kb" -gt "$LARGE_FILE_LIMIT_KB" ]; then
      echo -e "  ${RED}Large file${NC}: $file (${file_size_kb}KB > ${LARGE_FILE_LIMIT_KB}KB limit)"
      large_files_found=true
    fi
  done

  if [ "$large_files_found" = true ]; then
    fail "Files exceeding ${LARGE_FILE_LIMIT_KB}KB size limit detected." \
         "Large files should not be committed to the repository.
    1. If this is a binary/asset, use Git LFS: git lfs track '*.png'
    2. If this is generated code, add it to .gitignore
    3. If this is a data file, store it externally and reference by URL
    4. If the limit is too low for your project, increase LARGE_FILE_LIMIT_KB in this hook."
  else
    pass "No oversized files"
  fi
fi

# ---------------------------------------------------------------------------
# Check 4: Forbidden Patterns (Project-Specific)
# ---------------------------------------------------------------------------
if [ "$CHECK_FORBIDDEN_PATTERNS" = "true" ]; then
  # [CUSTOMIZE] Add your project-specific forbidden patterns
  # Format: "pattern|file_glob|explanation|fix"

  FORBIDDEN=(
    # Example: Prevent direct database imports in client components
    # "from.*database|src/app/**/*.tsx|Direct DB import in client component|Use a server action instead. See src/server/actions/ for examples."

    # Example: Prevent direct env access outside config module
    # "process\.env\.|src/(?!config).*\.ts|Direct process.env access|Use the config module: import { config } from '@/config'"
  )

  forbidden_found=false
  for rule in "${FORBIDDEN[@]}"; do
    IFS='|' read -r pattern glob explanation fix <<< "$rule"
    for file in $STAGED_FILES; do
      [ ! -f "$file" ] && continue
      if echo "$file" | grep -qE "$glob" && grep -qEn "$pattern" "$file" 2>/dev/null; then
        echo -e "  ${RED}Forbidden pattern${NC}: $explanation in $file"
        grep -En "$pattern" "$file" 2>/dev/null | head -3
        forbidden_found=true
      fi
    done
  done

  if [ "$forbidden_found" = true ]; then
    fail "Forbidden code patterns detected." \
         "See the specific violations above. Each has a project-specific fix."
  else
    pass "No forbidden patterns"
  fi
fi

# ---------------------------------------------------------------------------
# Check 5: Type Checking
# ---------------------------------------------------------------------------
if [ "$CHECK_TYPES" = "true" ]; then
  # [CUSTOMIZE] Replace with your type check command
  if $TYPECHECK_CMD > /dev/null 2>&1; then
    pass "Type check passed"
  else
    fail "Type checking failed." \
         "Run '${TYPECHECK_CMD}' to see all type errors and fix them.
    Common causes:
    1. Missing type annotations on new functions
    2. Incompatible types in function arguments
    3. Missing null checks (enable strictNullChecks)
    4. Importing types from the wrong module"
    # Show the actual errors
    $TYPECHECK_CMD 2>&1 | tail -20
  fi
fi

# ---------------------------------------------------------------------------
# Check 6: Linting
# ---------------------------------------------------------------------------
if [ "$CHECK_LINT" = "true" ]; then
  # Only lint staged files
  # [CUSTOMIZE] Adjust file extension filter for your project
  LINTABLE_FILES=$(echo "$STAGED_FILES" | grep -E '\.(ts|tsx|js|jsx)$' || true)
  if [ -n "$LINTABLE_FILES" ]; then
    if echo "$LINTABLE_FILES" | xargs $LINT_CMD > /dev/null 2>&1; then
      pass "Lint check passed"
    else
      fail "Lint errors found in staged files." \
           "Run '${LINT_CMD} --fix' to auto-fix what is possible, then manually fix the rest.
    If a lint rule seems wrong for your case, check if there is a project-specific
    override in .eslintrc or discuss with the team before disabling."
      echo "$LINTABLE_FILES" | xargs $LINT_CMD 2>&1 | tail -20
    fi
  else
    pass "No lintable files staged"
  fi
fi

# ---------------------------------------------------------------------------
# Check 7: Tests for Changed Files
# ---------------------------------------------------------------------------
if [ "$CHECK_TESTS" = "true" ]; then
  # [CUSTOMIZE] Adjust for your test framework and conventions
  test_files=""
  for file in $STAGED_FILES; do
    # Skip if the file itself is a test
    if echo "$file" | grep -qE '\.(test|spec)\.(ts|js|tsx|jsx)$'; then
      continue
    fi
    # Look for corresponding test file
    # [CUSTOMIZE] Adjust test file discovery for your project
    test_file=$(echo "$file" | sed 's/\.\(ts\|js\|tsx\|jsx\)$/.test.\1/')
    if [ -f "$test_file" ]; then
      test_files="$test_files $test_file"
    fi
  done

  if [ -n "$test_files" ]; then
    if $TEST_CMD $test_files > /dev/null 2>&1; then
      pass "Related tests pass"
    else
      fail "Tests failed for changed files." \
           "Run '${TEST_CMD}' to see failures.
    1. Fix the failing tests
    2. If you changed a function signature, update the tests to match
    3. If you added new behavior, add new test cases
    4. Run the full test suite before committing: ${TEST_CMD}"
      $TEST_CMD $test_files 2>&1 | tail -20
    fi
  else
    pass "No related test files found (consider adding tests)"
  fi
fi

# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
echo ""
if [ $errors_found -gt 0 ]; then
  echo -e "${RED}Commit blocked: $errors_found check(s) failed.${NC}"
  echo "Fix the issues above and try again."
  exit 1
else
  echo -e "${GREEN}All checks passed.${NC}"
  exit 0
fi
