<?php
/**
 * Unauthenticated Health Check API
 * Provides system health metrics for monitoring tools
 *
 * Endpoints:
 *   GET /api/health.php?check=cpu               - CPU load status
 *   GET /api/health.php?check=connections       - HTTP/HTTPS connection status
 *   GET /api/health.php?check=disk              - Disk usage status (/ and /docker)
 *   GET /api/health.php?check=swap              - Swap usage status
 *   GET /api/health.php?check=docker            - Docker daemon status
 *   GET /api/health.php?check=haproxy           - HAProxy container health status
 *   GET /api/health.php?check=all               - All health checks (default)
 *   GET /api/health.php?check=all&detailed=1    - All checks with detailed metrics
 *
 * Parameters:
 *   check (optional)              - Type of health check (cpu|connections|disk|swap|docker|haproxy|all)
 *   detailed (optional)           - Set to '1' to include detailed metrics (default: status only)
 *
 *   Threshold Overrides (optional):
 *   connections_warning (int)     - Override connection warning threshold (default: 200)
 *   connections_critical (int)    - Override connection critical threshold (default: 400)
 *   cpu_warning (float)           - Override CPU warning threshold (default: 0.7)
 *   cpu_critical (float)          - Override CPU critical threshold (default: 1.0)
 *   disk_warning (int)            - Override disk warning threshold % (default: 80)
 *   disk_critical (int)           - Override disk critical threshold % (default: 90)
 *   swap_warning (int)            - Override swap warning threshold % (default: 50)
 *   swap_critical (int)           - Override swap critical threshold % (default: 80)
 *
 * Rate Limiting: 10 requests per minute per IP address
 *
 * Response Format (default):
 *   {"success": true, "timestamp": "2025-10-09T...", "data": {"status": "healthy"}}
 *
 * Response Format (detailed=1):
 *   {"success": true, "timestamp": "2025-10-09T...", "data": {"status": "healthy", ...}}
 */

// Load health check configuration
if (!defined('WHP')) { define('WHP', true); }
require_once(__DIR__ . '/../configs/health-check.config.php');

// Simple rate limiting for unauthenticated health check endpoint
// Allows 10 requests per minute per IP address
$clientIP = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$rateLimitFile = '/tmp/health_check_rate_limit_' . md5($clientIP);
$rateLimitWindow = 60; // seconds
$rateLimitMax = 10; // requests per window

// Check rate limit
if (file_exists($rateLimitFile)) {
    $rateLimitData = json_decode(file_get_contents($rateLimitFile), true);
    $currentTime = time();

    // Clean up old entries outside the window
    $rateLimitData = array_filter($rateLimitData, function($timestamp) use ($currentTime, $rateLimitWindow) {
        return ($currentTime - $timestamp) < $rateLimitWindow;
    });

    // Check if limit exceeded
    if (count($rateLimitData) >= $rateLimitMax) {
        http_response_code(429);
        header('Retry-After: ' . $rateLimitWindow);
        header('Content-Type: application/json');
        echo json_encode([
            'success' => false,
            'error' => 'Rate limit exceeded. Maximum ' . $rateLimitMax . ' requests per minute.',
            'retry_after' => $rateLimitWindow
        ]);
        exit(1);
    }

    // Add current request
    $rateLimitData[] = $currentTime;
} else {
    $rateLimitData = [time()];
}

// Save rate limit data
file_put_contents($rateLimitFile, json_encode($rateLimitData));

header('Content-Type: application/json');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Access-Control-Allow-Methods: GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');

// Handle preflight requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit(0);
}

// Only allow GET requests
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    http_response_code(405);
    echo json_encode([
        'success' => false,
        'error' => 'Method not allowed. Use GET.'
    ]);
    exit(1);
}

// Get requested check type with explicit validation
$check = $_GET['check'] ?? 'all';
$check = strtolower(trim($check));

// Get detailed parameter (defaults to false for security)
$detailed = isset($_GET['detailed']) && $_GET['detailed'] === '1';

// Parse threshold overrides from URL parameters
$thresholds = parseThresholdOverrides();

// Whitelist validation
$allowed_checks = ['cpu', 'connections', 'disk', 'swap', 'docker', 'haproxy', 'all'];
if (!in_array($check, $allowed_checks, true)) {
    http_response_code(400);
    echo json_encode([
        'success' => false,
        'error' => 'Invalid check parameter. Allowed: ' . implode(', ', $allowed_checks)
    ]);
    exit(1);
}

try {
    switch ($check) {
        case 'cpu':
            $result = getCpuLoad($detailed, $thresholds);
            break;

        case 'connections':
            $result = getConnectionCount($detailed, $thresholds);
            break;

        case 'disk':
            $result = getDiskUsage($detailed, $thresholds);
            break;

        case 'swap':
            $result = getSwapUsage($detailed, $thresholds);
            break;

        case 'docker':
            $result = getDockerStatus($detailed);
            break;

        case 'haproxy':
            $result = getHAProxyHealth($detailed);
            break;

        case 'all':
            $result = getAllHealthChecks($detailed, $thresholds);
            break;

        default:
            http_response_code(400);
            echo json_encode([
                'success' => false,
                'error' => 'Invalid check parameter. Use: cpu, connections, disk, docker, or all'
            ]);
            exit(1);
    }

    http_response_code(200);
    echo json_encode([
        'success' => true,
        'timestamp' => date('c'),
        'data' => $result
    ]);

} catch (Exception $e) {
    error_log("Health Check API Error: " . $e->getMessage());
    http_response_code(500);
    echo json_encode([
        'success' => false,
        'error' => 'Internal server error',
        'timestamp' => date('c')
    ]);
}

exit(0);

/**
 * Parse and validate threshold overrides from URL parameters
 * @return array Validated threshold values
 */
function parseThresholdOverrides() {
    $thresholds = [
        'connections_warning' => HEALTH_CHECK_CONNECTIONS_WARNING_DEFAULT,
        'connections_critical' => HEALTH_CHECK_CONNECTIONS_CRITICAL_DEFAULT,
        'cpu_warning' => HEALTH_CHECK_CPU_WARNING_DEFAULT,
        'cpu_critical' => HEALTH_CHECK_CPU_CRITICAL_DEFAULT,
        'disk_warning' => HEALTH_CHECK_DISK_WARNING_DEFAULT,
        'disk_critical' => HEALTH_CHECK_DISK_CRITICAL_DEFAULT,
        'swap_warning' => HEALTH_CHECK_SWAP_WARNING_DEFAULT,
        'swap_critical' => HEALTH_CHECK_SWAP_CRITICAL_DEFAULT
    ];

    // Connection thresholds (positive integers)
    if (isset($_GET['connections_warning'])) {
        $value = filter_var($_GET['connections_warning'], FILTER_VALIDATE_INT);
        if ($value !== false && $value > 0 && $value < 10000) {
            $thresholds['connections_warning'] = $value;
        }
    }
    if (isset($_GET['connections_critical'])) {
        $value = filter_var($_GET['connections_critical'], FILTER_VALIDATE_INT);
        if ($value !== false && $value > 0 && $value < 10000) {
            $thresholds['connections_critical'] = $value;
        }
    }

    // CPU thresholds (positive floats)
    if (isset($_GET['cpu_warning'])) {
        $value = filter_var($_GET['cpu_warning'], FILTER_VALIDATE_FLOAT);
        if ($value !== false && $value > 0 && $value < 100) {
            $thresholds['cpu_warning'] = $value;
        }
    }
    if (isset($_GET['cpu_critical'])) {
        $value = filter_var($_GET['cpu_critical'], FILTER_VALIDATE_FLOAT);
        if ($value !== false && $value > 0 && $value < 100) {
            $thresholds['cpu_critical'] = $value;
        }
    }

    // Disk thresholds (percentage 1-100)
    if (isset($_GET['disk_warning'])) {
        $value = filter_var($_GET['disk_warning'], FILTER_VALIDATE_INT);
        if ($value !== false && $value > 0 && $value <= 100) {
            $thresholds['disk_warning'] = $value;
        }
    }
    if (isset($_GET['disk_critical'])) {
        $value = filter_var($_GET['disk_critical'], FILTER_VALIDATE_INT);
        if ($value !== false && $value > 0 && $value <= 100) {
            $thresholds['disk_critical'] = $value;
        }
    }

    // Swap thresholds (percentage 1-100)
    if (isset($_GET['swap_warning'])) {
        $value = filter_var($_GET['swap_warning'], FILTER_VALIDATE_INT);
        if ($value !== false && $value > 0 && $value <= 100) {
            $thresholds['swap_warning'] = $value;
        }
    }
    if (isset($_GET['swap_critical'])) {
        $value = filter_var($_GET['swap_critical'], FILTER_VALIDATE_INT);
        if ($value !== false && $value > 0 && $value <= 100) {
            $thresholds['swap_critical'] = $value;
        }
    }

    return $thresholds;
}

/**
 * Get CPU load average
 * @param bool $detailed Whether to include detailed metrics
 * @param array $thresholds Threshold overrides
 * @return array CPU load statistics
 */
function getCpuLoad($detailed = false, $thresholds = []) {
    $loadavg = @file_get_contents('/proc/loadavg');

    if ($loadavg === false) {
        return [
            'status' => 'error',
            'message' => 'Unable to read CPU load'
        ];
    }

    $load = explode(' ', trim($loadavg));
    $cpuCores = intval(@shell_exec('nproc 2>/dev/null') ?: 1);

    $load1min = floatval($load[0]);
    $load5min = floatval($load[1]);
    $load15min = floatval($load[2]);

    // Parse running/total processes
    $processCounts = explode('/', $load[3]);
    $runningProcesses = intval($processCounts[0]);
    $totalProcesses = intval($processCounts[1]);

    // Calculate load per core
    $loadPerCore = $load1min / $cpuCores;

    // Get threshold values
    $warningThreshold = $thresholds['cpu_warning'] ?? HEALTH_CHECK_CPU_WARNING_DEFAULT;
    $criticalThreshold = $thresholds['cpu_critical'] ?? HEALTH_CHECK_CPU_CRITICAL_DEFAULT;

    // Determine status based on load per core
    if ($loadPerCore < $warningThreshold) {
        $status = 'healthy';
    } elseif ($loadPerCore < $criticalThreshold) {
        $status = 'warning';
    } else {
        $status = 'critical';
    }

    // Return only status by default, detailed metrics only if requested
    if (!$detailed) {
        return ['status' => $status];
    }

    return [
        'status' => $status,
        'cpu_cores' => $cpuCores,
        'load_average' => [
            '1_minute' => $load1min,
            '5_minutes' => $load5min,
            '15_minutes' => $load15min
        ],
        'load_per_core' => round($loadPerCore, 2),
        'processes' => [
            'running' => $runningProcesses,
            'total' => $totalProcesses
        ]
    ];
}

/**
 * Get HTTP/HTTPS connection count
 * @param bool $detailed Whether to include detailed metrics
 * @param array $thresholds Threshold overrides
 * @return array Connection statistics for ports 80 and 443
 */
function getConnectionCount($detailed = false, $thresholds = []) {
    // Get unique client connections from HAProxy container
    // This counts unique IP addresses connected to ports 80 and 443
    $output = @shell_exec('docker exec haproxy-manager netstat -plan 2>/dev/null | grep -E "(:443|:80)" | awk -F " " \'{print $5}\' | awk -F ":" \'{print $1}\' | grep -v "0.0.0.0" | grep -v "::" | sort -u | wc -l');

    $uniqueClients = 0;
    if ($output !== null && trim($output) !== '') {
        $uniqueClients = intval(trim($output));
    }

    // Get total connection count for detailed mode
    $totalConnections = 0;
    $connections80 = 0;
    $connections443 = 0;

    if ($detailed) {
        // Count connections on port 80
        $output80 = @shell_exec('docker exec haproxy-manager netstat -plan 2>/dev/null | grep -E ":80 " | wc -l');
        if ($output80 !== null && trim($output80) !== '') {
            $connections80 = intval(trim($output80));
        }

        // Count connections on port 443
        $output443 = @shell_exec('docker exec haproxy-manager netstat -plan 2>/dev/null | grep -E ":443 " | wc -l');
        if ($output443 !== null && trim($output443) !== '') {
            $connections443 = intval(trim($output443));
        }

        $totalConnections = $connections80 + $connections443;
    }

    // Get threshold values
    $warningThreshold = $thresholds['connections_warning'] ?? HEALTH_CHECK_CONNECTIONS_WARNING_DEFAULT;
    $criticalThreshold = $thresholds['connections_critical'] ?? HEALTH_CHECK_CONNECTIONS_CRITICAL_DEFAULT;

    // Determine status based on unique client connections
    // Thresholds are for unique clients, not total connections
    $status = 'healthy';
    if ($uniqueClients >= $criticalThreshold) {
        $status = 'critical';
    } elseif ($uniqueClients >= $warningThreshold) {
        $status = 'warning';
    }

    // Return only status by default, detailed metrics only if requested
    if (!$detailed) {
        return ['status' => $status];
    }

    return [
        'status' => $status,
        'unique_clients' => $uniqueClients,
        'total_connections' => $totalConnections,
        'connections' => [
            'port_80' => $connections80,
            'port_443' => $connections443
        ]
    ];
}

/**
 * Get disk usage statistics
 * @param bool $detailed Whether to include detailed metrics
 * @param array $thresholds Threshold overrides
 * @return array Disk usage information
 */
function getDiskUsage($detailed = false, $thresholds = []) {
    $rootFree = @disk_free_space('/');
    $rootTotal = @disk_total_space('/');
    $dockerFree = @disk_free_space('/docker');
    $dockerTotal = @disk_total_space('/docker');

    // Get threshold values
    $warningThreshold = $thresholds['disk_warning'] ?? HEALTH_CHECK_DISK_WARNING_DEFAULT;
    $criticalThreshold = $thresholds['disk_critical'] ?? HEALTH_CHECK_DISK_CRITICAL_DEFAULT;

    $filesystems = [];
    $overallStatus = 'healthy';

    // Check root filesystem
    if ($rootFree !== false && $rootTotal !== false) {
        $rootUsed = $rootTotal - $rootFree;
        $rootUsagePercent = ($rootUsed / $rootTotal) * 100;

        $rootStatus = 'healthy';
        if ($rootUsagePercent >= $criticalThreshold) {
            $rootStatus = 'critical';
            $overallStatus = 'critical';
        } elseif ($rootUsagePercent >= $warningThreshold) {
            $rootStatus = 'warning';
            if ($overallStatus === 'healthy') {
                $overallStatus = 'warning';
            }
        }

        $filesystems['root'] = [
            'status' => $rootStatus,
            'total_gb' => round($rootTotal / (1024 * 1024 * 1024), 2),
            'used_gb' => round($rootUsed / (1024 * 1024 * 1024), 2),
            'free_gb' => round($rootFree / (1024 * 1024 * 1024), 2),
            'usage_percent' => round($rootUsagePercent, 2)
        ];
    } else {
        $overallStatus = 'error';
    }

    // Check /docker partition if it exists and is a separate mount
    if ($dockerFree !== false && $dockerTotal !== false && $dockerTotal !== $rootTotal) {
        $dockerUsed = $dockerTotal - $dockerFree;
        $dockerUsagePercent = ($dockerUsed / $dockerTotal) * 100;

        $dockerStatus = 'healthy';
        if ($dockerUsagePercent >= $criticalThreshold) {
            $dockerStatus = 'critical';
            $overallStatus = 'critical';
        } elseif ($dockerUsagePercent >= $warningThreshold) {
            $dockerStatus = 'warning';
            if ($overallStatus === 'healthy') {
                $overallStatus = 'warning';
            }
        }

        $filesystems['docker'] = [
            'status' => $dockerStatus,
            'total_gb' => round($dockerTotal / (1024 * 1024 * 1024), 2),
            'used_gb' => round($dockerUsed / (1024 * 1024 * 1024), 2),
            'free_gb' => round($dockerFree / (1024 * 1024 * 1024), 2),
            'usage_percent' => round($dockerUsagePercent, 2)
        ];
    }

    // Return only status by default, detailed metrics only if requested
    if (!$detailed) {
        return ['status' => $overallStatus];
    }

    return [
        'status' => $overallStatus,
        'filesystems' => $filesystems
    ];
}

/**
 * Get swap usage statistics
 * @param bool $detailed Whether to include detailed metrics
 * @param array $thresholds Threshold overrides
 * @return array Swap usage information
 */
function getSwapUsage($detailed = false, $thresholds = []) {
    $meminfo = @file_get_contents('/proc/meminfo');

    if ($meminfo === false) {
        return [
            'status' => 'error',
            'message' => 'Unable to read swap usage'
        ];
    }

    // Parse meminfo for swap data
    preg_match_all('/^(\w+):\s+(\d+)\s+kB$/m', $meminfo, $matches);
    $memory = array_combine($matches[1], $matches[2]);

    $swapTotal = isset($memory['SwapTotal']) ? intval($memory['SwapTotal']) : 0;
    $swapFree = isset($memory['SwapFree']) ? intval($memory['SwapFree']) : 0;

    // If no swap is configured, that's actually healthy
    if ($swapTotal === 0) {
        if (!$detailed) {
            return ['status' => 'healthy'];
        }

        return [
            'status' => 'healthy',
            'swap_configured' => false,
            'message' => 'No swap space configured'
        ];
    }

    $swapUsed = $swapTotal - $swapFree;
    $swapUsagePercent = ($swapUsed / $swapTotal) * 100;

    // Get threshold values
    $warningThreshold = $thresholds['swap_warning'] ?? HEALTH_CHECK_SWAP_WARNING_DEFAULT;
    $criticalThreshold = $thresholds['swap_critical'] ?? HEALTH_CHECK_SWAP_CRITICAL_DEFAULT;

    // Determine status based on swap usage
    // High swap usage indicates memory pressure
    $status = 'healthy';
    if ($swapUsagePercent >= $criticalThreshold) {
        $status = 'critical';
    } elseif ($swapUsagePercent >= $warningThreshold) {
        $status = 'warning';
    }

    // Return only status by default, detailed metrics only if requested
    if (!$detailed) {
        return ['status' => $status];
    }

    return [
        'status' => $status,
        'swap_configured' => true,
        'total_mb' => round($swapTotal / 1024, 2),
        'used_mb' => round($swapUsed / 1024, 2),
        'free_mb' => round($swapFree / 1024, 2),
        'usage_percent' => round($swapUsagePercent, 2)
    ];
}

/**
 * Get Docker daemon status
 * @param bool $detailed Whether to include detailed metrics
 * @return array Docker status information
 */
function getDockerStatus($detailed = false) {
    // Check if Docker daemon is accessible
    $dockerInfo = @shell_exec('docker info 2>/dev/null');

    if ($dockerInfo === null || empty(trim($dockerInfo))) {
        return [
            'status' => 'error',
            'message' => 'Docker daemon not accessible',
            'running' => false
        ];
    }

    // Get container counts
    $totalContainers = 0;
    $runningContainers = 0;

    $psOutput = @shell_exec('docker ps -a --format "{{.Status}}" 2>/dev/null');
    if ($psOutput !== null) {
        $lines = explode("\n", trim($psOutput));
        $totalContainers = count(array_filter($lines));

        foreach ($lines as $line) {
            if (stripos($line, 'Up') === 0) {
                $runningContainers++;
            }
        }
    }

    // Determine status
    if ($runningContainers === 0 && $totalContainers > 0) {
        $status = 'warning'; // Containers exist but none running
    } else {
        $status = 'healthy';
    }

    // Return only status by default, detailed metrics only if requested
    if (!$detailed) {
        return ['status' => $status];
    }

    return [
        'status' => $status,
        'running' => true,
        'containers' => [
            'total' => $totalContainers,
            'running' => $runningContainers,
            'stopped' => $totalContainers - $runningContainers
        ]
    ];
}

/**
 * Get HAProxy container health status
 * @param bool $detailed Whether to include detailed metrics
 * @return array HAProxy health information
 */
function getHAProxyHealth($detailed = false) {
    // Check if container is running
    $running = trim(@shell_exec('docker ps --filter name=haproxy-manager --format "{{.Names}}" 2>/dev/null') ?? '');

    if (empty($running)) {
        return [
            'status' => 'critical',
            'message' => 'HAProxy container is not running'
        ];
    }

    // Get Docker health status
    $healthStatus = trim(@shell_exec('docker inspect --format="{{if .State.Health}}{{.State.Health.Status}}{{end}}" haproxy-manager 2>/dev/null') ?? '');

    // Map Docker health status to our status format
    if ($healthStatus === 'healthy') {
        $status = 'healthy';
    } elseif ($healthStatus === 'unhealthy') {
        $status = 'critical';
    } elseif ($healthStatus === 'starting') {
        $status = 'warning';
    } else {
        // No healthcheck configured or unknown status
        $status = 'healthy';
        $healthStatus = 'no_healthcheck';
    }

    if (!$detailed) {
        return ['status' => $status];
    }

    // Get container uptime
    $containerStatus = trim(@shell_exec('docker ps --filter name=haproxy-manager --format "{{.Status}}" 2>/dev/null') ?? '');

    $result = [
        'status' => $status,
        'container_running' => true,
        'docker_health' => $healthStatus,
        'container_status' => $containerStatus
    ];

    // Get last health check log if unhealthy
    if ($healthStatus === 'unhealthy' || $healthStatus === 'starting') {
        $lastLog = trim(@shell_exec('docker inspect --format="{{if .State.Health}}{{(index .State.Health.Log (sub (len .State.Health.Log) 1)).Output}}{{end}}" haproxy-manager 2>/dev/null') ?? '');
        if (!empty($lastLog)) {
            $result['last_health_log'] = substr($lastLog, 0, 500);
        }
    }

    return $result;
}

/**
 * Get all health checks
 * @param bool $detailed Whether to include detailed metrics
 * @param array $thresholds Threshold overrides
 * @return array Combined health check results
 */
function getAllHealthChecks($detailed = false, $thresholds = []) {
    $cpu = getCpuLoad($detailed, $thresholds);
    $connections = getConnectionCount($detailed, $thresholds);
    $disk = getDiskUsage($detailed, $thresholds);
    $swap = getSwapUsage($detailed, $thresholds);
    $docker = getDockerStatus($detailed);
    $haproxy = getHAProxyHealth($detailed);

    // Determine overall status
    $statuses = [$cpu['status'], $connections['status'], $disk['status'], $swap['status'], $docker['status'], $haproxy['status']];

    if (in_array('critical', $statuses)) {
        $overallStatus = 'critical';
    } elseif (in_array('warning', $statuses)) {
        $overallStatus = 'warning';
    } elseif (in_array('error', $statuses)) {
        $overallStatus = 'error';
    } else {
        $overallStatus = 'healthy';
    }

    // Return only overall status by default
    if (!$detailed) {
        return ['overall_status' => $overallStatus];
    }

    return [
        'overall_status' => $overallStatus,
        'checks' => [
            'cpu' => $cpu,
            'connections' => $connections,
            'disk' => $disk,
            'swap' => $swap,
            'docker' => $docker,
            'haproxy' => $haproxy
        ]
    ];
}
?>
