#!/usr/bin/env bash

# Kubernetes Manifest Validator
# Validates manifests using kubectl and optional tools (kubeconform, kubeval)

set -euo pipefail

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

# Configuration
KUBE_VERSION="${KUBE_VERSION:-1.29.0}"
STRICT_MODE="${STRICT_MODE:-false}"

# Counters
TOTAL_FILES=0
VALID_FILES=0
INVALID_FILES=0
WARNINGS=0

# Helper functions
print_header() {
    echo -e "\n${BLUE}========================================${NC}"
    echo -e "${BLUE}$1${NC}"
    echo -e "${BLUE}========================================${NC}\n"
}

print_success() {
    echo -e "${GREEN}✓ $1${NC}"
}

print_error() {
    echo -e "${RED}✗ $1${NC}"
}

print_warning() {
    echo -e "${YELLOW}⚠ $1${NC}"
}

print_info() {
    echo -e "${BLUE}ℹ $1${NC}"
}

# Check if command exists
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# Validate with kubectl
validate_kubectl() {
    local file="$1"
    local result=0

    echo "  Validating with kubectl..."

    if kubectl apply --dry-run=client -f "$file" >/dev/null 2>&1; then
        print_success "kubectl dry-run passed"
    else
        print_error "kubectl dry-run failed"
        kubectl apply --dry-run=client -f "$file" 2>&1 | sed 's/^/    /'
        result=1
    fi

    return $result
}

# Validate with kubeconform
validate_kubeconform() {
    local file="$1"
    local result=0

    if ! command_exists kubeconform; then
        print_warning "kubeconform not installed (optional)"
        return 0
    fi

    echo "  Validating with kubeconform..."

    local output
    if output=$(kubeconform \
        -kubernetes-version "$KUBE_VERSION" \
        -strict \
        -summary \
        "$file" 2>&1); then
        print_success "kubeconform validation passed"
    else
        print_error "kubeconform validation failed"
        echo "$output" | sed 's/^/    /'
        result=1
    fi

    return $result
}

# Validate with kubeval
validate_kubeval() {
    local file="$1"
    local result=0

    if ! command_exists kubeval; then
        return 0  # Skip if not installed
    fi

    echo "  Validating with kubeval..."

    local output
    if output=$(kubeval \
        --kubernetes-version "$KUBE_VERSION" \
        --strict \
        "$file" 2>&1); then
        print_success "kubeval validation passed"
    else
        print_error "kubeval validation failed"
        echo "$output" | sed 's/^/    /'
        result=1
    fi

    return $result
}

# Check for common security issues
check_security() {
    local file="$1"
    local issues=0

    echo "  Checking security best practices..."

    # Check for runAsNonRoot
    if ! grep -q "runAsNonRoot.*true" "$file"; then
        print_warning "Security: runAsNonRoot not set to true"
        ((issues++))
    fi

    # Check for readOnlyRootFilesystem
    if ! grep -q "readOnlyRootFilesystem.*true" "$file"; then
        print_warning "Security: readOnlyRootFilesystem not set to true"
        ((issues++))
    fi

    # Check for allowPrivilegeEscalation
    if ! grep -q "allowPrivilegeEscalation.*false" "$file"; then
        print_warning "Security: allowPrivilegeEscalation not set to false"
        ((issues++))
    fi

    # Check for capabilities drop
    if ! grep -q "drop:" "$file" || ! grep -q "- ALL" "$file"; then
        print_warning "Security: capabilities not dropped (drop: [ALL])"
        ((issues++))
    fi

    # Check for :latest tag
    if grep -q "image:.*:latest" "$file"; then
        print_warning "Security: using :latest image tag (use specific versions)"
        ((issues++))
    fi

    # Check for resource limits
    if grep -q "kind: Deployment\|kind: StatefulSet" "$file"; then
        if ! grep -q "resources:" "$file"; then
            print_warning "Best practice: resource limits/requests not defined"
            ((issues++))
        fi
    fi

    # Check for health probes
    if grep -q "kind: Deployment\|kind: StatefulSet" "$file"; then
        if ! grep -q "livenessProbe:" "$file"; then
            print_warning "Best practice: livenessProbe not defined"
            ((issues++))
        fi
        if ! grep -q "readinessProbe:" "$file"; then
            print_warning "Best practice: readinessProbe not defined"
            ((issues++))
        fi
    fi

    if [ $issues -eq 0 ]; then
        print_success "Security checks passed"
    else
        WARNINGS=$((WARNINGS + issues))
    fi

    return 0  # Don't fail on warnings
}

# Check YAML syntax
check_yaml_syntax() {
    local file="$1"
    local result=0

    echo "  Checking YAML syntax..."

    if command_exists yamllint; then
        if yamllint -d relaxed "$file" >/dev/null 2>&1; then
            print_success "YAML syntax valid"
        else
            print_error "YAML syntax errors found"
            yamllint -d relaxed "$file" 2>&1 | sed 's/^/    /'
            result=1
        fi
    else
        # Basic YAML check with kubectl
        if kubectl apply --dry-run=client -f "$file" >/dev/null 2>&1; then
            print_success "YAML syntax valid"
        else
            print_error "YAML syntax errors found"
            result=1
        fi
    fi

    return $result
}

# Validate a single file
validate_file() {
    local file="$1"
    local file_valid=true

    print_info "Validating: $file"

    # Check if file exists
    if [ ! -f "$file" ]; then
        print_error "File not found: $file"
        return 1
    fi

    # Check if file is empty
    if [ ! -s "$file" ]; then
        print_error "File is empty: $file"
        return 1
    fi

    # YAML syntax check
    if ! check_yaml_syntax "$file"; then
        file_valid=false
    fi

    # kubectl validation
    if ! validate_kubectl "$file"; then
        file_valid=false
    fi

    # kubeconform validation
    if ! validate_kubeconform "$file"; then
        file_valid=false
    fi

    # Security checks (warnings only)
    check_security "$file"

    echo ""

    if [ "$file_valid" = true ]; then
        print_success "File validation passed: $file"
        ((VALID_FILES++))
        return 0
    else
        print_error "File validation failed: $file"
        ((INVALID_FILES++))
        return 1
    fi
}

# Process files and directories
process_path() {
    local path="$1"

    if [ -f "$path" ]; then
        # Single file
        if [[ "$path" =~ \.(yaml|yml)$ ]]; then
            ((TOTAL_FILES++))
            validate_file "$path"
        else
            print_warning "Skipping non-YAML file: $path"
        fi
    elif [ -d "$path" ]; then
        # Directory - process all YAML files
        local yaml_files
        yaml_files=$(find "$path" -type f \( -name "*.yaml" -o -name "*.yml" \))

        if [ -z "$yaml_files" ]; then
            print_warning "No YAML files found in: $path"
            return 0
        fi

        while IFS= read -r file; do
            ((TOTAL_FILES++))
            validate_file "$file"
        done <<< "$yaml_files"
    else
        print_error "Invalid path: $path"
        return 1
    fi
}

# Print summary
print_summary() {
    print_header "Validation Summary"

    echo "Total files validated: $TOTAL_FILES"
    echo -e "${GREEN}Valid files: $VALID_FILES${NC}"

    if [ $INVALID_FILES -gt 0 ]; then
        echo -e "${RED}Invalid files: $INVALID_FILES${NC}"
    fi

    if [ $WARNINGS -gt 0 ]; then
        echo -e "${YELLOW}Warnings: $WARNINGS${NC}"
    fi

    echo ""

    if [ $INVALID_FILES -eq 0 ]; then
        if [ $WARNINGS -eq 0 ]; then
            print_success "All validations passed! No issues found."
            return 0
        else
            print_warning "Validation passed with warnings. Review warnings above."
            if [ "$STRICT_MODE" = "true" ]; then
                return 1
            else
                return 0
            fi
        fi
    else
        print_error "Validation failed. Fix errors above."
        return 1
    fi
}

# Check prerequisites
check_prerequisites() {
    if ! command_exists kubectl; then
        print_error "kubectl is not installed or not in PATH"
        echo ""
        echo "Install kubectl:"
        echo "  macOS: brew install kubectl"
        echo "  Linux: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/"
        echo "  Windows: https://kubernetes.io/docs/tasks/tools/install-kubectl-windows/"
        exit 1
    fi

    print_info "Using kubectl version: $(kubectl version --client --short 2>/dev/null || kubectl version --client)"
    print_info "Kubernetes target version: $KUBE_VERSION"

    if command_exists kubeconform; then
        print_info "kubeconform is available"
    else
        print_warning "kubeconform not found (optional but recommended)"
        echo "  Install: https://github.com/yannh/kubeconform"
    fi

    if command_exists kubeval; then
        print_info "kubeval is available"
    else
        print_info "kubeval not found (optional)"
    fi

    if command_exists yamllint; then
        print_info "yamllint is available"
    else
        print_info "yamllint not found (optional)"
    fi

    echo ""
}

# Usage information
usage() {
    cat <<EOF
Kubernetes Manifest Validator

Usage: $0 [OPTIONS] <file|directory>...

Validates Kubernetes YAML manifests using:
  - kubectl dry-run
  - kubeconform (if installed)
  - kubeval (if installed)
  - Security best practice checks

Options:
  -h, --help              Show this help message
  -v, --version VERSION   Kubernetes version to validate against (default: $KUBE_VERSION)
  -s, --strict            Strict mode - fail on warnings

Environment Variables:
  KUBE_VERSION           Kubernetes version (default: 1.29.0)
  STRICT_MODE            Enable strict mode (true/false)

Examples:
  $0 deployment.yaml
  $0 manifests/
  $0 deployment.yaml service.yaml
  KUBE_VERSION=1.28.0 $0 manifests/
  $0 --strict manifests/

EOF
}

# Parse arguments
parse_args() {
    if [ $# -eq 0 ]; then
        usage
        exit 1
    fi

    local paths=()

    while [ $# -gt 0 ]; do
        case $1 in
            -h|--help)
                usage
                exit 0
                ;;
            -v|--version)
                KUBE_VERSION="$2"
                shift 2
                ;;
            -s|--strict)
                STRICT_MODE=true
                shift
                ;;
            -*)
                print_error "Unknown option: $1"
                usage
                exit 1
                ;;
            *)
                paths+=("$1")
                shift
                ;;
        esac
    done

    echo "${paths[@]}"
}

# Main function
main() {
    local paths
    paths=($(parse_args "$@"))

    print_header "Kubernetes Manifest Validator"

    check_prerequisites

    for path in "${paths[@]}"; do
        process_path "$path"
    done

    print_summary
}

# Run main function
main "$@"
