#!/bin/bash

# security_scanner.sh
# Performs security scans on a target codebase using tools in our DevSecOps environment

set -euo pipefail
IFS=$'\n\t'

# Configuration
SCAN_DIR=${1:-"."}                                    # Default to current directory if no argument provided
REPORT_DIR="reports"                                  # Base directory for all reports
LOG_FILE="/var/log/security_scanner.log"             # Central log file
TIMESTAMP=${CI_JOB_ID:-$(date +%Y%m%d_%H%M%S)}       # Use CI job ID or timestamp
REPORT_NAME="security_scan_${TIMESTAMP}"             # Base name for report files

# Tool-specific configurations
BANDIT_ARGS="-r"                                     # Recursive scan
TRIVY_ARGS="--format json"                          # JSON output format

# Report file paths
SAST_REPORT="${REPORT_DIR}/${REPORT_NAME}_sast.txt"
DEPS_REPORT="${REPORT_DIR}/${REPORT_NAME}_deps.txt"
CONTAINER_REPORT="${REPORT_DIR}/${REPORT_NAME}_containers.json"
SUMMARY_REPORT="${REPORT_DIR}/security_scan_${CI_JOB_ID}_summary.md"

# Exit codes
readonly EXIT_SUCCESS=0
readonly EXIT_FAILURE=1
readonly EXIT_INVALID_ARGS=2

# Logging setup
setup_logging() {
    if [[ ! -f "$LOG_FILE" ]]; then
        touch "$LOG_FILE"
        chmod 644 "$LOG_FILE"
    fi
}

log() {
    local level=$1
    shift
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] [${level}] $*" | tee -a "$LOG_FILE"
}

# Error handler
error_handler() {
    local line_num=$1
    local error_code=$2
    log "ERROR" "Error occurred in script at line: ${line_num} (Exit code: ${error_code})"
}

trap 'error_handler ${LINENO} $?' ERR

# Validation functions
validate_environment() {
    local required_tools=("bandit" "trivy" "dependency-check")
    
    for tool in "${required_tools[@]}"; do
        if ! command -v "$tool" &> /dev/null; then
            log "ERROR" "Required tool not found: $tool"
            return $EXIT_FAILURE
        fi
    done
    
    if [[ ! -d "$REPORT_DIR" ]]; then
        mkdir -p "$REPORT_DIR"
        chmod 777 "$REPORT_DIR"
    fi
    
    return $EXIT_SUCCESS
}

validate_target() {
    if [[ ! -d "$SCAN_DIR" ]]; then
        log "ERROR" "Invalid target directory: $SCAN_DIR"
        return $EXIT_FAILURE
    fi
    
    if [[ ! -r "$SCAN_DIR" ]]; then
        log "ERROR" "Cannot read target directory: $SCAN_DIR"
        return $EXIT_FAILURE
    fi
    
    return $EXIT_SUCCESS
}

# Scanning functions
perform_sast_scan() {
    log "INFO" "Starting SAST scan with Bandit"
    
    if bandit $BANDIT_ARGS "$SCAN_DIR" -f txt -o "$SAST_REPORT" 2>&1; then
        log "INFO" "SAST scan completed successfully"
        return $EXIT_SUCCESS
    else
        log "ERROR" "SAST scan did not complete successfully"
        return $EXIT_SUCCESS  # Don't fail the build for findings
    fi
}

perform_dependency_scan() {
    log "INFO" "Starting dependency scan"
    
    if dependency-check --scan "$SCAN_DIR" --out "$DEPS_REPORT" -f ALL 2>&1; then
        log "INFO" "Dependency scan completed successfully"
        return $EXIT_SUCCESS
    else
        log "ERROR" "Dependency scan did not complete successfully"
        return $EXIT_SUCCESS  # Don't fail the build for findings
    fi
}

perform_container_scan() {
    log "INFO" "Starting container image scan"
    
    # Find all Dockerfiles in the target directory
    while IFS= read -r -d '' dockerfile; do
        local dir_name
        dir_name=$(dirname "$dockerfile")
        local image_name
        image_name=$(basename "$dir_name" | tr -dc '[:alnum:]-_' | tr '[:upper:]' '[:lower:]')
        
        # Use a safe default if the directory name is empty or just "."
        if [[ -z "$image_name" ]] || [[ "$image_name" == "." ]]; then
            image_name="app"
        fi
        
        # Debug output
        log "DEBUG" "Dockerfile location: $dockerfile"
        log "DEBUG" "Build context directory: $dir_name"
        log "DEBUG" "Contents of build context:"
        ls -la "$dir_name"
        
        log "INFO" "Building container from Dockerfile: $dockerfile"
        if docker build --no-cache -t "scan_target_${image_name}:latest" -f "$dockerfile" "$dir_name" 2>&1; then
            log "INFO" "Scanning container image: scan_target_${image_name}:latest"
            if ! trivy image $TRIVY_ARGS -o "$CONTAINER_REPORT" "scan_target_${image_name}:latest"; then
                log "WARNING" "Container vulnerabilities found"
                return $EXIT_SUCCESS  # Don't fail the build for findings
            fi
        else
            log "ERROR" "Failed to build container from $dockerfile"
            log "DEBUG" "Running docker build with more verbosity"
            docker build --no-cache -t "scan_target_${image_name}:latest" -f "$dockerfile" "$dir_name" --progress=plain
            return $EXIT_FAILURE
        fi
    done < <(find "$SCAN_DIR" -name "Dockerfile" -print0)
    
    return $EXIT_SUCCESS
}

# Results processing
generate_summary() {
    log "INFO" "Generating summary report"
    
    # Debug output
    log "DEBUG" "Creating summary at: $SUMMARY_REPORT"
    log "DEBUG" "Current directory: $(pwd)"
    log "DEBUG" "Report directory contents:"
    ls -la "$REPORT_DIR"
    
    {
        echo "# Security Scan Summary"
        echo "## Scan Information"
        echo "- Date: $(date)"
        echo "- Target: $SCAN_DIR"
        echo
        echo "## Findings Summary"
        echo "### SAST Scan"
        echo "\`\`\`"
        if [[ -f "$SAST_REPORT" ]]; then
            cat "$SAST_REPORT"
        else
            echo "No SAST scan results available"
        fi
        echo "\`\`\`"
        echo
        echo "### Dependency Scan"
        echo "\`\`\`"
        if [[ -f "$DEPS_REPORT" ]]; then
            cat "$DEPS_REPORT"
        else
            echo "No dependency scan results available"
        fi
        echo "\`\`\`"
        echo
        echo "### Container Scan"
        echo "\`\`\`"
        if [[ -f "$CONTAINER_REPORT" ]]; then
            cat "$CONTAINER_REPORT"
        else
            echo "No container scan results available"
        fi
        echo "\`\`\`"
    } > "$SUMMARY_REPORT"
    
    log "DEBUG" "Summary file created. Contents:"
    cat "$SUMMARY_REPORT"
    
    log "INFO" "Summary report generated: $SUMMARY_REPORT"
}

# Main execution
main() {
    setup_logging
    log "INFO" "Starting security scan of $SCAN_DIR"
    
    # Validate environment and target
    if ! validate_environment; then
        log "ERROR" "Environment validation failed"
        return $EXIT_FAILURE
    fi
    
    if ! validate_target; then
        log "ERROR" "Target validation failed"
        return $EXIT_FAILURE
    fi
    
    # Perform scans
    perform_sast_scan
    perform_dependency_scan
    perform_container_scan
    
    # Generate summary
    generate_summary
    
    log "INFO" "Security scan completed"
    return $EXIT_SUCCESS
}

main "$@"
