#!/usr/bin/env bash

# Comprehensive static analysis using cppcheck and other quality tools

set -euo pipefail

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# Default values
PROFILE="normal"
OUTPUT_FORMAT="human"
OUTPUT_DIR=""
GIT_ONLY=false
PARALLEL=true
VERBOSE=false
FIX_FORMAT=false
BUILD_DIR=""
COMPILE_COMMANDS=""
EXCLUDE_DIRS=""
INCLUDE_DIRS=""
SUPPRESSIONS_FILE=""
BASELINE_FILE=""
GENERATE_BASELINE=false

# Script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

# Function to print colored output
print_color() {
    local color=$1
    shift
    echo -e "${color}$*${NC}"
}

# Function to print section headers
print_section() {
    print_color "$CYAN" "\n==== $* ===="
}

# Function to print progress
print_progress() {
    print_color "$BLUE" "→ $*"
}

# Function to print success
print_success() {
    print_color "$GREEN" "✓ $*"
}

# Function to print warning
print_warning() {
    print_color "$YELLOW" "⚠ $*"
}

# Function to print error
print_error() {
    print_color "$RED" "✗ $*"
}

# Usage information
usage() {
    cat << EOF
Usage: $(basename "$0") [OPTIONS]

Code Quality Analysis Script for YAMS C++ Project

OPTIONS:
    Quality Profiles:
        -p, --profile PROFILE    Quality profile: strict, normal, quick, ci (default: normal)
        
    Output Options:
        -f, --format FORMAT      Output format: human, xml, json, sarif (default: human)
        -o, --output DIR         Output directory for reports (default: stdout)
        -v, --verbose            Show detailed output and progress
        
    Source Selection:
        -g, --git                Only analyze files changed in git (staged and unstaged)
        -d, --build-dir DIR      Build directory for compile_commands.json
        
    Analysis Options:
        --fix-format             Automatically fix formatting issues found
        --suppressions FILE      Use custom suppressions file
        --baseline FILE          Compare against baseline results
        --generate-baseline      Generate baseline file for future comparisons
        
    Performance:
        -j, --parallel           Enable parallel analysis (default: true)
        -s, --serial             Force serial processing
        
    Advanced:
        --exclude DIRS           Additional directories to exclude (comma-separated)
        --include DIRS           Additional include directories (comma-separated)
        --compile-commands FILE  Explicit path to compile_commands.json
        
    Help:
        -h, --help               Show this help message

PROFILES:
    strict      All checks enabled, warnings as errors, strict coding standards
    normal      Standard checks with reasonable suppressions (default)
    quick       Essential checks only for rapid feedback
    ci          Optimized for continuous integration with machine-readable output

OUTPUT FORMATS:
    human       Human-readable colored terminal output (default)
    xml         XML format for IDE integration
    json        JSON format for tool integration
    sarif       SARIF format for GitHub integration

EXAMPLES:
    # Basic quality check
    $(basename "$0")
    
    # Strict analysis with XML output
    $(basename "$0") --profile strict --format xml --output reports/
    
    # Quick check on git changes only
    $(basename "$0") --profile quick --git
    
    # CI mode with baseline comparison
    $(basename "$0") --profile ci --format json --baseline quality-baseline.json
    
    # Generate new baseline
    $(basename "$0") --generate-baseline --output quality-baseline.json
    
    # Fix formatting issues automatically
    $(basename "$0") --fix-format --git

EOF
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        -p|--profile)
            PROFILE="$2"
            shift 2
            ;;
        -f|--format)
            OUTPUT_FORMAT="$2"
            shift 2
            ;;
        -o|--output)
            OUTPUT_DIR="$2"
            shift 2
            ;;
        -v|--verbose)
            VERBOSE=true
            shift
            ;;
        -g|--git)
            GIT_ONLY=true
            shift
            ;;
        -d|--build-dir)
            BUILD_DIR="$2"
            shift 2
            ;;
        --fix-format)
            FIX_FORMAT=true
            shift
            ;;
        --suppressions)
            SUPPRESSIONS_FILE="$2"
            shift 2
            ;;
        --baseline)
            BASELINE_FILE="$2"
            shift 2
            ;;
        --generate-baseline)
            GENERATE_BASELINE=true
            shift
            ;;
        -j|--parallel)
            PARALLEL=true
            shift
            ;;
        -s|--serial)
            PARALLEL=false
            shift
            ;;
        --exclude)
            EXCLUDE_DIRS="$2"
            shift 2
            ;;
        --include)
            INCLUDE_DIRS="$2"
            shift 2
            ;;
        --compile-commands)
            COMPILE_COMMANDS="$2"
            shift 2
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            print_error "Unknown option: $1"
            usage
            exit 1
            ;;
    esac
done

# Validate profile
case $PROFILE in
    strict|normal|quick|ci)
        ;;
    *)
        print_error "Invalid profile: $PROFILE"
        print_error "Valid profiles: strict, normal, quick, ci"
        exit 1
        ;;
esac

# Validate output format
case $OUTPUT_FORMAT in
    human|xml|json|sarif)
        ;;
    *)
        print_error "Invalid output format: $OUTPUT_FORMAT"
        print_error "Valid formats: human, xml, json, sarif"
        exit 1
        ;;
esac

# Check for required tools
check_tool() {
    local tool=$1
    local package=$2
    local description=$3
    
    if ! command -v "$tool" &> /dev/null; then
        print_error "$tool not found in PATH"
        print_error "$description"
        print_error "Install with: $package"
        return 1
    fi
    return 0
}

print_section "Checking Required Tools"

MISSING_TOOLS=false

if ! check_tool "cppcheck" "brew install cppcheck" "Static analysis tool"; then
    MISSING_TOOLS=true
fi

# Check for optional tools
if ! command -v "clang-format" &> /dev/null; then
    print_warning "clang-format not found (needed for --fix-format)"
    print_warning "Install with: brew install clang-format"
    if [[ "$FIX_FORMAT" == true ]]; then
        print_error "--fix-format requires clang-format"
        exit 1
    fi
fi

if [[ "$MISSING_TOOLS" == true ]]; then
    exit 1
fi

print_success "All required tools found"

# Change to project root
cd "$PROJECT_ROOT"

# Set up default paths
if [[ -z "$BUILD_DIR" ]]; then
    # Look for common build directories (prefer new yams-* presets)
    for candidate in "build/yams-release" "build/yams-debug" "build" "cmake-build-release" "cmake-build-debug"; do
        if [[ -d "$candidate" ]]; then
            BUILD_DIR="$candidate"
            break
        fi
    done
fi

if [[ -z "$COMPILE_COMMANDS" && -n "$BUILD_DIR" ]]; then
    if [[ -f "$BUILD_DIR/compile_commands.json" ]]; then
        COMPILE_COMMANDS="$BUILD_DIR/compile_commands.json"
    fi
fi

if [[ -z "$SUPPRESSIONS_FILE" ]]; then
    SUPPRESSIONS_FILE="$PROJECT_ROOT/.cppcheck-suppressions"
fi

# Create output directory if specified
if [[ -n "$OUTPUT_DIR" ]]; then
    mkdir -p "$OUTPUT_DIR"
fi

print_section "Configuration"
print_progress "Profile: $PROFILE"
print_progress "Output format: $OUTPUT_FORMAT"
[[ -n "$OUTPUT_DIR" ]] && print_progress "Output directory: $OUTPUT_DIR"
[[ -n "$BUILD_DIR" ]] && print_progress "Build directory: $BUILD_DIR"
[[ -n "$COMPILE_COMMANDS" ]] && print_progress "Compile commands: $COMPILE_COMMANDS"
[[ -f "$SUPPRESSIONS_FILE" ]] && print_progress "Suppressions file: $SUPPRESSIONS_FILE"
print_progress "Git only: $GIT_ONLY"
print_progress "Parallel: $PARALLEL"
print_progress "Fix format: $FIX_FORMAT"

# Get list of files to analyze
print_section "Collecting Source Files"

if [[ "$GIT_ONLY" == true ]]; then
    print_progress "Getting list of git-modified files..."
    
    # Get both staged and unstaged files
    FILES=$(git diff --name-only --diff-filter=ACMR HEAD 2>/dev/null || true)
    FILES+=$'\n'
    FILES+=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null || true)
    
    # Filter for C++ files and remove duplicates
    FILES=$(echo "$FILES" | grep -E '\.(cpp|h|hpp|cc|cxx|c)$' | sort -u || true)
    
    if [[ -z "$FILES" ]]; then
        print_success "No modified C++ files found"
        exit 0
    fi
    
    # Convert to array
    FILE_ARRAY=()
    while IFS= read -r line; do
        [[ -n "$line" ]] && FILE_ARRAY+=("$line")
    done <<< "$FILES"
else
    # Find all C++ source files, excluding build directories and third-party code
    print_progress "Finding all C++ source files..."
    
    FIND_EXCLUDES=(
        -not -path "./build/*"
        -not -path "./.cache/*"
        -not -path "./cmake-build-*/*"
        -not -path "./third_party/*"
        -not -path "./external/*"
        -not -path "./vendor/*"
        -not -path "./.git/*"
        -not -path "*/generated/*"
        -not -path "*/_deps/*"
        -not -path "*.pb.h"
        -not -path "*.pb.cc"
    )
    
    # Add custom excludes
    if [[ -n "$EXCLUDE_DIRS" ]]; then
        IFS=',' read -ra EXCLUDE_ARRAY <<< "$EXCLUDE_DIRS"
        for exclude in "${EXCLUDE_ARRAY[@]}"; do
            FIND_EXCLUDES+=(-not -path "./${exclude}/*")
        done
    fi
    
    FILE_ARRAY=()
    while IFS= read -r line; do
        FILE_ARRAY+=("$line")
    done < <(find . \
        -type f \
        \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" -o -name "*.cc" -o -name "*.cxx" -o -name "*.c" \) \
        "${FIND_EXCLUDES[@]}" \
        | sort)
fi

# Check if we found any files
if [[ ${#FILE_ARRAY[@]} -eq 0 ]]; then
    print_warning "No C++ files found to analyze"
    exit 0
fi

print_success "Found ${#FILE_ARRAY[@]} files to analyze"

# Fix formatting if requested
if [[ "$FIX_FORMAT" == true ]]; then
    print_section "Fixing Code Formatting"
    
    if [[ -x "$SCRIPT_DIR/format-code.sh" ]]; then
        if [[ "$GIT_ONLY" == true ]]; then
            "$SCRIPT_DIR/format-code.sh" --git
        else
            "$SCRIPT_DIR/format-code.sh"
        fi
    else
        print_warning "format-code.sh not found or not executable"
    fi
fi

# Configure cppcheck based on profile
print_section "Configuring Static Analysis"

CPPCHECK_ARGS=()

# Base configuration
CPPCHECK_ARGS+=(
    "--enable=warning,style,performance,portability,information"
    "--std=c++20"
    "--language=c++"
    "--inline-suppr"
    "--quiet"
)

# Add suppressions file if it exists
if [[ -f "$SUPPRESSIONS_FILE" ]]; then
    CPPCHECK_ARGS+=("--suppressions-list=$SUPPRESSIONS_FILE")
fi

# Add include directories
CPPCHECK_ARGS+=("-I" "include")
CPPCHECK_ARGS+=("-I" "src")
if [[ -n "$INCLUDE_DIRS" ]]; then
    IFS=',' read -ra INCLUDE_ARRAY <<< "$INCLUDE_DIRS"
    for include_dir in "${INCLUDE_ARRAY[@]}"; do
        CPPCHECK_ARGS+=("-I" "$include_dir")
    done
fi

# Add compile commands if available (but only if not using git-only mode)
USE_COMPILE_COMMANDS=false
if [[ -f "$COMPILE_COMMANDS" && "$GIT_ONLY" == false ]]; then
    CPPCHECK_ARGS+=("--project=$COMPILE_COMMANDS")
    USE_COMPILE_COMMANDS=true
fi

# Profile-specific configuration
case $PROFILE in
    strict)
        CPPCHECK_ARGS+=(
            "--enable=all"
            "--error-exitcode=1"
            "--check-config"
            "--check-library"
            "--inconclusive"
        )
        ;;
    normal)
        CPPCHECK_ARGS+=(
            "--enable=warning,style,performance,portability"
        )
        ;;
    quick)
        CPPCHECK_ARGS+=(
            "--enable=error,warning"
        )
        ;;
    ci)
        CPPCHECK_ARGS+=(
            "--enable=warning,style,performance,portability"
            "--error-exitcode=1"
        )
        ;;
esac

# Output format configuration
case $OUTPUT_FORMAT in
    xml)
        CPPCHECK_ARGS+=("--xml" "--xml-version=2")
        ;;
    json)
        # cppcheck doesn't have native JSON, we'll convert XML
        CPPCHECK_ARGS+=("--xml" "--xml-version=2")
        ;;
    sarif)
        # We'll need to convert XML to SARIF
        CPPCHECK_ARGS+=("--xml" "--xml-version=2")
        ;;
    human)
        CPPCHECK_ARGS+=("--template={file}:{line}:{column}: {severity}: {message} [{id}]")
        ;;
esac

# Add parallel processing
if [[ "$PARALLEL" == true ]]; then
    # Use number of CPU cores
    if command -v nproc &> /dev/null; then
        JOBS=$(nproc)
    elif [[ -f /proc/cpuinfo ]]; then
        JOBS=$(grep -c ^processor /proc/cpuinfo)
    else
        JOBS=4  # Default fallback
    fi
    CPPCHECK_ARGS+=("-j" "$JOBS")
fi

print_success "cppcheck configured with ${#CPPCHECK_ARGS[@]} arguments"

# Run static analysis
print_section "Running Static Analysis"

# Prepare output redirection
OUTPUT_FILE=""
if [[ -n "$OUTPUT_DIR" ]]; then
    case $OUTPUT_FORMAT in
        xml)
            OUTPUT_FILE="$OUTPUT_DIR/cppcheck-results.xml"
            ;;
        json)
            OUTPUT_FILE="$OUTPUT_DIR/cppcheck-results.json"
            ;;
        sarif)
            OUTPUT_FILE="$OUTPUT_DIR/cppcheck-results.sarif"
            ;;
        human)
            OUTPUT_FILE="$OUTPUT_DIR/cppcheck-results.txt"
            ;;
    esac
fi

# Create file list for cppcheck
TEMP_FILE_LIST=$(mktemp)
trap "rm -f $TEMP_FILE_LIST" EXIT

printf '%s\n' "${FILE_ARRAY[@]}" > "$TEMP_FILE_LIST"

# Run cppcheck
print_progress "Running cppcheck analysis..."

# Choose between project mode and file list mode
if [[ "$USE_COMPILE_COMMANDS" == true ]]; then
    # Use project mode - don't specify individual files
    if [[ "$VERBOSE" == true ]]; then
        print_progress "Command: cppcheck ${CPPCHECK_ARGS[*]} (using compile_commands.json)"
    fi
    
    CPPCHECK_EXIT_CODE=0
    if [[ -n "$OUTPUT_FILE" ]]; then
        # Output to file
        if [[ "$OUTPUT_FORMAT" == "xml" || "$OUTPUT_FORMAT" == "json" || "$OUTPUT_FORMAT" == "sarif" ]]; then
            # XML output goes to stderr
            if ! cppcheck "${CPPCHECK_ARGS[@]}" 2> "$OUTPUT_FILE.tmp"; then
                CPPCHECK_EXIT_CODE=$?
            fi
            mv "$OUTPUT_FILE.tmp" "$OUTPUT_FILE"
        else
            # Human output
            if ! cppcheck "${CPPCHECK_ARGS[@]}" > "$OUTPUT_FILE" 2>&1; then
                CPPCHECK_EXIT_CODE=$?
            fi
        fi
    else
        # Output to stdout/stderr
        if ! cppcheck "${CPPCHECK_ARGS[@]}"; then
            CPPCHECK_EXIT_CODE=$?
        fi
    fi
else
    # Use file list mode
    if [[ "$VERBOSE" == true ]]; then
        print_progress "Command: cppcheck ${CPPCHECK_ARGS[*]} --file-list=$TEMP_FILE_LIST"
    fi
    
    CPPCHECK_EXIT_CODE=0
    if [[ -n "$OUTPUT_FILE" ]]; then
        # Output to file
        if [[ "$OUTPUT_FORMAT" == "xml" || "$OUTPUT_FORMAT" == "json" || "$OUTPUT_FORMAT" == "sarif" ]]; then
            # XML output goes to stderr
            if ! cppcheck "${CPPCHECK_ARGS[@]}" --file-list="$TEMP_FILE_LIST" 2> "$OUTPUT_FILE.tmp"; then
                CPPCHECK_EXIT_CODE=$?
            fi
            mv "$OUTPUT_FILE.tmp" "$OUTPUT_FILE"
        else
            # Human output
            if ! cppcheck "${CPPCHECK_ARGS[@]}" --file-list="$TEMP_FILE_LIST" > "$OUTPUT_FILE" 2>&1; then
                CPPCHECK_EXIT_CODE=$?
            fi
        fi
    else
        # Output to stdout/stderr
        if ! cppcheck "${CPPCHECK_ARGS[@]}" --file-list="$TEMP_FILE_LIST"; then
            CPPCHECK_EXIT_CODE=$?
        fi
    fi
fi

# Post-process output if needed
if [[ -n "$OUTPUT_FILE" && -f "$OUTPUT_FILE" ]]; then
    case $OUTPUT_FORMAT in
        json)
            # Convert XML to JSON (basic conversion)
            print_progress "Converting XML output to JSON..."
            # This is a placeholder - you might want to use a proper XML to JSON converter
            print_warning "JSON conversion not yet implemented, XML file saved as $OUTPUT_FILE"
            ;;
        sarif)
            # Convert XML to SARIF
            print_progress "Converting XML output to SARIF..."
            # This is a placeholder - you might want to use a proper XML to SARIF converter
            print_warning "SARIF conversion not yet implemented, XML file saved as $OUTPUT_FILE"
            ;;
    esac
fi

# Handle baseline comparison
if [[ -n "$BASELINE_FILE" && -f "$BASELINE_FILE" ]]; then
    print_section "Baseline Comparison"
    print_progress "Comparing results against baseline: $BASELINE_FILE"
    # This would need to be implemented based on your needs
    print_warning "Baseline comparison not yet implemented"
fi

# Generate baseline if requested
if [[ "$GENERATE_BASELINE" == true ]]; then
    print_section "Generating Baseline"
    BASELINE_OUTPUT="$OUTPUT_DIR/quality-baseline.xml"
    if [[ -z "$OUTPUT_DIR" ]]; then
        BASELINE_OUTPUT="quality-baseline.xml"
    fi
    print_progress "Generating baseline file: $BASELINE_OUTPUT"
    # Copy current results as baseline
    if [[ -n "$OUTPUT_FILE" && -f "$OUTPUT_FILE" ]]; then
        cp "$OUTPUT_FILE" "$BASELINE_OUTPUT"
        print_success "Baseline generated: $BASELINE_OUTPUT"
    fi
fi

# Summary
print_section "Analysis Summary"

if [[ -n "$OUTPUT_FILE" && -f "$OUTPUT_FILE" ]]; then
    # Count issues in output file
    case $OUTPUT_FORMAT in
        xml)
            ERROR_COUNT=$(grep -c '<error ' "$OUTPUT_FILE" 2>/dev/null || echo "0")
            ;;
        human)
            ERROR_COUNT=$(wc -l < "$OUTPUT_FILE" 2>/dev/null || echo "0")
            ;;
        *)
            ERROR_COUNT="unknown"
            ;;
    esac
    
    print_progress "Results saved to: $OUTPUT_FILE"
    if [[ "$ERROR_COUNT" != "unknown" ]]; then
        print_progress "Issues found: $ERROR_COUNT"
    fi
fi

print_progress "Files analyzed: ${#FILE_ARRAY[@]}"
print_progress "Profile: $PROFILE"
print_progress "Format: $OUTPUT_FORMAT"

# Final exit code and summary
if [[ $CPPCHECK_EXIT_CODE -eq 0 ]]; then
    print_success "Code quality analysis completed successfully"
    print_success "No critical issues found"
else
    print_error "Code quality analysis found issues (exit code: $CPPCHECK_EXIT_CODE)"
    if [[ "$PROFILE" == "strict" || "$PROFILE" == "ci" ]]; then
        print_error "Critical issues detected in $PROFILE mode"
    else
        print_warning "Issues found, but not treating as errors in $PROFILE mode"
        CPPCHECK_EXIT_CODE=0  # Don't fail in normal/quick mode
    fi
fi

exit $CPPCHECK_EXIT_CODE