#!/bin/bash
# =============================================================================
# PRODUCTION DOCKER BUILD SCRIPT - AUTOMATIC VERSIONING
# =============================================================================
# Purpose: Build and push Docker images with automatic versioning and validation
# Features:
#   - Automatic version tagging (git commit, timestamp, latest)
#   - BuildKit optimization with cache mounts
#   - Binary validation in built image
#   - Image size reporting
#   - Build time measurement
#   - Multi-platform support (future)
#   - Error handling with exit codes
#
# Usage:
#   ./scripts/build_docker_images.sh                    # Build and push with all defaults
#   ./scripts/build_docker_images.sh --no-push          # Build only, skip push
#   ./scripts/build_docker_images.sh --dry-run          # Show what would be built
#   ./scripts/build_docker_images.sh --dockerfile Dockerfile.runpod  # Specify Dockerfile
#   ./scripts/build_docker_images.sh --platform linux/amd64  # Specific platform
#   ./scripts/build_docker_images.sh --skip-validation  # Skip binary validation
#
# Exit codes:
#   0 - Success
#   1 - Build failed
#   2 - Validation failed
#   3 - Push failed
#   4 - Invalid arguments
# =============================================================================

set -euo pipefail

# =============================================================================
# COLOR DEFINITIONS
# =============================================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# =============================================================================
# CONFIGURATION
# =============================================================================
DOCKER_REGISTRY="jgrusewski"
IMAGE_NAME="foxhunt-hyperopt"
DEFAULT_DOCKERFILE="Dockerfile.foxhunt-build"
EXPECTED_BINARIES=(
    "hyperopt_baseline_rl"
    "hyperopt_baseline_supervised"
)

# =============================================================================
# SCRIPT VARIABLES
# =============================================================================
DOCKERFILE="${DEFAULT_DOCKERFILE}"
DO_PUSH=true
DRY_RUN=false
PLATFORM=""
SKIP_VALIDATION=false
BUILD_START_TIME=""
BUILD_END_TIME=""

# =============================================================================
# HELPER FUNCTIONS
# =============================================================================

# Print colored output
print_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

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

print_header() {
    echo -e "\n${CYAN}==============================================================================${NC}"
    echo -e "${CYAN}$1${NC}"
    echo -e "${CYAN}==============================================================================${NC}\n"
}

# Calculate time difference
time_diff() {
    local start=$1
    local end=$2
    local diff=$((end - start))
    echo "${diff}s"
}

# Format bytes to human-readable
format_bytes() {
    local bytes=$1
    if [ "$bytes" -lt 1024 ]; then
        echo "${bytes}B"
    elif [ "$bytes" -lt 1048576 ]; then
        echo "$((bytes / 1024))KB"
    elif [ "$bytes" -lt 1073741824 ]; then
        echo "$((bytes / 1048576))MB"
    else
        echo "$((bytes / 1073741824))GB"
    fi
}

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

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

Build and push Docker images with automatic versioning.

OPTIONS:
    --dockerfile FILE       Dockerfile to build (default: ${DEFAULT_DOCKERFILE})
    --no-push              Build only, skip pushing to registry
    --dry-run              Show what would be built without building
    --platform PLATFORM    Build for specific platform (e.g., linux/amd64)
    --skip-validation      Skip binary validation after build
    -h, --help             Show this help message

EXAMPLES:
    $(basename "$0")                                    # Build and push with all defaults
    $(basename "$0") --no-push                          # Build only, no push
    $(basename "$0") --dry-run                          # Show build plan
    $(basename "$0") --dockerfile Dockerfile.runpod     # Specific Dockerfile
    $(basename "$0") --platform linux/amd64             # Specific platform

EXIT CODES:
    0 - Success
    1 - Build failed
    2 - Validation failed
    3 - Push failed
    4 - Invalid arguments
EOF
}

# =============================================================================
# ARGUMENT PARSING
# =============================================================================
parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --dockerfile)
                DOCKERFILE="$2"
                shift 2
                ;;
            --no-push)
                DO_PUSH=false
                shift
                ;;
            --dry-run)
                DRY_RUN=true
                shift
                ;;
            --platform)
                PLATFORM="$2"
                shift 2
                ;;
            --skip-validation)
                SKIP_VALIDATION=true
                shift
                ;;
            -h|--help)
                usage
                exit 0
                ;;
            *)
                print_error "Unknown option: $1"
                usage
                exit 4
                ;;
        esac
    done
}

# =============================================================================
# VALIDATION FUNCTIONS
# =============================================================================

# Check prerequisites
check_prerequisites() {
    print_header "CHECKING PREREQUISITES"

    # Check Docker
    if ! command_exists docker; then
        print_error "Docker is not installed"
        exit 4
    fi
    print_success "Docker: $(docker --version)"

    # Check git
    if ! command_exists git; then
        print_error "Git is not installed"
        exit 4
    fi
    print_success "Git: $(git --version)"

    # Check if in git repository
    if ! git rev-parse --git-dir > /dev/null 2>&1; then
        print_error "Not in a git repository"
        exit 4
    fi
    print_success "Git repository detected"

    # Check if Dockerfile exists
    if [ ! -f "$DOCKERFILE" ]; then
        print_error "Dockerfile not found: $DOCKERFILE"
        exit 4
    fi
    print_success "Dockerfile: $DOCKERFILE"

    # Check Docker daemon
    if ! docker info > /dev/null 2>&1; then
        print_error "Docker daemon is not running"
        exit 4
    fi
    print_success "Docker daemon running"

    # Check BuildKit support
    if ! docker buildx version > /dev/null 2>&1; then
        print_warning "BuildKit not available, using legacy builder"
    else
        print_success "BuildKit available: $(docker buildx version | head -n1)"
    fi
}

# Get version tags
get_version_tags() {
    print_header "GENERATING VERSION TAGS"

    # Git commit hash (short)
    GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
    print_info "Git commit: $GIT_COMMIT"

    # Timestamp (YYYYMMDD_HHMMSS UTC)
    TIMESTAMP=$(date -u +"%Y%m%d_%H%M%S")
    print_info "Timestamp: $TIMESTAMP"

    # Check if working directory is clean
    if ! git diff-index --quiet HEAD -- 2>/dev/null; then
        print_warning "Working directory has uncommitted changes"
        GIT_COMMIT="${GIT_COMMIT}-dirty"
    fi

    # Generate full image tags
    TAG_LATEST="${DOCKER_REGISTRY}/${IMAGE_NAME}:latest"
    TAG_COMMIT="${DOCKER_REGISTRY}/${IMAGE_NAME}:${GIT_COMMIT}"
    TAG_TIMESTAMP="${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP}"

    print_success "Tags generated:"
    echo "  - ${TAG_LATEST}"
    echo "  - ${TAG_COMMIT}"
    echo "  - ${TAG_TIMESTAMP}"
}

# Validate binaries in built image (EMBEDDED BINARIES ARCHITECTURE)
validate_binaries() {
    local image_tag=$1

    print_header "VALIDATING IMAGE"

    # Check that all expected binaries are embedded in /usr/local/bin/
    print_info "Checking embedded binaries in /usr/local/bin/..."
    local missing_binaries=()

    for binary in "${EXPECTED_BINARIES[@]}"; do
        # Override entrypoint to bypass self-termination wrapper during validation
        if ! docker run --rm --entrypoint="" "$image_tag" test -f "/usr/local/bin/$binary"; then
            print_error "Binary not found: /usr/local/bin/$binary"
            missing_binaries+=("$binary")
        else
            print_success "Binary exists: /usr/local/bin/$binary"
        fi
    done

    if [ ${#missing_binaries[@]} -gt 0 ]; then
        print_error "Missing binaries: ${missing_binaries[*]}"
        return 1
    fi

    # Check CUDA libraries
    print_info "Checking CUDA libraries..."
    if ! docker run --rm --entrypoint="" "$image_tag" test -d /usr/local/cuda; then
        print_error "CUDA directory not found in image"
        return 1
    fi
    print_success "CUDA runtime present: /usr/local/cuda"

    # Check cuDNN libraries
    print_info "Checking cuDNN libraries..."
    if ! docker run --rm --entrypoint="" "$image_tag" sh -c 'ldconfig -p | grep -q libcudnn'; then
        print_warning "cuDNN library not found (may be OK if using base image)"
    else
        print_success "cuDNN library present"
    fi

    # Check GLIBC version (should be 2.35 for Ubuntu 22.04)
    print_info "Checking GLIBC version..."
    local glibc_version
    glibc_version=$(docker run --rm --entrypoint="" "$image_tag" ldd --version | head -n1 | grep -oP '\d+\.\d+' | head -n1)
    print_success "GLIBC version: $glibc_version"

    print_success "Image validation complete"
    return 0
}

# Get image size
get_image_size() {
    local image_tag=$1

    print_header "IMAGE SIZE REPORT"

    # Get image size in bytes
    local size_bytes
    size_bytes=$(docker image inspect "$image_tag" --format='{{.Size}}' 2>/dev/null || echo "0")

    # Get human-readable size
    local size_human
    size_human=$(docker image inspect "$image_tag" --format='{{.Size}}' 2>/dev/null | \
        awk '{ sum=$1; hum[1024**3]="GB"; hum[1024**2]="MB"; hum[1024]="KB";
               for (x=1024**3; x>=1024; x/=1024) {
                   if (sum>=x) { printf "%.2f %s\n", sum/x, hum[x]; break }
               } }')

    if [ -z "$size_human" ]; then
        size_human=$(format_bytes "$size_bytes")
    fi

    print_info "Image size: ${size_human}"

    # Show layer sizes
    print_info "Layer breakdown:"
    docker history --human=true --no-trunc "$image_tag" | head -n 10
}

# =============================================================================
# BUILD FUNCTIONS
# =============================================================================

# Build Docker image
build_image() {
    print_header "BUILDING DOCKER IMAGE"

    BUILD_START_TIME=$(date +%s)

    # Prepare build command
    local build_cmd="docker build"
    local build_args=()

    # Enable BuildKit if available
    if command_exists docker-buildx || [ -n "${DOCKER_BUILDKIT:-}" ]; then
        export DOCKER_BUILDKIT=1
        print_info "BuildKit enabled"
    fi

    # Add build arguments
    build_args+=("--build-arg" "GIT_COMMIT=${GIT_COMMIT}")
    build_args+=("--build-arg" "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')")
    build_args+=("--build-arg" "VERSION=${GIT_COMMIT}")

    # Add platform if specified
    if [ -n "$PLATFORM" ]; then
        build_args+=("--platform" "$PLATFORM")
        print_info "Building for platform: $PLATFORM"
    fi

    # Add tags
    build_args+=("-t" "$TAG_LATEST")
    build_args+=("-t" "$TAG_COMMIT")
    build_args+=("-t" "$TAG_TIMESTAMP")

    # Add Dockerfile
    build_args+=("-f" "$DOCKERFILE")

    # Add context (current directory)
    build_args+=(".")

    # Show build command
    print_info "Build command:"
    echo "  DOCKER_BUILDKIT=1 ${build_cmd} ${build_args[*]}"
    echo ""

    # Execute build
    if [ "$DRY_RUN" = true ]; then
        print_warning "DRY RUN: Would execute build command above"
        return 0
    fi

    print_info "Starting build..."
    if ${build_cmd} "${build_args[@]}"; then
        BUILD_END_TIME=$(date +%s)
        local build_time
        build_time=$(time_diff "$BUILD_START_TIME" "$BUILD_END_TIME")
        print_success "Build completed in ${build_time}"
        return 0
    else
        print_error "Build failed"
        return 1
    fi
}

# Push images to registry
push_images() {
    print_header "PUSHING IMAGES TO REGISTRY"

    if [ "$DRY_RUN" = true ]; then
        print_warning "DRY RUN: Would push the following tags:"
        echo "  - ${TAG_LATEST}"
        echo "  - ${TAG_COMMIT}"
        echo "  - ${TAG_TIMESTAMP}"
        return 0
    fi

    if [ "$DO_PUSH" = false ]; then
        print_warning "Skipping push (--no-push flag)"
        return 0
    fi

    # Check Docker Hub login
    if ! docker info 2>/dev/null | grep -q "Username: ${DOCKER_REGISTRY}"; then
        print_warning "Not logged in to Docker Hub as ${DOCKER_REGISTRY}"
        print_info "Run: docker login"
        print_warning "Skipping push"
        return 0
    fi

    # Push all tags
    local tags=("$TAG_LATEST" "$TAG_COMMIT" "$TAG_TIMESTAMP")
    for tag in "${tags[@]}"; do
        print_info "Pushing: $tag"
        if docker push "$tag"; then
            print_success "Pushed: $tag"
        else
            print_error "Failed to push: $tag"
            return 1
        fi
    done

    print_success "All images pushed successfully"
    return 0
}

# =============================================================================
# MAIN EXECUTION
# =============================================================================

main() {
    print_header "FOXHUNT DOCKER BUILD SCRIPT"

    # Parse command line arguments
    parse_args "$@"

    # Check prerequisites
    check_prerequisites

    # Generate version tags
    get_version_tags

    # Show dry-run notice
    if [ "$DRY_RUN" = true ]; then
        print_warning "DRY RUN MODE - No actual changes will be made"
        echo ""
    fi

    # Build image
    if ! build_image; then
        print_error "Build failed"
        exit 1
    fi

    # Skip validation and reporting in dry-run mode
    if [ "$DRY_RUN" = true ]; then
        print_success "Dry-run completed successfully"
        exit 0
    fi

    # Validate binaries (unless skipped)
    if [ "$SKIP_VALIDATION" = false ]; then
        if ! validate_binaries "$TAG_LATEST"; then
            print_error "Validation failed"
            exit 2
        fi
    else
        print_warning "Skipping validation (--skip-validation flag)"
    fi

    # Report image size
    get_image_size "$TAG_LATEST"

    # Push images
    if ! push_images; then
        print_error "Push failed"
        exit 3
    fi

    # Final summary
    print_header "BUILD SUMMARY"
    print_success "Build completed successfully"
    echo ""
    echo "Image tags:"
    echo "  - ${TAG_LATEST}"
    echo "  - ${TAG_COMMIT}"
    echo "  - ${TAG_TIMESTAMP}"
    echo ""

    if [ "$DO_PUSH" = true ] && docker info 2>/dev/null | grep -q "Username: ${DOCKER_REGISTRY}"; then
        echo "Images pushed to Docker Hub: https://hub.docker.com/r/${DOCKER_REGISTRY}/${IMAGE_NAME}"
    else
        echo "Images built locally (not pushed)"
    fi

    if [ -n "$BUILD_START_TIME" ] && [ -n "$BUILD_END_TIME" ]; then
        local total_time
        total_time=$(time_diff "$BUILD_START_TIME" "$BUILD_END_TIME")
        echo "Total build time: ${total_time}"
    fi
    echo ""

    print_success "Done!"
    exit 0
}

# Run main function
main "$@"
