#!/bin/bash
# Validation functions for Mosaic Stack installer
# Post-install validation and health checks

# shellcheck source=lib/platform.sh
source "${BASH_SOURCE[0]%/*}/platform.sh"

# ============================================================================
# Validation Result Codes
# ============================================================================

readonly CHECK_PASS=0
readonly CHECK_WARN=1
readonly CHECK_FAIL=2

# ============================================================================
# Port Validation
# ============================================================================

# Check if a port is in use
check_port_in_use() {
    local port="$1"
    
    # Try ss first (most common on modern Linux)
    if command -v ss &>/dev/null; then
        ss -tuln 2>/dev/null | grep -q ":${port} "
        return $?
    fi
    
    # Fall back to netstat
    if command -v netstat &>/dev/null; then
        netstat -tuln 2>/dev/null | grep -q ":${port} "
        return $?
    fi
    
    # Fall back to lsof
    if command -v lsof &>/dev/null; then
        lsof -i ":$port" &>/dev/null
        return $?
    fi
    
    # Can't check, assume port is free
    return 1
}

# Get process using a port
get_process_on_port() {
    local port="$1"
    
    if command -v lsof &>/dev/null; then
        lsof -i ":$port" -t 2>/dev/null | head -1
    elif command -v ss &>/dev/null; then
        ss -tulnp 2>/dev/null | grep ":${port} " | grep -oP 'pid=\K[0-9]+' | head -1
    else
        echo "unknown"
    fi
}

# Validate port number
validate_port() {
    local port="$1"
    
    if [[ "$port" =~ ^[0-9]+$ ]] && [[ "$port" -ge 1 ]] && [[ "$port" -le 65535 ]]; then
        return 0
    fi
    return 1
}

# Check all configured ports
check_all_ports() {
    local env_file="${1:-.env}"
    local errors=0
    local warnings=0
    
    # Load env file if it exists
    if [[ -f "$env_file" ]]; then
        set -a
        # shellcheck source=/dev/null
        source "$env_file" 2>/dev/null || true
        set +a
    fi
    
    # Default ports
    declare -A default_ports=(
        [WEB_PORT]=3000
        [API_PORT]=3001
        [POSTGRES_PORT]=5432
        [VALKEY_PORT]=6379
        [AUTHENTIK_PORT_HTTP]=9000
        [AUTHENTIK_PORT_HTTPS]=9443
        [OLLAMA_PORT]=11434
        [TRAEFIK_HTTP_PORT]=80
        [TRAEFIK_HTTPS_PORT]=443
        [TRAEFIK_DASHBOARD_PORT]=8080
    )
    
    echo -e "${BOLD}Checking ports...${NC}"
    echo ""
    
    for port_var in "${!default_ports[@]}"; do
        local port="${!port_var:-${default_ports[$port_var]}}"
        
        if check_port_in_use "$port"; then
            local process
            process=$(get_process_on_port "$port")
            echo -e "${WARN}⚠${NC} $port_var: Port $port is in use (PID: $process)"
            ((warnings++))
        else
            echo -e "${SUCCESS}✓${NC} $port_var: Port $port available"
        fi
    done
    
    echo ""
    
    if [[ $warnings -gt 0 ]]; then
        return $CHECK_WARN
    fi
    return $CHECK_PASS
}

# ============================================================================
# Environment Validation
# ============================================================================

# Required environment variables
REQUIRED_ENV_VARS=(
    "DATABASE_URL"
    "JWT_SECRET"
    "BETTER_AUTH_SECRET"
    "ENCRYPTION_KEY"
)

# Optional but recommended environment variables
RECOMMENDED_ENV_VARS=(
    "POSTGRES_PASSWORD"
    "VALKEY_URL"
    "NEXT_PUBLIC_API_URL"
    "NEXT_PUBLIC_APP_URL"
)

# Check if env file exists
check_env_file() {
    local env_file="${1:-.env}"
    
    if [[ -f "$env_file" ]]; then
        echo -e "${SUCCESS}✓${NC} .env file exists"
        return 0
    else
        echo -e "${ERROR}✗${NC} .env file not found"
        return $CHECK_FAIL
    fi
}

# Check required environment variables
check_required_env() {
    local env_file="${1:-.env}"
    local errors=0
    
    echo -e "${BOLD}Checking required environment variables...${NC}"
    echo ""
    
    # Load env file
    if [[ -f "$env_file" ]]; then
        set -a
        # shellcheck source=/dev/null
        source "$env_file" 2>/dev/null || true
        set +a
    fi
    
    for var in "${REQUIRED_ENV_VARS[@]}"; do
        local value="${!var:-}"
        if [[ -z "$value" ]]; then
            echo -e "${ERROR}✗${NC} $var: Not set"
            ((errors++))
        elif is_placeholder "$value"; then
            echo -e "${WARN}⚠${NC} $var: Contains placeholder value"
            ((errors++))
        else
            echo -e "${SUCCESS}✓${NC} $var: Set"
        fi
    done
    
    echo ""
    
    if [[ $errors -gt 0 ]]; then
        return $CHECK_FAIL
    fi
    return $CHECK_PASS
}

# Check recommended environment variables
check_recommended_env() {
    local env_file="${1:-.env}"
    local warnings=0
    
    echo -e "${BOLD}Checking recommended environment variables...${NC}"
    echo ""
    
    # Load env file
    if [[ -f "$env_file" ]]; then
        set -a
        # shellcheck source=/dev/null
        source "$env_file" 2>/dev/null || true
        set +a
    fi
    
    for var in "${RECOMMENDED_ENV_VARS[@]}"; do
        local value="${!var:-}"
        if [[ -z "$value" ]]; then
            echo -e "${WARN}⚠${NC} $var: Not set (using default)"
            ((warnings++))
        elif is_placeholder "$value"; then
            echo -e "${WARN}⚠${NC} $var: Contains placeholder value"
            ((warnings++))
        else
            echo -e "${SUCCESS}✓${NC} $var: Set"
        fi
    done
    
    echo ""
    
    if [[ $warnings -gt 0 ]]; then
        return $CHECK_WARN
    fi
    return $CHECK_PASS
}

# Check if a value is a placeholder
is_placeholder() {
    local value="$1"
    
    if [[ -z "$value" ]]; then
        return 0
    fi
    
    # Common placeholder patterns
    case "$value" in
        *"REPLACE_WITH"*|*"CHANGE_ME"*|*"changeme"*|*"your-"*|*"example"*|*"placeholder"*|*"TODO"*|*"FIXME"*)
            return 0
            ;;
        *"xxx"*|*"<"*">"*|*"\${"*|*"$${"*)
            return 0
            ;;
    esac
    
    return 1
}

# ============================================================================
# Secret Validation
# ============================================================================

# Minimum secret lengths
declare -A MIN_SECRET_LENGTHS=(
    [JWT_SECRET]=32
    [BETTER_AUTH_SECRET]=32
    [ENCRYPTION_KEY]=64
    [AUTHENTIK_SECRET_KEY]=50
    [COORDINATOR_API_KEY]=32
    [ORCHESTRATOR_API_KEY]=32
)

# Check secret strength
check_secrets() {
    local env_file="${1:-.env}"
    local errors=0
    local warnings=0
    
    echo -e "${BOLD}Checking secret strength...${NC}"
    echo ""
    
    # Load env file
    if [[ -f "$env_file" ]]; then
        set -a
        # shellcheck source=/dev/null
        source "$env_file" 2>/dev/null || true
        set +a
    fi
    
    for secret_var in "${!MIN_SECRET_LENGTHS[@]}"; do
        local value="${!secret_var:-}"
        local min_len="${MIN_SECRET_LENGTHS[$secret_var]}"
        
        if [[ -z "$value" ]]; then
            echo -e "${WARN}⚠${NC} $secret_var: Not set"
            ((warnings++))
        elif is_placeholder "$value"; then
            echo -e "${ERROR}✗${NC} $secret_var: Contains placeholder (MUST change)"
            ((errors++))
        elif [[ ${#value} -lt $min_len ]]; then
            echo -e "${WARN}⚠${NC} $secret_var: Too short (${#value} chars, minimum $min_len)"
            ((warnings++))
        else
            echo -e "${SUCCESS}✓${NC} $secret_var: Strong (${#value} chars)"
        fi
    done
    
    echo ""
    
    if [[ $errors -gt 0 ]]; then
        return $CHECK_FAIL
    fi
    if [[ $warnings -gt 0 ]]; then
        return $CHECK_WARN
    fi
    return $CHECK_PASS
}

# ============================================================================
# Docker Validation
# ============================================================================

# Check Docker containers are running
check_docker_containers() {
    local compose_file="${1:-docker-compose.yml}"
    local errors=0
    
    echo -e "${BOLD}Checking Docker containers...${NC}"
    echo ""
    
    # Expected container names
    local containers=("mosaic-postgres" "mosaic-valkey" "mosaic-api" "mosaic-web")
    
    for container in "${containers[@]}"; do
        local status
        status=$(docker inspect --format='{{.State.Status}}' "$container" 2>/dev/null || echo "not_found")
        
        case "$status" in
            running)
                echo -e "${SUCCESS}✓${NC} $container: Running"
                ;;
            exited)
                echo -e "${ERROR}✗${NC} $container: Exited"
                ((errors++))
                ;;
            not_found)
                # Container might not be in current profile
                echo -e "${MUTED}○${NC} $container: Not found (may not be in profile)"
                ;;
            *)
                echo -e "${WARN}⚠${NC} $container: $status"
                ((errors++))
                ;;
        esac
    done
    
    echo ""
    
    if [[ $errors -gt 0 ]]; then
        return $CHECK_FAIL
    fi
    return $CHECK_PASS
}

# Check container health
check_container_health() {
    local errors=0
    
    echo -e "${BOLD}Checking container health...${NC}"
    echo ""
    
    # Get all mosaic containers
    local containers
    containers=$(docker ps --filter "name=mosaic-" --format "{{.Names}}" 2>/dev/null)
    
    for container in $containers; do
        local health
        health=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "no_healthcheck")
        
        case "$health" in
            healthy)
                echo -e "${SUCCESS}✓${NC} $container: Healthy"
                ;;
            unhealthy)
                echo -e "${ERROR}✗${NC} $container: Unhealthy"
                ((errors++))
                ;;
            starting)
                echo -e "${WARN}⚠${NC} $container: Starting..."
                ;;
            no_healthcheck)
                echo -e "${INFO}ℹ${NC} $container: No health check"
                ;;
            *)
                echo -e "${WARN}⚠${NC} $container: $health"
                ;;
        esac
    done
    
    echo ""
    
    if [[ $errors -gt 0 ]]; then
        return $CHECK_FAIL
    fi
    return $CHECK_PASS
}

# ============================================================================
# Service Connectivity
# ============================================================================

# Check if a URL responds
check_url_responds() {
    local url="$1"
    local expected_status="${2:-200}"
    local timeout="${3:-10}"
    
    if command -v curl &>/dev/null; then
        local status
        status=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$timeout" "$url" 2>/dev/null)
        
        if [[ "$status" == "$expected_status" ]]; then
            return 0
        fi
    fi
    
    return 1
}

# Check API health endpoint
check_api_health() {
    local api_url="${1:-http://localhost:3001}"
    
    echo -e "${BOLD}Checking API health...${NC}"
    echo ""
    
    if check_url_responds "${api_url}/health" 200 10; then
        echo -e "${SUCCESS}✓${NC} API health check passed"
        return $CHECK_PASS
    else
        echo -e "${ERROR}✗${NC} API health check failed"
        return $CHECK_FAIL
    fi
}

# Check Web frontend
check_web_health() {
    local web_url="${1:-http://localhost:3000}"
    
    echo -e "${BOLD}Checking Web frontend...${NC}"
    echo ""
    
    if check_url_responds "$web_url" 200 10; then
        echo -e "${SUCCESS}✓${NC} Web frontend responding"
        return $CHECK_PASS
    else
        echo -e "${WARN}⚠${NC} Web frontend not responding (may still be starting)"
        return $CHECK_WARN
    fi
}

# Check database connectivity
check_database_connection() {
    local host="${1:-localhost}"
    local port="${2:-5432}"
    local user="${3:-mosaic}"
    local database="${4:-mosaic}"
    
    echo -e "${BOLD}Checking database connection...${NC}"
    echo ""
    
    # Try via Docker if postgres container exists
    if docker exec mosaic-postgres pg_isready -U "$user" -d "$database" &>/dev/null; then
        echo -e "${SUCCESS}✓${NC} Database connection successful"
        return $CHECK_PASS
    fi
    
    # Try via psql if available
    if command -v psql &>/dev/null; then
        if PGPASSWORD="${POSTGRES_PASSWORD:-}" psql -h "$host" -p "$port" -U "$user" -d "$database" -c "SELECT 1" &>/dev/null; then
            echo -e "${SUCCESS}✓${NC} Database connection successful"
            return $CHECK_PASS
        fi
    fi
    
    # Try via TCP
    if command -v nc &>/dev/null; then
        if nc -z "$host" "$port" 2>/dev/null; then
            echo -e "${WARN}⚠${NC} Database port open but could not verify connection"
            return $CHECK_WARN
        fi
    fi
    
    echo -e "${ERROR}✗${NC} Database connection failed"
    return $CHECK_FAIL
}

# Check Valkey/Redis connectivity
check_valkey_connection() {
    local host="${1:-localhost}"
    local port="${2:-6379}"
    
    echo -e "${BOLD}Checking Valkey/Redis connection...${NC}"
    echo ""
    
    # Try via Docker if valkey container exists
    if docker exec mosaic-valkey valkey-cli ping 2>/dev/null | grep -q PONG; then
        echo -e "${SUCCESS}✓${NC} Valkey connection successful"
        return $CHECK_PASS
    fi
    
    # Try via redis-cli if available
    if command -v redis-cli &>/dev/null; then
        if redis-cli -h "$host" -p "$port" ping 2>/dev/null | grep -q PONG; then
            echo -e "${SUCCESS}✓${NC} Valkey/Redis connection successful"
            return $CHECK_PASS
        fi
    fi
    
    # Try via TCP
    if command -v nc &>/dev/null; then
        if nc -z "$host" "$port" 2>/dev/null; then
            echo -e "${WARN}⚠${NC} Valkey port open but could not verify connection"
            return $CHECK_WARN
        fi
    fi
    
    echo -e "${ERROR}✗${NC} Valkey/Redis connection failed"
    return $CHECK_FAIL
}

# ============================================================================
# System Requirements
# ============================================================================

# Check minimum system requirements
check_system_requirements() {
    local min_ram="${1:-2048}"
    local min_disk="${2:-10}"
    local errors=0
    local warnings=0
    
    echo -e "${BOLD}Checking system requirements...${NC}"
    echo ""
    
    # RAM check
    local ram
    ram=$(get_total_ram)
    if [[ "$ram" -lt "$min_ram" ]]; then
        echo -e "${ERROR}✗${NC} RAM: ${ram}MB (minimum: ${min_ram}MB)"
        ((errors++))
    else
        echo -e "${SUCCESS}✓${NC} RAM: ${ram}MB"
    fi
    
    # Disk check
    local disk
    disk=$(get_available_disk "$HOME")
    if [[ "$disk" -lt "$min_disk" ]]; then
        echo -e "${WARN}⚠${NC} Disk: ${disk}GB available (recommended: ${min_disk}GB+)"
        ((warnings++))
    else
        echo -e "${SUCCESS}✓${NC} Disk: ${disk}GB available"
    fi
    
    # Docker disk (if using Docker)
    if command -v docker &>/dev/null && docker info &>/dev/null; then
        local docker_disk
        docker_disk=$(docker system df --format "{{.Total}}" 2>/dev/null | head -1 || echo "unknown")
        echo -e "${INFO}ℹ${NC} Docker storage: $docker_disk"
    fi
    
    echo ""
    
    if [[ $errors -gt 0 ]]; then
        return $CHECK_FAIL
    fi
    if [[ $warnings -gt 0 ]]; then
        return $CHECK_WARN
    fi
    return $CHECK_PASS
}

# ============================================================================
# File Permissions
# ============================================================================

# Check .env file permissions
check_env_permissions() {
    local env_file="${1:-.env}"
    
    echo -e "${BOLD}Checking file permissions...${NC}"
    echo ""
    
    if [[ ! -f "$env_file" ]]; then
        echo -e "${WARN}⚠${NC} .env file not found"
        return $CHECK_WARN
    fi
    
    local perms
    perms=$(stat -c "%a" "$env_file" 2>/dev/null || stat -f "%OLp" "$env_file" 2>/dev/null)
    
    # Check if world-readable
    if [[ "$perms" =~ [0-7][0-7][4-7]$ ]]; then
        echo -e "${WARN}⚠${NC} .env is world-readable (permissions: $perms)"
        echo -e "  ${INFO}Fix: chmod 600 $env_file${NC}"
        return $CHECK_WARN
    fi
    
    echo -e "${SUCCESS}✓${NC} .env permissions: $perms"
    return $CHECK_PASS
}

# ============================================================================
# Comprehensive Doctor Check
# ============================================================================

# Run all checks and report results
run_doctor() {
    local env_file="${1:-.env}"
    local compose_file="${2:-docker-compose.yml}"
    local mode="${3:-docker}"
    
    local errors=0
    local warnings=0
    
    echo ""
    echo -e "${BOLD}════════════════════════════════════════════════════════════${NC}"
    echo -e "${BOLD}  Mosaic Stack Doctor${NC}"
    echo -e "${BOLD}════════════════════════════════════════════════════════════${NC}"
    echo ""
    
    # System requirements
    run_doctor_check "System Requirements" check_system_requirements 2048 10
    collect_result $?
    
    # Environment file
    run_doctor_check "Environment File" check_env_file "$env_file"
    collect_result $?
    
    # Required environment variables
    run_doctor_check "Required Variables" check_required_env "$env_file"
    collect_result $?
    
    # Secret strength
    run_doctor_check "Secret Strength" check_secrets "$env_file"
    collect_result $?
    
    # File permissions
    run_doctor_check "File Permissions" check_env_permissions "$env_file"
    collect_result $?
    
    if [[ "$mode" == "docker" ]]; then
        # Docker containers
        run_doctor_check "Docker Containers" check_docker_containers "$compose_file"
        collect_result $?
        
        # Container health
        run_doctor_check "Container Health" check_container_health
        collect_result $?
        
        # Database connection
        run_doctor_check "Database" check_database_connection
        collect_result $?
        
        # Valkey connection
        run_doctor_check "Cache (Valkey)" check_valkey_connection
        collect_result $?
        
        # API health
        run_doctor_check "API" check_api_health
        collect_result $?
        
        # Web frontend
        run_doctor_check "Web Frontend" check_web_health
        collect_result $?
    fi
    
    echo ""
    echo -e "${BOLD}════════════════════════════════════════════════════════════${NC}"
    
    # Summary
    if [[ $errors -gt 0 ]]; then
        echo -e "${ERROR}✗${NC} ${BOLD}Failed${NC}: $errors errors, $warnings warnings"
        echo ""
        echo "Fix the errors above and run doctor again."
        return $CHECK_FAIL
    elif [[ $warnings -gt 0 ]]; then
        echo -e "${WARN}⚠${NC} ${BOLD}Warnings${NC}: $warnings warnings"
        echo ""
        echo "System is operational but some optimizations are recommended."
        return $CHECK_WARN
    else
        echo -e "${SUCCESS}✓${NC} ${BOLD}All checks passed${NC}"
        echo ""
        echo "Mosaic Stack is healthy and ready to use."
        return $CHECK_PASS
    fi
}

# Helper to run a check and print result
run_doctor_check() {
    local name="$1"
    shift
    
    echo -e "${BOLD}Checking: $name${NC}"
    echo ""
    
    "$@"
    return $?
}

# Helper to collect check results
collect_result() {
    local result=$1
    
    case $result in
        $CHECK_PASS) ;;
        $CHECK_WARN) ((warnings++)) ;;
        $CHECK_FAIL) ((errors++)) ;;
    esac
}

# ============================================================================
# Quick Health Check
# ============================================================================

# Quick check for CI/CD or scripts
quick_health_check() {
    local api_url="${1:-http://localhost:3001}"
    
    check_url_responds "${api_url}/health" 200 5
}

# Wait for healthy state
wait_for_healthy() {
    local timeout="${1:-120}"
    local interval="${2:-5}"
    
    echo -e "${INFO}ℹ${NC} Waiting for healthy state..."
    
    local elapsed=0
    while [[ $elapsed -lt $timeout ]]; do
        if quick_health_check &>/dev/null; then
            echo -e "${SUCCESS}✓${NC} System is healthy"
            return 0
        fi
        
        sleep "$interval"
        ((elapsed += interval))
        echo -n "."
    done
    
    echo ""
    echo -e "${ERROR}✗${NC} Timeout waiting for healthy state"
    return 1
}
