#!/bin/bash
#
# Docker Compose Validator - Validates compose files for syntax and best practices
#
# Usage: ./validate-compose.sh [compose-file-or-directory]
#

set -e

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

print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
print_success() { echo -e "${GREEN}✓${NC} $1"; }
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
print_error() { echo -e "${RED}✗${NC} $1"; }

# Parse argument
COMPOSE_PATH="${1:-.}"

# Find compose file
if [[ -d "$COMPOSE_PATH" ]]; then
    # Check for compose.yaml first (modern), then docker-compose.yml (legacy)
    if [[ -f "$COMPOSE_PATH/compose.yaml" ]]; then
        COMPOSE_FILE="$COMPOSE_PATH/compose.yaml"
    elif [[ -f "$COMPOSE_PATH/docker-compose.yml" ]]; then
        COMPOSE_FILE="$COMPOSE_PATH/docker-compose.yml"
        print_warning "Using legacy filename 'docker-compose.yml'. Consider renaming to 'compose.yaml'"
    elif [[ -f "$COMPOSE_PATH/docker-compose.yaml" ]]; then
        COMPOSE_FILE="$COMPOSE_PATH/docker-compose.yaml"
        print_warning "Using legacy filename 'docker-compose.yaml'. Consider renaming to 'compose.yaml'"
    else
        print_error "No compose file found in $COMPOSE_PATH"
        exit 1
    fi
    COMPOSE_DIR="$COMPOSE_PATH"
elif [[ -f "$COMPOSE_PATH" ]]; then
    COMPOSE_FILE="$COMPOSE_PATH"
    COMPOSE_DIR="$(dirname "$COMPOSE_PATH")"
else
    print_error "File or directory not found: $COMPOSE_PATH"
    exit 1
fi

echo
print_info "Validating Docker Compose file: $COMPOSE_FILE"
echo

# Check if docker is installed
if ! command -v docker &> /dev/null; then
    print_error "Docker is not installed"
    exit 1
fi

# Check if docker compose is available
if ! docker compose version &> /dev/null; then
    print_error "Docker Compose is not available"
    print_info "Install: https://docs.docker.com/compose/install/"
    exit 1
fi

ERRORS=0
WARNINGS=0

# Test 1: Syntax validation
print_info "Test 1: Validating syntax..."
cd "$COMPOSE_DIR"
if docker compose -f "$COMPOSE_FILE" config > /dev/null 2>&1; then
    print_success "Syntax valid"
else
    print_error "Syntax validation failed"
    docker compose -f "$COMPOSE_FILE" config
    ((ERRORS++))
fi
echo

# Test 2: Check for version field (should not exist in v2)
print_info "Test 2: Checking for deprecated version field..."
if grep -q "^version:" "$COMPOSE_FILE"; then
    print_warning "File contains deprecated 'version' field (Compose v2 ignores this)"
    ((WARNINGS++))
else
    print_success "No deprecated version field"
fi
echo

# Test 3: Check for explicit image tags
print_info "Test 3: Checking image tags..."
if grep -E "image:.*:latest" "$COMPOSE_FILE" > /dev/null 2>&1; then
    print_warning "Found ':latest' tag - use explicit versions in production"
    ((WARNINGS++))
fi

if grep -E "image:.*[^:]$" "$COMPOSE_FILE" | grep -v "#" > /dev/null 2>&1; then
    print_warning "Found images without tags - always specify explicit versions"
    ((WARNINGS++))
fi

if ! grep -q "image:.*:" "$COMPOSE_FILE"; then
    print_warning "No images found - this may be a build-only compose file"
else
    print_success "Image tags specified"
fi
echo

# Test 4: Check for restart policies
print_info "Test 4: Checking restart policies..."
if grep -q "restart:" "$COMPOSE_FILE"; then
    print_success "Restart policies defined"
    if grep -q "restart:.*no" "$COMPOSE_FILE"; then
        print_warning "Some services have restart: 'no' - consider 'unless-stopped' for production"
        ((WARNINGS++))
    fi
else
    print_warning "No restart policies found - services won't auto-restart"
    ((WARNINGS++))
fi
echo

# Test 5: Check for health checks
print_info "Test 5: Checking health checks..."
if grep -q "healthcheck:" "$COMPOSE_FILE"; then
    print_success "Health checks found"
else
    print_warning "No health checks defined - consider adding for production reliability"
    ((WARNINGS++))
fi
echo

# Test 6: Check for resource limits
print_info "Test 6: Checking resource limits..."
if grep -q "resources:" "$COMPOSE_FILE" || grep -q "deploy:" "$COMPOSE_FILE"; then
    print_success "Resource limits found"
else
    print_warning "No resource limits defined - containers can consume unlimited resources"
    ((WARNINGS++))
fi
echo

# Test 7: Check for hardcoded secrets
print_info "Test 7: Checking for hardcoded secrets..."
FOUND_SECRETS=0
if grep -E "(PASSWORD|SECRET|KEY).*=.*['\"]" "$COMPOSE_FILE" | grep -v "change-" > /dev/null 2>&1; then
    print_warning "Found hardcoded secrets/passwords - use environment variables"
    ((WARNINGS++))
    FOUND_SECRETS=1
fi

if grep -q "env_file:" "$COMPOSE_FILE"; then
    print_success "Using env_file for configuration"
    if [[ -f "$COMPOSE_DIR/.env.example" ]]; then
        print_success "Found .env.example file"
    fi
else
    if [[ $FOUND_SECRETS -eq 0 ]]; then
        print_info "No env_file found - ensure secrets are managed securely"
    fi
fi
echo

# Test 8: Check for named volumes
print_info "Test 8: Checking volume configuration..."
if grep -q "^volumes:" "$COMPOSE_FILE"; then
    print_success "Named volumes defined"
else
    if grep -q "volumes:" "$COMPOSE_FILE"; then
        print_warning "Using volumes but no named volumes defined - consider using named volumes"
        ((WARNINGS++))
    else
        print_info "No volumes defined"
    fi
fi
echo

# Test 9: Check for networks
print_info "Test 9: Checking network configuration..."
if grep -q "^networks:" "$COMPOSE_FILE"; then
    print_success "Custom networks defined"
    if grep -q "internal: true" "$COMPOSE_FILE"; then
        print_success "Internal networks found for isolation"
    fi
else
    print_warning "No custom networks - all services on default network (less isolation)"
    ((WARNINGS++))
fi
echo

# Test 10: Check for depends_on with conditions
print_info "Test 10: Checking service dependencies..."
if grep -q "depends_on:" "$COMPOSE_FILE"; then
    if grep -A 2 "depends_on:" "$COMPOSE_FILE" | grep -q "condition:" ; then
        print_success "Service dependencies with conditions"
    else
        print_warning "depends_on without conditions - services may start before dependencies are ready"
        ((WARNINGS++))
    fi
else
    print_info "No service dependencies defined"
fi
echo

# Test 11: Check .env file
if [[ -f "$COMPOSE_DIR/.env" ]]; then
    print_info "Test 11: Checking .env file..."
    if [[ -r "$COMPOSE_DIR/.env" ]]; then
        # Check for default/example passwords
        if grep -E "(password|PASSWORD).*=.*(change|example|test)" "$COMPOSE_DIR/.env" > /dev/null 2>&1; then
            print_error "Found default passwords in .env file - UPDATE ALL PASSWORDS!"
            ((ERRORS++))
        else
            print_success ".env file exists and readable"
        fi

        # Check permissions
        PERMS=$(stat -f "%Lp" "$COMPOSE_DIR/.env" 2>/dev/null || stat -c "%a" "$COMPOSE_DIR/.env" 2>/dev/null)
        if [[ "$PERMS" != "600" ]] && [[ "$PERMS" != "400" ]]; then
            print_warning ".env file permissions are $PERMS - should be 600 or 400"
            ((WARNINGS++))
        fi
    fi
    echo
fi

# Test 12: Try to render the compose file
print_info "Test 12: Testing compose file rendering..."
if docker compose -f "$COMPOSE_FILE" config > /tmp/compose-validation-$$.yaml 2>&1; then
    print_success "Compose file renders successfully"
    rm -f /tmp/compose-validation-$$.yaml
else
    print_error "Failed to render compose file"
    ((ERRORS++))
fi
echo

# Test 13: Check for security best practices
print_info "Test 13: Checking security practices..."
SECURITY_SCORE=0

if grep -q "privileged: true" "$COMPOSE_FILE"; then
    print_warning "Found privileged containers - avoid if possible"
    ((WARNINGS++))
else
    ((SECURITY_SCORE++))
fi

if grep -q "read_only: true" "$COMPOSE_FILE"; then
    print_success "Found read-only root filesystems"
    ((SECURITY_SCORE++))
fi

if grep -q "user:" "$COMPOSE_FILE"; then
    print_success "Found non-root user configuration"
    ((SECURITY_SCORE++))
fi

if [[ $SECURITY_SCORE -eq 0 ]]; then
    print_warning "No security configurations found - consider adding user, read_only, etc."
    ((WARNINGS++))
fi
echo

# Summary
echo "========================================"
echo "Validation Summary"
echo "========================================"
echo

if [[ $ERRORS -eq 0 ]]; then
    print_success "No errors found"
else
    print_error "$ERRORS error(s) found"
fi

if [[ $WARNINGS -eq 0 ]]; then
    print_success "No warnings"
else
    print_warning "$WARNINGS warning(s) found"
fi

echo
if [[ $ERRORS -eq 0 ]]; then
    print_success "Compose file validation passed!"
    echo
    print_info "Ready to deploy:"
    echo "  cd $(dirname "$COMPOSE_FILE")"
    echo "  docker compose up -d"
    echo
    print_info "View rendered configuration:"
    echo "  docker compose config"
    exit 0
else
    print_error "Compose file validation failed"
    echo
    print_info "Fix the errors above before deploying"
    exit 1
fi
