<?php
// Set content type to JSON
header('Content-Type: application/json');

// Check API permission
check_api_permission('sites');

require_once('/docker/whp/web/libs/mysqlmgmt.php');
require_once('/docker/whp/web/libs/docker-api.php');
require_once('/docker/whp/web/libs/pdns-integration.php');
require_once('/docker/whp/web/libs/valkey-hook.php');

$MySQLMgmt = new mysqlmgmt();
$db = $MySQLMgmt->getMySQLConnection();
if (!$db) {
    http_response_code(500);
    echo json_encode(['status' => 'error', 'message' => 'Database connection failed']);
    exit;
}

$DockerAPI = new docker_api();
$PowerDNS = new PowerDNSIntegration($db);

$method = $_SERVER['REQUEST_METHOD'];

if ($method === 'POST' && isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') === 0) {
    $input = json_decode(file_get_contents('php://input'), true);
    if (is_array($input)) {
        foreach ($input as $k => $v) {
            $_POST[$k] = $v;
        }
    }
}

$action = isset($_GET['action']) ? $_GET['action'] : (isset($_POST['action']) ? $_POST['action'] : '');

// Handle different HTTP methods and actions
switch ($method) {
    case 'GET':
        switch ($action) {
            case 'get_site_details':
                if (!empty($_GET['site_id'])) {
                    $site_id = intval($_GET['site_id']);
                    
                    global $db;
                    try {
                        // Get site details. Derive ssl_enabled from per-domain
                        // haproxy_domains.ssl_enabled rather than reading sites.ssl_enabled
                        // directly — the orchestrator only maintains the per-domain column,
                        // and the legacy ssl_manager-managed sites.ssl_enabled would drift.
                        // MIN(COALESCE(...,0)) means "1 only if every site_domain has a
                        // matching haproxy_domains row with ssl_enabled=1"; a missing row
                        // counts as 0 (which MIN would otherwise skip).
                        $stmt = $db->prepare("
                            SELECT
                                s.*,
                                COALESCE(sd.fqdn, d.domain_name) as primary_domain,
                                ct.name as container_type_name,
                                ct.base_image,
                                ct.listen_port,
                                ct.description as container_type_description,
                                (
                                    SELECT COALESCE(MIN(COALESCE(hd.ssl_enabled, 0)), 0)
                                    FROM whp.site_domains sd2
                                    LEFT JOIN whp.haproxy_domains hd
                                        ON hd.domain_name = COALESCE(sd2.fqdn, '')
                                    WHERE sd2.site_id = s.id
                                ) AS derived_ssl_enabled
                            FROM whp.sites s
                            JOIN whp.domains d ON s.primary_domain_id = d.id
                            LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                            JOIN whp.container_types ct ON s.container_type_id = ct.id
                            WHERE s.id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();
                        
                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Site not found'
                            ));
                            break;
                        }
                        
                        // Check ownership
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Access denied'
                            ));
                            break;
                        }
                        
                        // Get site domains
                        $stmt = $db->prepare("
                            SELECT 
                                sd.*,
                                d.domain_name
                            FROM whp.site_domains sd
                            JOIN whp.domains d ON sd.domain_id = d.id
                            WHERE sd.site_id = ?
                            ORDER BY sd.is_primary DESC, d.domain_name ASC
                        ");
                        $stmt->execute([$site_id]);
                        $site_domains = $stmt->fetchAll();
                        
                        // Build site_fqdns array for JSON response
                        $site_fqdns = [];
                        foreach ($site_domains as $sd) {
                            $site_fqdns[] = [
                                'fqdn' => $sd['fqdn'] ?: $sd['domain_name'], // Use FQDN if available, fallback to domain_name
                                'is_primary' => $sd['is_primary'],
                                'is_wildcard' => !empty($sd['is_wildcard']),
                                'domain' => $sd['domain_name'],
                            ];
                        }

                        // Get DNS challenge info for wildcard sites
                        $dns_challenges = [];
                        if (!empty($site['is_wildcard'])) {
                            $stmt = $db->prepare("
                                SELECT id, domain_name, challenge_fqdn, status, error_message, requested_at, completed_at
                                FROM whp.dns_challenges WHERE site_id = ? ORDER BY requested_at DESC LIMIT 5
                            ");
                            $stmt->execute([$site_id]);
                            $dns_challenges = $stmt->fetchAll(PDO::FETCH_ASSOC);
                        }
                        
                        // Get site containers
                        $stmt = $db->prepare("
                            SELECT *
                            FROM whp.site_containers
                            WHERE site_id = ?
                            ORDER BY container_number ASC
                        ");
                        $stmt->execute([$site_id]);
                        $site_containers = $stmt->fetchAll();
                        
                        // Get Docker stats for containers
                        $container_stats = [];
                        if (!empty($site_containers)) {
                            $container_ids = array_column($site_containers, 'container_id');
                            $ids_string = implode(' ', array_map('escapeshellarg', $container_ids));
                            $stats_command = "docker stats --no-stream --format '{\"name\":\"{{.Container}}\",\"cpu\":\"{{.CPUPerc}}\",\"memory\":\"{{.MemUsage}}\",\"memory_pct\":\"{{.MemPerc}}\"}' " . $ids_string;
                            $stats_output = shell_exec($stats_command);
                            
                            if ($stats_output) {
                                $stats_lines = explode("\n", trim($stats_output));
                                foreach ($stats_lines as $line) {
                                    if (!empty($line)) {
                                        $stat = json_decode($line, true);
                                        if ($stat) {
                                            $container_stats[$stat['name']] = $stat;
                                        }
                                    }
                                }
                            }
                        }
                        
                        // Get available domains for this user
                        $stmt = $db->prepare("
                            SELECT d.*
                            FROM whp.domains d
                            WHERE d.username = ? AND d.active = 1
                            AND d.id NOT IN (
                                SELECT domain_id FROM whp.site_domains WHERE site_id = ?
                            )
                            ORDER BY d.domain_name ASC
                        ");
                        $stmt->execute([$site['username'], $site_id]);
                        $available_domains = $stmt->fetchAll();
                        
                        // Generate HTML for modal
                        $html = '<div class="row">';
                        
                        // Container Status (move to top, make compact)
                        $html .= '<div class="col-12">';
                        $html .= '<div class="site-section-header" style="margin-top:0;">Container Status</div>';
                        if (empty($site_containers)) {
                            $html .= '<div class="alert alert-info p-2 m-0" style="font-size:0.95rem;max-width:320px;">No containers created yet</div>';
                        } else {
                            $html .= '<div class="list-group list-group-flush" style="max-width:500px;">';
                            foreach ($site_containers as $container) {
                                $status_class = ($container['status'] === 'running') ? 'success' : 'secondary';
                                // Generate container name from primary domain and container number (same as Docker naming)
                                $container_name = $site['primary_domain'] . '-' . str_pad($container['container_number'], 2, '0', STR_PAD_LEFT);
                                
                                $html .= '<div class="list-group-item py-2 px-3">';
                                $html .= '<div class="d-flex justify-content-between align-items-center mb-1">';
                                $html .= '<span style="font-size:0.95rem; font-weight:500;">' . htmlspecialchars($container_name) . '</span>';
                                $html .= '<span class="badge bg-' . $status_class . '" style="font-size:0.95rem;">' . ucfirst($container['status']) . '</span>';
                                $html .= '</div>';
                                
                                // Add container stats if available and active/running
                                if (($container['status'] === 'running') && isset($container_stats[$container['container_id']])) {
                                    $stats = $container_stats[$container['container_id']];
                                    $html .= '<div class="row g-2 mt-2">';
                                    $html .= '<div class="col-6">';
                                    $html .= '<div class="d-flex align-items-center">';
                                    $html .= '<i class="fa fa-microchip text-primary me-1"></i>';
                                    $html .= '<small style="color: #212529 !important; font-weight: 700 !important;">CPU:</small>';
                                    $html .= '<span class="badge bg-primary ms-1" style="font-size:0.8rem;">' . htmlspecialchars($stats['cpu']) . '</span>';
                                    $html .= '</div>';
                                    $html .= '</div>';
                                    $html .= '<div class="col-6">';
                                    $html .= '<div class="d-flex align-items-center">';
                                    $html .= '<i class="fa fa-memory text-info me-1"></i>';
                                    $html .= '<small style="color: #212529 !important; font-weight: 700 !important;">Memory:</small>';
                                    $html .= '<span class="badge bg-info ms-1" style="font-size:0.8rem;">' . htmlspecialchars($stats['memory_pct']) . '</span>';
                                    $html .= '</div>';
                                    $html .= '</div>';
                                    $html .= '</div>';
                                } elseif ($container['status'] !== 'running') {
                                    $html .= '<div class="text-muted mt-1" style="font-size:0.85rem;">Container not running</div>';
                                }
                                
                                $html .= '</div>';
                            }
                            $html .= '</div>';
                        }
                        $html .= '</div>';
                        
                        // Site Information (now below container status)
                        $html .= '<div class="col-md-6">';
                        $html .= '<div class="site-section-header">Site Information</div>';
                        $html .= '<table class="table table-sm">';
                        $html .= '<tr><td><strong>Name:</strong></td><td>' . htmlspecialchars($site['site_name']) . '</td></tr>';
                        $html .= '<tr><td><strong>Primary Domain:</strong></td><td>' . htmlspecialchars($site['primary_domain']) . '</td></tr>';
                        $html .= '<tr><td><strong>Container Type:</strong></td><td>' . htmlspecialchars($site['container_type_name']) . '</td></tr>';
                        $html .= '<tr><td><strong>Container Count:</strong></td><td>' . $site['container_count'] . '</td></tr>';
                        $html .= '<tr><td><strong>CPU per Container:</strong></td><td>' . $site['cpu_per_container'] . ' cores</td></tr>';
                        $html .= '<tr><td><strong>Memory per Container:</strong></td><td>' . $site['memory_per_container'] . ' MB</td></tr>';
                        $html .= '<tr><td><strong>Status:</strong></td><td>' . ($site['active'] ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Inactive</span>') . '</td></tr>';
                        $html .= '</table>';
                        $html .= '</div>';
                        
                        // Domains Section — now includes per-domain SSL renewal state badge
                        // (renewal_state, last_renewal_error from haproxy_domains).
                        $domain_names_for_ssl = [];
                        foreach ($site_domains as $sd) {
                            $domain_names_for_ssl[] = $sd['fqdn'] ?: $sd['domain_name'];
                        }
                        $ssl_state_by_domain = [];
                        if (!empty($domain_names_for_ssl)) {
                            $placeholders = implode(',', array_fill(0, count($domain_names_for_ssl), '?'));
                            $stmt_ssl = $db->prepare("
                                SELECT domain_name, renewal_state, last_renewal_error,
                                       last_renewal_attempt_at, next_renewal_attempt_at,
                                       ssl_last_renewal, dns_validation
                                FROM whp.haproxy_domains
                                WHERE domain_name IN ($placeholders)
                            ");
                            $stmt_ssl->execute($domain_names_for_ssl);
                            foreach ($stmt_ssl->fetchAll(PDO::FETCH_ASSOC) as $r) {
                                $ssl_state_by_domain[$r['domain_name']] = $r;
                            }
                        }

                        // Badge mapping. "pending" splits into two visuals depending on
                        // whether SSL has *ever* been issued for the domain — a never-set-up
                        // cert isn't a failure and shouldn't look alarming.
                        $ssl_badge = function ($state, $ssl_info = null) {
                            $never_issued = $state === 'pending'
                                && empty($ssl_info['ssl_last_renewal']);
                            $effective_state = $never_issued ? 'not_enabled' : $state;

                            $map = [
                                'ok' => ['success', 'fa-lock', 'SSL OK'],
                                'not_enabled' => ['light text-dark border', 'fa-unlock', 'Not enabled'],
                                'pending' => ['secondary', 'fa-clock-o', 'Renewal queued'],
                                'transient_failure' => ['warning', 'fa-refresh', 'SSL retry scheduled'],
                                'unreachable' => ['warning', 'fa-exclamation-triangle', 'Domain unreachable'],
                                'manual_review' => ['danger', 'fa-times-circle', 'SSL needs attention'],
                                'deferred' => ['secondary', 'fa-pause', 'SSL deferred'],
                            ];
                            $entry = $map[$effective_state] ?? ['secondary', 'fa-question', 'No SSL'];
                            return '<span class="badge bg-' . $entry[0] . ' ms-2" title="' . htmlspecialchars($entry[2]) . '">'
                                 . '<i class="fa ' . $entry[1] . '"></i> ' . htmlspecialchars($entry[2])
                                 . '</span>';
                        };

                        $html .= '<div class="row mt-3">';
                        $html .= '<div class="col-12">';
                        $html .= '<div class="site-section-header">Associated Domains</div>';
                        $html .= '<div class="list-group">';
                        foreach ($site_domains as $site_domain) {
                            $display_fqdn = $site_domain['fqdn'] ?: $site_domain['domain_name'];
                            $ssl_info = $ssl_state_by_domain[$display_fqdn] ?? null;
                            $state = $ssl_info['renewal_state'] ?? 'pending';

                            $html .= '<div class="list-group-item">';
                            $html .= '<div class="d-flex justify-content-between align-items-center">';
                            $html .= '<span>';
                            $html .= '<a href="https://' . htmlspecialchars($display_fqdn) . '" target="_blank" class="text-decoration-none">';
                            $html .= htmlspecialchars($display_fqdn);
                            $html .= '</a>';
                            if ($site_domain['is_primary']) {
                                $html .= ' <span class="badge bg-primary ms-2">Primary</span>';
                            }
                            $html .= $ssl_badge($state, $ssl_info);
                            $html .= '</span>';

                            // Action button: "Issue SSL" for never-set-up rows,
                            // "Retry SSL" when an issuance has previously failed.
                            $never_issued = $state === 'pending' && empty($ssl_info['ssl_last_renewal']);
                            if ($never_issued) {
                                $html .= '<button type="button" class="btn btn-sm btn-outline-primary ssl-retry-btn"'
                                       . ' data-domain="' . htmlspecialchars($display_fqdn) . '"'
                                       . '><i class="fa fa-lock"></i> Issue SSL</button>';
                            } elseif (in_array($state, ['unreachable', 'manual_review', 'transient_failure'], true)) {
                                $html .= '<button type="button" class="btn btn-sm btn-outline-primary ssl-retry-btn"'
                                       . ' data-domain="' . htmlspecialchars($display_fqdn) . '"'
                                       . '><i class="fa fa-refresh"></i> Retry SSL</button>';
                            }
                            $html .= '</div>';

                            // Show last error in a small expandable note when present
                            if (!empty($ssl_info['last_renewal_error'])) {
                                $html .= '<div class="small text-muted mt-1" style="word-break:break-word;">'
                                       . '<strong>Last attempt:</strong> '
                                       . htmlspecialchars($ssl_info['last_renewal_attempt_at'] ?? 'unknown')
                                       . '<br><strong>Error:</strong> '
                                       . htmlspecialchars($ssl_info['last_renewal_error']);
                                if (!empty($ssl_info['next_renewal_attempt_at'])) {
                                    $html .= '<br><strong>Next retry:</strong> ' . htmlspecialchars($ssl_info['next_renewal_attempt_at']);
                                }
                                $html .= '</div>';
                            }
                            $html .= '</div>';
                        }
                        $html .= '</div>';
                        $html .= '</div>';
                        $html .= '</div>';
                        // (Removed Add Domain to Site form)
                        
                        // For shared_httpd sites, map the FPM type back to the standalone
                        // type for display (users see "PHP 8.3", not "PHP-FPM 8.3")
                        $display_type_id = $site['container_type_id'];
                        $display_type_name = $site['container_type_name'];
                        if (($site['container_mode'] ?? 'standalone') === 'shared_httpd') {
                            require_once('/docker/whp/web/libs/shared_httpd_manager.php');
                            $standalone_name = shared_httpd_manager::mapToStandaloneType($site['container_type_name']);
                            if ($standalone_name) {
                                $display_type_name = $standalone_name;
                                $stmt_type = $db->prepare("SELECT id FROM whp.container_types WHERE name = ? AND active = 1");
                                $stmt_type->execute([$standalone_name]);
                                $standalone_row = $stmt_type->fetch();
                                if ($standalone_row) {
                                    $display_type_id = $standalone_row['id'];
                                }
                            }
                        }

                        echo json_encode(array(
                            'status' => 'success',
                            'html' => $html,
                            'site' => array(
                                'id' => $site['id'],
                                'site_name' => $site['site_name'],
                                'primary_domain_id' => $site['primary_domain_id'],
                                'primary_domain' => $site['primary_domain'],
                                'container_type_id' => $display_type_id,
                                'container_type_name' => $display_type_name,
                                'container_count' => $site['container_count'],
                                'cpu_per_container' => $site['cpu_per_container'],
                                'memory_per_container' => $site['memory_per_container'],
                                'user_variables' => $site['user_variables'],
                                'active' => $site['active'],
                                'username' => $site['username'],
                                'auto_recreate_on_update' => $site['auto_recreate_on_update'],
                                'container_mode' => $site['container_mode'] ?? 'standalone',
                                'is_wildcard' => !empty($site['is_wildcard']),
                                'ssl_enabled' => intval($site['derived_ssl_enabled'] ?? 0),
                                'fqdns' => $site_fqdns,
                                'dns_challenges' => $dns_challenges
                            )
                        ));
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;
                
            case 'get_user_limits':
                global $db;
                try {
                    $stmt = $db->prepare("SELECT * FROM whp.client_allowances WHERE username = ?");
                    $stmt->execute([AUTH_USER]);
                    $limits = $stmt->fetch();
                    
                    echo json_encode(array(
                        'status' => 'success',
                        'data' => $limits ?: array()
                    ));
                } catch (PDOException $e) {
                    http_response_code(500);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Database error: ' . $e->getMessage()
                    ));
                }
                break;
                
            default:
                http_response_code(400);
                echo json_encode(array(
                    'status' => 'error',
                    'message' => 'Invalid action'
                ));
                break;
        }
        break;
        
    case 'POST':
        $input = json_decode(file_get_contents('php://input'), true);
        if (!$input) {
            $input = $_POST;
        }
        
        switch ($action) {
            case 'create_site':
                // Site creation with original working logic
                if (!empty($input['site_name']) && !empty($input['primary_domain_id']) && !empty($input['container_type_id'])) {
                    $site_name = trim($input['site_name']);
                    $primary_fqdn = trim($input['primary_domain_id']);
                    $container_type_id = intval($input['container_type_id']);
                    $container_count = max(1, min(10, intval($input['container_count'] ?? 1)));
                    $cpu_per_container = floatval($input['cpu_per_container'] ?? 0.25);
                    $memory_per_container = intval($input['memory_per_container'] ?? 256);
                    $auto_recreate_on_update = !empty($input['auto_recreate_on_update']) ? 1 : 0;
                    $is_wildcard_site = str_starts_with($primary_fqdn, '*.');

                    // Container mode: root-only toggle for shared httpd
                    // Determine container mode: explicit value > server default
                    if (!empty($input['container_mode']) && $input['container_mode'] === 'shared_httpd') {
                        $container_mode = 'shared_httpd';
                    } elseif (!empty($input['container_mode']) && $input['container_mode'] === 'standalone') {
                        $container_mode = 'standalone';
                    } else {
                        // No explicit mode — use server default from settings.json
                        $container_mode = 'standalone';
                        if (file_exists('/docker/whp/settings.json')) {
                            $whp_settings = json_decode(file_get_contents('/docker/whp/settings.json'), true);
                            if (($whp_settings['shared_httpd']['default_mode'] ?? '') === 'shared_httpd') {
                                $container_mode = 'shared_httpd';
                            }
                        }
                    }

                    // Compute safe FQDN for directory and container names (no * in filesystem paths)
                    $safe_fqdn = $is_wildcard_site ? str_replace('*.', 'wildcard.', $primary_fqdn) : $primary_fqdn;

                    // Collect user variables
                    $user_variables = [];
                    if (!empty($input['user_variables'])) {
                        $user_variables = json_decode($input['user_variables'], true) ?: [];
                    }

                    try {
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');

                        // Extract the apex domain from the FQDN
                        // For example: knowledgebase.intranet.brainjar.net -> brainjar.net
                        $domain_parts = explode('.', $primary_fqdn);
                        $apex_domain = null;
                        
                        // Try to find the apex domain by checking progressively shorter domain combinations
                        for ($i = 0; $i < count($domain_parts) - 1; $i++) {
                            $potential_domain = implode('.', array_slice($domain_parts, $i));
                            $stmt = $db->prepare("SELECT * FROM whp.domains WHERE domain_name = ? AND (username = ? OR ? = 1)");
                            $stmt->execute([$potential_domain, AUTH_USER, $is_root ? 1 : 0]);
                            $domain_row = $stmt->fetch(PDO::FETCH_ASSOC);
                            if ($domain_row) {
                                $apex_domain = $potential_domain;
                                $primary_domain_row = $domain_row;
                                break;
                            }
                        }

                        if (!$apex_domain || !$primary_domain_row) {
                            http_response_code(403);
                            echo json_encode(['success' => false, 'error' => 'Domain not found or access denied']);
                            break;
                        }

                        // DNS validation (using getRecords method)
                        // Wildcard sites (*.domain.tld) skip A/CNAME validation - they use the apex domain
                        $valid_fqdn = false;
                        if ($is_wildcard_site) {
                            // For wildcard sites, just verify the apex domain exists in our system
                            $valid_fqdn = true;
                        } else {
                            // Use the apex domain we found to get DNS records
                            $fqdn_records = $PowerDNS->getRecords($apex_domain);
                            foreach ($fqdn_records as $rec) {
                                if (strcasecmp($rec['name'], $primary_fqdn) === 0 && in_array(strtoupper($rec['type']), ['A','CNAME'])) {
                                    $valid_fqdn = true;
                                    break;
                                }
                            }
                        }
                        if (!$valid_fqdn) {
                            http_response_code(400);
                            echo json_encode(['success' => false, 'error' => 'DNS validation failed: No A or CNAME record found for ' . $primary_fqdn]);
                            break;
                        }

                        // Container type validation
                        $stmt = $db->prepare("SELECT * FROM whp.container_types WHERE id = ? AND active = 1");
                        $stmt->execute([$container_type_id]);
                        $container_type = $stmt->fetch(PDO::FETCH_ASSOC);

                        if (!$container_type) {
                            http_response_code(400);
                            echo json_encode(['status' => 'error', 'message' => 'Invalid container type']);
                            break;
                        }

                        // Shared httpd mode: validate and map container type
                        if ($container_mode === 'shared_httpd') {
                            // Only root can set shared_httpd mode
                            if (!$is_root) {
                                http_response_code(403);
                                echo json_encode(['status' => 'error', 'message' => 'Only root can enable shared httpd mode']);
                                break;
                            }

                            // Only PHP types support shared httpd mode
                            require_once('/docker/whp/web/libs/shared_httpd_manager.php');
                            if (!shared_httpd_manager::isPhpType($container_type['name'])) {
                                http_response_code(400);
                                echo json_encode(['status' => 'error', 'message' => 'Shared httpd mode is only available for PHP container types']);
                                break;
                            }

                            // Map standalone PHP type to FPM equivalent (e.g., "PHP 8.3" -> "PHP-FPM 8.3")
                            $fpm_type_name = shared_httpd_manager::mapToFpmType($container_type['name']);
                            if ($fpm_type_name) {
                                $stmt = $db->prepare("SELECT * FROM whp.container_types WHERE name = ? AND active = 1");
                                $stmt->execute([$fpm_type_name]);
                                $fpm_type = $stmt->fetch(PDO::FETCH_ASSOC);
                                if ($fpm_type) {
                                    $container_type = $fpm_type;
                                    $container_type_id = $fpm_type['id'];
                                } else {
                                    http_response_code(400);
                                    echo json_encode(['status' => 'error', 'message' => 'PHP-FPM container type not found: ' . $fpm_type_name]);
                                    break;
                                }
                            }
                        }

                        // Resource validation
                        if ($cpu_per_container < $container_type['min_cpu'] || $memory_per_container < $container_type['min_memory']) {
                            http_response_code(400);
                            echo json_encode(['status' => 'error', 'message' => 'Resources below container type minimums']);
                            break;
                        }

                        // Determine the actual site owner (use domain owner, not the user creating the site)
                        $site_owner = $primary_domain_row['username'];

                        // Check user resource limits before creating the site
                        // Skip this check for root users
                        if ($site_owner !== 'root') {
                            require_once('/docker/whp/web/libs/mysqlmgmt.php');
                            $MySQLMgmt = new mysqlmgmt();
                            
                            // Get user's resource limits
                            $stmt = $db->prepare("SELECT max_cpu_allowance, max_memory_allowance FROM whp.client_allowances WHERE username = ?");
                            $stmt->execute([$site_owner]);
                            $user_limits = $stmt->fetch();
                            
                            if ($user_limits) {
                                // Get current resource usage for the user
                                $usage = $MySQLMgmt->getUserResourceUsage($site_owner);
                                $current_cpu_used = floatval($usage['cpu_used'] ?? 0);
                                $current_memory_used = intval($usage['memory_used'] ?? 0);
                                
                                // Calculate resources needed for the new site
                                $new_site_cpu = $cpu_per_container * $container_count;
                                $new_site_memory = $memory_per_container * $container_count;
                                
                                // Calculate projected totals
                                $projected_cpu = $current_cpu_used + $new_site_cpu;
                                $projected_memory = $current_memory_used + $new_site_memory;
                                
                                // Debug logging
                                error_log("=== CREATE SITE RESOURCE CHECK ===");
                                error_log("User: $site_owner");
                                error_log("User limits: {$user_limits['max_cpu_allowance']} CPU cores, {$user_limits['max_memory_allowance']} MB RAM");
                                error_log("Current usage: $current_cpu_used CPU cores, $current_memory_used MB RAM");
                                error_log("New site requirements: $new_site_cpu CPU cores, $new_site_memory MB RAM");
                                error_log("Projected total: $projected_cpu CPU cores, $projected_memory MB RAM");
                                error_log("=== END RESOURCE CHECK ===");
                                
                                // Check if user would exceed CPU limits
                                if ($projected_cpu > $user_limits['max_cpu_allowance']) {
                                    http_response_code(400);
                                    echo json_encode([
                                        'status' => 'error', 
                                        'message' => 'Creating this site would exceed your CPU limit. ' .
                                                    'Current usage: ' . $current_cpu_used . ' cores, ' .
                                                    'New site needs: ' . $new_site_cpu . ' cores, ' .
                                                    'Your limit: ' . $user_limits['max_cpu_allowance'] . ' cores'
                                    ]);
                                    break;
                                }
                                
                                // Check if user would exceed memory limits
                                if ($projected_memory > $user_limits['max_memory_allowance']) {
                                    http_response_code(400);
                                    echo json_encode([
                                        'status' => 'error', 
                                        'message' => 'Creating this site would exceed your memory limit. ' .
                                                    'Current usage: ' . $current_memory_used . ' MB, ' .
                                                    'New site needs: ' . $new_site_memory . ' MB, ' .
                                                    'Your limit: ' . $user_limits['max_memory_allowance'] . ' MB'
                                    ]);
                                    break;
                                }
                            } else {
                                // No limits defined for user - log warning but allow creation
                                error_log("WARNING: No resource limits defined for user $site_owner");
                            }
                        }

                        // File system preparation (use safe FQDN - no * in filesystem paths)
                        $domain_dir = '/docker/users/' . $site_owner . '/' . $safe_fqdn;
                        if (!file_exists($domain_dir)) {
                            if (!mkdir($domain_dir, 0755, true)) {
                                http_response_code(500);
                                echo json_encode(['status' => 'error', 'message' => 'Failed to create directory']);
                                break;
                            }
                            
                            // Set proper ownership
                            $uid = trim(shell_exec("id -u " . escapeshellarg($site_owner)));
                            $gid = trim(shell_exec("id -g " . escapeshellarg($site_owner)));
                            if (is_numeric($uid) && is_numeric($gid)) {
                                chown($domain_dir, intval($uid));
                                chgrp($domain_dir, intval($gid));
                            }
                        }

                        // Database site record creation
                        $stmt = $db->prepare("
                            INSERT INTO whp.sites (site_name, primary_domain_id, username, container_type_id, container_mode, container_count,
                                                  cpu_per_container, memory_per_container, user_variables, auto_recreate_on_update, is_wildcard)
                            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        ");
                        $stmt->execute([
                            $site_name,
                            $primary_domain_row['id'],
                            $site_owner,
                            $container_type_id,
                            $container_mode,
                            $container_count,
                            $cpu_per_container,
                            $memory_per_container,
                            json_encode($user_variables),
                            $auto_recreate_on_update,
                            $is_wildcard_site ? 1 : 0
                        ]);
                        
                        $site_id = $db->lastInsertId();

                        // Link primary domain to site
                        $stmt = $db->prepare("INSERT INTO whp.site_domains (site_id, domain_id, fqdn, is_primary, is_wildcard) VALUES (?, ?, ?, 1, ?)");
                        $stmt->execute([$site_id, $primary_domain_row['id'], $primary_fqdn, $is_wildcard_site ? 1 : 0]);

                        // Process additional domains (using original working logic)
                        $additional_domains_added = [];
                        if (!empty($input['additional_domains']) && is_array($input['additional_domains'])) {
                            foreach ($input['additional_domains'] as $additional_fqdn) {
                                $additional_fqdn = trim($additional_fqdn);
                                if (empty($additional_fqdn)) continue;

                                // Find parent domain
                                $parts = explode('.', $additional_fqdn);
                                $potential_domains = [];
                                for ($i = 0; $i < count($parts); $i++) {
                                    $potential_domains[] = implode('.', array_slice($parts, $i));
                                }

                                $parent_row = null;
                                foreach ($potential_domains as $potential_domain) {
                                    $stmt = $db->prepare("SELECT * FROM whp.domains WHERE domain_name = ? AND (username = ? OR ? = 1)");
                                    $stmt->execute([$potential_domain, AUTH_USER, $is_root ? 1 : 0]);
                                    $parent_row = $stmt->fetch(PDO::FETCH_ASSOC);
                                    if ($parent_row) break;
                                }

                                if ($parent_row) {
                                    // Validate FQDN has valid A or CNAME record
                                    $valid_fqdn = false;
                                    $fqdn_records = $PowerDNS->getRecords($parent_row['domain_name']);
                                    foreach ($fqdn_records as $rec) {
                                        if (strcasecmp($rec['name'], $additional_fqdn) === 0 && in_array(strtoupper($rec['type']), ['A','CNAME'])) {
                                            $valid_fqdn = true;
                                            break;
                                        }
                                    }
                                    
                                    if ($valid_fqdn) {
                                        // Check if not already linked
                                        $stmt = $db->prepare("SELECT id FROM whp.site_domains WHERE site_id = ? AND domain_id = ? AND fqdn = ?");
                                        $stmt->execute([$site_id, $parent_row['id'], $additional_fqdn]);
                                        
                                        if (!$stmt->fetch()) {
                                            $stmt = $db->prepare("INSERT INTO whp.site_domains (site_id, domain_id, fqdn, is_primary) VALUES (?, ?, ?, 0)");
                                            $stmt->execute([$site_id, $parent_row['id'], $additional_fqdn]);
                                            $additional_domains_added[] = $additional_fqdn;
                                        }
                                    }
                                }
                            }
                        }

                        // Container creation
                        $container_creation_success = true;
                        $container_errors = [];
                        $created_containers = [];

                        // Get user UID (use site owner, not the user creating the site)
                        $uid = trim(shell_exec("id -u " . escapeshellarg($site_owner)));
                        if (!is_numeric($uid)) {
                            throw new Exception('Invalid user UID');
                        }

                        for ($i = 1; $i <= $container_count; $i++) {
                            // Use safe FQDN for container name (no * allowed in Docker names)
                            $container_name = $safe_fqdn . '-' . str_pad($i, 2, '0', STR_PAD_LEFT);

                            // Environment variables (WHP_DOMAIN keeps the original FQDN with *)
                            $env_vars = [
                                'WHP_USER=' . $site_owner,
                                'WHP_UID=' . $uid,
                                'WHP_DOMAIN=' . $primary_fqdn,
                                'WHP_CONTAINER_NUMBER=' . $i
                            ];

                            // Add user variables
                            foreach ($user_variables as $var_name => $var_value) {
                                $env_vars[] = $var_name . '=' . $var_value;
                            }

                            // Process startup environment variables
                            if (!empty($container_type['startup_env'])) {
                                $startup_env = json_decode($container_type['startup_env'], true);
                                if ($startup_env) {
                                    foreach ($startup_env as $var_name => $var_value) {
                                        $var_value = $MySQLMgmt->replaceDynamicVariables($var_value, $site_owner, $primary_fqdn, $uid, $i, $container_type['listen_port'] ?? 80);
                                        $env_vars[] = $var_name . '=' . $var_value;
                                    }
                                }
                            }

                            // Prepare volumes - only use mount_options from container type
                            $volumes = [];

                            // Process mount options (use safe FQDN for filesystem paths - no * in paths)
                            if (!empty($container_type['mount_options'])) {
                                $mount_options = json_decode($container_type['mount_options'], true);
                                if ($mount_options && isset($mount_options['volumes'])) {
                                    foreach ($mount_options['volumes'] as $volume) {
                                        $source = $MySQLMgmt->replaceDynamicVariables($volume['source'], $site_owner, $safe_fqdn, $uid, $i, $container_type['listen_port'] ?? 80);
                                        $target = $MySQLMgmt->replaceDynamicVariables($volume['target'], $site_owner, $safe_fqdn, $uid, $i, $container_type['listen_port'] ?? 80);
                                        $volumes[] = $source . ':' . $target;
                                    }
                                }
                            }

                            // Container configuration
                            $container_config = [
                                'image' => $container_type['base_image'],
                                'name' => $container_name,
                                'labels' => [
                                    'whp.user' => $site_owner,
                                    'whp.domain' => $primary_fqdn,
                                    'whp.site_id' => strval($site_id),
                                    'whp.container_number' => strval($i)
                                ],
                                'env' => $env_vars,
                                'volumes' => $volumes,
                                'cpu' => $cpu_per_container,
                                'memory' => $memory_per_container,
                                'network' => 'client-net'
                            ];

                            $result = $DockerAPI->create_container($container_config);

                            if ($result['success']) {
                                $stmt = $db->prepare("INSERT INTO whp.site_containers (site_id, container_id, container_number, status) VALUES (?, ?, ?, 'running')");
                                $stmt->execute([$site_id, $result['container_id'], $i]);
                                $created_containers[] = $result['container_id'];
                                error_log("Container created successfully: $container_name (ID: {$result['container_id']})");
                                @whp_valkey_attach_if_enabled($db, $site_owner, $container_name);
                            } else {
                                $container_creation_success = false;
                                $error_msg = "Container $i: " . ($result['error'] ?? 'Unknown error');
                                $container_errors[] = $error_msg;
                                error_log("=== CONTAINER CREATION FAILED ===");
                                error_log("Container name: $container_name");
                                error_log("Error: " . ($result['error'] ?? 'Unknown error'));
                                error_log("Full result: " . json_encode($result));
                                error_log("Container config: " . json_encode($container_config));
                                error_log("=== END CONTAINER CREATION ERROR ===");
                            }
                        }

                        // Configure shared httpd vhost if in shared_httpd mode
                        if ($container_creation_success && $container_mode === 'shared_httpd') {
                            require_once('/docker/whp/web/libs/shared_httpd_manager.php');
                            $shared_httpd = new shared_httpd_manager();

                            // Build list of all FPM container names for load balancing
                            $fpm_containers = [];
                            for ($ci = 1; $ci <= $container_count; $ci++) {
                                $fpm_containers[] = $safe_fqdn . '-' . str_pad($ci, 2, '0', STR_PAD_LEFT);
                            }

                            // Collect aliases from additional domains
                            $alias_domains = [];
                            if (!empty($additional_domains_added)) {
                                $alias_domains = $additional_domains_added;
                            }

                            $vhost_result = $shared_httpd->addVhost($primary_fqdn, $site_owner, $fpm_containers, 9000, $alias_domains);
                            if (!$vhost_result['success']) {
                                $container_errors[] = "Shared httpd vhost config failed: " . $vhost_result['error'];
                            } else {
                                $reload_result = $shared_httpd->reloadHttpd();
                                if (!$reload_result['success']) {
                                    $container_errors[] = "Shared httpd reload failed";
                                }
                            }
                        }

                        // Configure HAProxy if containers were created successfully
                        if ($container_creation_success) {
                            require_once('/docker/whp/web/libs/haproxy_manager.php');
                            $haproxy_manager = new haproxy_manager();
                            $haproxy_result = $haproxy_manager->configureSite($site_id, [
                                'username' => $site_owner,
                                'primary_domain' => $primary_domain_row['domain_name'],
                                'site_name' => $site_name,
                                'container_count' => $container_count
                            ]);

                            if (!$haproxy_result['success']) {
                                $container_errors[] = "HAProxy configuration failed: " . $haproxy_result['error'];
                            }
                        }

                        // Prepare response (using original working format)
                        $response = [
                            'success' => $container_creation_success,
                            'site_id' => $site_id,
                            'container_count' => $container_count,
                            'created_containers' => $created_containers
                        ];

                        if ($container_creation_success) {
                            $domain_info = '';
                            if (!empty($additional_domains_added)) {
                                $domain_info = ' with additional domains: ' . implode(', ', $additional_domains_added);
                            }
                            $response['message'] = 'Site created successfully with ' . $container_count . ' container(s) started and HAProxy configured: ' . $site_name . $domain_info;
                        } else {
                            $response['success'] = false;
                            $response['message'] = 'Site created but some containers failed to start';
                            $response['errors'] = $container_errors;
                        }

                        echo json_encode($response);

                    } catch (Exception $e) {
                        error_log("Site creation error: " . $e->getMessage());
                        http_response_code(500);
                        echo json_encode([
                            'success' => false,
                            'error' => 'Site creation failed: ' . $e->getMessage()
                        ]);
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(['success' => false, 'error' => 'Missing required fields for create_site']);
                }
                break;

            case 'create_containers':
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);
                    
                    global $db;
                    try {
                        // Get site details
                        $stmt = $db->prepare("
                            SELECT 
                                s.*,
                                COALESCE(sd.fqdn, d.domain_name) as primary_domain,
                                ct.base_image,
                                ct.startup_env,
                                ct.mount_options
                            FROM whp.sites s
                            JOIN whp.domains d ON s.primary_domain_id = d.id
                            LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                            JOIN whp.container_types ct ON s.container_type_id = ct.id
                            WHERE s.id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();
                        
                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Site not found'
                            ));
                            break;
                        }
                        
                        // Check ownership
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Access denied'
                            ));
                            break;
                        }
                        
                        // Get user UID
                        $uid = trim(shell_exec("id -u " . escapeshellarg($site['username'])));
                        if (!is_numeric($uid)) {
                            http_response_code(400);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Invalid user'
                            ));
                            break;
                        }
                        
                        // primary_domain now contains the FQDN
                        
                        $created_containers = [];
                        $errors = [];
                        
                        // Create containers
                        for ($i = 1; $i <= $site['container_count']; $i++) {
                            // Check if container already exists
                            $stmt = $db->prepare("SELECT container_id FROM whp.site_containers WHERE site_id = ? AND container_number = ?");
                            $stmt->execute([$site_id, $i]);
                            if ($stmt->fetch()) {
                                $errors[] = "Container $i already exists";
                                continue;
                            }
                            
                            // Create container name using FQDN-based naming
                            // Sanitize wildcard FQDNs for Docker container names (no * allowed)
                            $safe_primary = str_starts_with($site['primary_domain'], '*.')
                                ? str_replace('*.', 'wildcard.', $site['primary_domain'])
                                : $site['primary_domain'];
                            $container_name = $safe_primary . '-' . str_pad($i, 2, '0', STR_PAD_LEFT);
                            
                            // Prepare environment variables
                            $env_vars = [
                                'WHP_USER=' . $site['username'],
                                'WHP_UID=' . $uid,
                                'WHP_DOMAIN=' . $site['primary_domain'],
                                'WHP_CONTAINER_NUMBER=' . $i
                            ];
                            
                            // Add user variables
                            if (!empty($site['user_variables'])) {
                                $user_vars = json_decode($site['user_variables'], true);
                                foreach ($user_vars as $var_name => $var_value) {
                                    $env_vars[] = $var_name . '=' . $var_value;
                                }
                            }
                            
                            // Process startup environment variables
                            if (!empty($site['startup_env'])) {
                                $startup_env = json_decode($site['startup_env'], true);
                                if ($startup_env) {
                                    foreach ($startup_env as $var_name => $var_value) {
                                        // Replace dynamic variables using centralized function
                                        $var_value = $MySQLMgmt->replaceDynamicVariables($var_value, $site['username'], $site['primary_domain'], $uid, $i, $site['listen_port'] ?? 80);
                                        $env_vars[] = $var_name . '=' . $var_value;
                                    }
                                }
                            }
                            
                            // Prepare volumes - only use mount_options from container type
                            $volumes = [];
                            
                            // Process mount options
                            if (!empty($site['mount_options'])) {
                                $mount_options = json_decode($site['mount_options'], true);
                                if ($mount_options && isset($mount_options['volumes'])) {
                                    foreach ($mount_options['volumes'] as $volume) {
                                        // Replace dynamic variables in both source and target paths
                                        $source = $MySQLMgmt->replaceDynamicVariables($volume['source'], $site['username'], $site['primary_domain'], $uid, $i, $site['listen_port'] ?? 80);
                                        $target = $MySQLMgmt->replaceDynamicVariables($volume['target'], $site['username'], $site['primary_domain'], $uid, $i, $site['listen_port'] ?? 80);
                                        $volumes[] = $source . ':' . $target;
                                    }
                                }
                            }
                            
                            // Create container config
                            $container_config = [
                                'image' => $site['base_image'],
                                'name' => $container_name,
                                'labels' => [
                                    'whp.user' => $site['username'],
                                    'whp.domain' => $site['primary_domain'],
                                    'whp.site_id' => strval($site_id),
                                    'whp.container_number' => strval($i)
                                ],
                                'env' => $env_vars,
                                'volumes' => $volumes,
                                'cpu' => $site['cpu_per_container'],
                                'memory' => $site['memory_per_container'],
                                'network' => 'client-net'
                            ];
                            
                            $result = $DockerAPI->create_container($container_config);

                            if ($result['success']) {
                                // Save container info to database
                                $stmt = $db->prepare("
                                    INSERT INTO whp.site_containers (site_id, container_id, container_number, status)
                                    VALUES (?, ?, ?, 'running')
                                ");
                                $stmt->execute([$site_id, $result['container_id'], $i]);

                                $created_containers[] = [
                                    'container_number' => $i,
                                    'container_id' => $result['container_id']
                                ];
                                @whp_valkey_attach_if_enabled($db, $site['username'], $container_name);
                            } else {
                                $errors[] = "Failed to create container $i: " . $result['error'];
                            }
                        }

                        // Configure HAProxy if containers were created successfully
                        if (!empty($created_containers)) {
                            require_once('/docker/whp/web/libs/haproxy_manager.php');
                            $haproxy_manager = new haproxy_manager();
                            $haproxy_result = $haproxy_manager->configureSite($site_id, $site);
                            
                            if (!$haproxy_result['success']) {
                                $errors[] = "HAProxy configuration failed: " . $haproxy_result['error'];
                            }
                        }
                        
                        if (empty($errors)) {
                            echo json_encode(array(
                                'status' => 'success',
                                'message' => 'All containers created successfully and HAProxy configured',
                                'containers' => $created_containers
                            ));
                        } else {
                            echo json_encode(array(
                                'status' => 'partial',
                                'message' => 'Some containers created with errors',
                                'containers' => $created_containers,
                                'errors' => $errors
                            ));
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;
                
            case 'start_containers':
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);
                    
                    global $db;
                    try {
                        // Get site containers
                        $stmt = $db->prepare("SELECT * FROM whp.site_containers WHERE site_id = ?");
                        $stmt->execute([$site_id]);
                        $containers = $stmt->fetchAll();
                        
                        $started_containers = [];
                        $errors = [];
                        
                        foreach ($containers as $container) {
                            $result = $DockerAPI->start_container($container['container_id']);
                            
                            if ($result['success']) {
                                // Update status in database
                                $stmt = $db->prepare("UPDATE whp.site_containers SET status = 'running' WHERE container_id = ?");
                                $stmt->execute([$container['container_id']]);
                                
                                $started_containers[] = $container['container_number'];
                            } else {
                                $errors[] = "Failed to start container " . $container['container_number'] . ": " . $result['error'];
                            }
                        }
                        
                        // Configure HAProxy after starting containers
                        if (!empty($started_containers)) {
                            require_once('/docker/whp/web/libs/haproxy_manager.php');
                            $haproxy_manager = new haproxy_manager();
                            $haproxy_result = $haproxy_manager->configureSite($site_id, null);
                            
                            if (!$haproxy_result['success']) {
                                $errors[] = "HAProxy configuration failed: " . $haproxy_result['error'];
                            }
                        }
                        
                        if (empty($errors)) {
                            echo json_encode(array(
                                'status' => 'success',
                                'message' => 'All containers started successfully and HAProxy configured',
                                'containers' => $started_containers
                            ));
                        } else {
                            echo json_encode(array(
                                'status' => 'partial',
                                'message' => 'Some containers started with errors',
                                'containers' => $started_containers,
                                'errors' => $errors
                            ));
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;
                
            case 'stop_containers':
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);
                    
                    global $db;
                    try {
                        // Get site containers
                        $stmt = $db->prepare("SELECT * FROM whp.site_containers WHERE site_id = ?");
                        $stmt->execute([$site_id]);
                        $containers = $stmt->fetchAll();
                        
                        $stopped_containers = [];
                        $errors = [];
                        
                        foreach ($containers as $container) {
                            $result = $DockerAPI->stop_container($container['container_id']);
                            
                            if ($result['success']) {
                                // Update status in database
                                $stmt = $db->prepare("UPDATE whp.site_containers SET status = 'stopped' WHERE container_id = ?");
                                $stmt->execute([$container['container_id']]);
                                
                                $stopped_containers[] = $container['container_number'];
                            } else {
                                $errors[] = "Failed to stop container " . $container['container_number'] . ": " . $result['error'];
                            }
                        }
                        
                        // Configure HAProxy after stopping containers
                        if (!empty($stopped_containers)) {
                            require_once('/docker/whp/web/libs/haproxy_manager.php');
                            $haproxy_manager = new haproxy_manager();
                            $haproxy_result = $haproxy_manager->configureSite($site_id, null);
                            
                            if (!$haproxy_result['success']) {
                                $errors[] = "HAProxy configuration failed: " . $haproxy_result['error'];
                            }
                        }
                        
                        if (empty($errors)) {
                            echo json_encode(array(
                                'status' => 'success',
                                'message' => 'All containers stopped successfully and HAProxy updated',
                                'containers' => $stopped_containers
                            ));
                        } else {
                            echo json_encode(array(
                                'status' => 'partial',
                                'message' => 'Some containers stopped with errors',
                                'containers' => $stopped_containers,
                                'errors' => $errors
                            ));
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;
                
            case 'update_site':
                // Update site settings and recreate containers
                if (!empty($input['site_id']) && !empty($input['site_name']) && !empty($input['primary_domain_id']) && !empty($input['container_type_id'])) {
                    $site_id = intval($input['site_id']);
                    $site_name = trim($input['site_name']);
                    $primary_domain_id = trim($input['primary_domain_id']); // Accept FQDN
                    $container_type_id = intval($input['container_type_id']);
                    $container_count = intval($input['container_count'] ?? 1);
                    $cpu_per_container = floatval($input['cpu_per_container'] ?? 0.25);
                    $memory_per_container = intval($input['memory_per_container'] ?? 256);
                    $user_variables = !empty($input['user_variables']) ? $input['user_variables'] : '{}';

                    try {
                        // Get current site info
                        $stmt = $db->prepare("SELECT * FROM whp.sites WHERE id = ?");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();
                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(['status' => 'error', 'message' => 'Site not found']);
                            break;
                        }
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(['status' => 'error', 'message' => 'Access denied']);
                            break;
                        }

                        // Handle container_mode (root-only)
                        $container_mode = $site['container_mode'] ?? 'standalone'; // preserve current mode
                        if ($is_root && !empty($input['container_mode'])) {
                            $container_mode = ($input['container_mode'] === 'shared_httpd') ? 'shared_httpd' : 'standalone';
                        }

                        // Validate container type
                        $stmt = $db->prepare("SELECT * FROM whp.container_types WHERE id = ? AND active = 1");
                        $stmt->execute([$container_type_id]);
                        $container_type = $stmt->fetch();

                        if (!$container_type) {
                            http_response_code(400);
                            echo json_encode(['status' => 'error', 'message' => 'Invalid or inactive container type']);
                            break;
                        }

                        // Map to FPM type if shared_httpd mode
                        if ($container_mode === 'shared_httpd') {
                            require_once('/docker/whp/web/libs/shared_httpd_manager.php');
                            if (!shared_httpd_manager::isPhpType($container_type['name'])) {
                                http_response_code(400);
                                echo json_encode(['status' => 'error', 'message' => 'Shared httpd mode is only available for PHP container types']);
                                break;
                            }
                            $fpm_type_name = shared_httpd_manager::mapToFpmType($container_type['name']);
                            if ($fpm_type_name) {
                                $stmt = $db->prepare("SELECT * FROM whp.container_types WHERE name = ? AND active = 1");
                                $stmt->execute([$fpm_type_name]);
                                $fpm_type = $stmt->fetch(PDO::FETCH_ASSOC);
                                if ($fpm_type) {
                                    $container_type = $fpm_type;
                                    $container_type_id = $fpm_type['id'];
                                }
                            }
                        }
                        
                        // Validate resource requirements
                        if ($cpu_per_container < $container_type['min_cpu']) {
                            http_response_code(400);
                            echo json_encode(['status' => 'error', 'message' => 'CPU per container must be at least ' . $container_type['min_cpu'] . ' cores']);
                            break;
                        }
                        
                        if ($memory_per_container < $container_type['min_memory']) {
                            http_response_code(400);
                            echo json_encode(['status' => 'error', 'message' => 'Memory per container must be at least ' . $container_type['min_memory'] . ' MB']);
                            break;
                        }
                        
                        // Check user resource limits (exclude current site from calculations)
                        if (!$is_root) {
                            require_once('/docker/whp/web/libs/mysqlmgmt.php');
                            $MySQLMgmt = new mysqlmgmt();
                            
                            $stmt = $db->prepare("SELECT max_cpu_allowance, max_memory_allowance FROM whp.client_allowances WHERE username = ?");
                            $stmt->execute([$site['username']]);
                            $user_limits = $stmt->fetch();
                            
                            if ($user_limits) {
                                // Get current resource usage excluding this site
                                $usage = $MySQLMgmt->getUserResourceUsage($site['username']);
                                $current_cpu_used = floatval($usage['cpu_used'] ?? 0);
                                $current_memory_used = intval($usage['memory_used'] ?? 0);
                                
                                // Subtract current site resources from usage
                                $current_site_cpu = $site['container_count'] * $site['cpu_per_container'];
                                $current_site_memory = $site['container_count'] * $site['memory_per_container'];
                                $adjusted_cpu_used = $current_cpu_used - $current_site_cpu;
                                $adjusted_memory_used = $current_memory_used - $current_site_memory;
                                
                                // Calculate new site resources (use input values for new configuration)
                                $new_site_cpu = $cpu_per_container * $container_count;
                                $new_site_memory = $memory_per_container * $container_count;
                                
                                // Calculate projected totals
                                $projected_cpu = $adjusted_cpu_used + $new_site_cpu;
                                $projected_memory = $adjusted_memory_used + $new_site_memory;
                                
                                // Detailed debug logging
                                error_log("=== RESOURCE VALIDATION DEBUG ===");
                                error_log("Site ID: $site_id, Username: {$site['username']}");
                                error_log("User limits: {$user_limits['max_cpu_allowance']} CPU cores, {$user_limits['max_memory_allowance']} MB RAM");
                                error_log("Current total usage: $current_cpu_used CPU cores, $current_memory_used MB RAM");
                                error_log("Current site config: {$site['container_count']} containers × {$site['cpu_per_container']} CPU = $current_site_cpu cores");
                                error_log("Current site memory: {$site['container_count']} containers × {$site['memory_per_container']} MB = $current_site_memory MB");
                                error_log("Adjusted usage (without current site): $adjusted_cpu_used CPU cores, $adjusted_memory_used MB RAM");
                                error_log("New site config: $container_count containers × $cpu_per_container CPU = $new_site_cpu cores");
                                error_log("New site memory: $container_count containers × $memory_per_container MB = $new_site_memory MB");
                                error_log("Projected total: $projected_cpu CPU cores, $projected_memory MB RAM");
                                error_log("CPU limit exceeded: " . ($projected_cpu > $user_limits['max_cpu_allowance'] ? 'YES' : 'NO'));
                                error_log("Memory limit exceeded: " . ($projected_memory > $user_limits['max_memory_allowance'] ? 'YES' : 'NO'));
                                error_log("=== END DEBUG ===");
                                
                                // Enforce limits
                                if ($projected_cpu > $user_limits['max_cpu_allowance']) {
                                    http_response_code(400);
                                    echo json_encode(['status' => 'error', 'message' => 'Total CPU allocation after update (' . $projected_cpu . ' cores) exceeds your limit (' . $user_limits['max_cpu_allowance'] . ' cores). Current usage: ' . $current_cpu_used . ' cores, this site currently uses: ' . $current_site_cpu . ' cores, new site will use: ' . $new_site_cpu . ' cores. Debug: container_count=' . $container_count . ', cpu_per_container=' . $cpu_per_container]);
                                    break;
                                }
                                
                                if ($projected_memory > $user_limits['max_memory_allowance']) {
                                    http_response_code(400);
                                    echo json_encode(['status' => 'error', 'message' => 'Total memory allocation after update (' . $projected_memory . ' MB) exceeds your limit (' . $user_limits['max_memory_allowance'] . ' MB). Current usage: ' . $current_memory_used . ' MB, this site currently uses: ' . $current_site_memory . ' MB, new site will use: ' . $new_site_memory . ' MB']);
                                    break;
                                }
                            }
                        }
                        
                        // Get auto-recreation setting
                        $auto_recreate_on_update = isset($input['auto_recreate_on_update']) ? 1 : 0;
                        
                        // Accept and process FQDN for primary_domain_id
                        $primary_fqdn = trim($input['primary_domain_id']);
                        $additional_fqdns = isset($input['additional_domains']) ? $input['additional_domains'] : [];
                        if (!is_array($additional_fqdns)) $additional_fqdns = [];
                        $all_domains = $db->query("SELECT id, domain_name, username FROM whp.domains WHERE active=1")->fetchAll(PDO::FETCH_ASSOC);
                        // Find parent for primary
                        $is_wildcard_fqdn = str_starts_with($primary_fqdn, '*.');
                        $primary_domain_row = null;
                        foreach ($all_domains as $row) {
                            if ($primary_fqdn === $row['domain_name'] || (str_ends_with($primary_fqdn, '.' . $row['domain_name']))) {
                                $primary_domain_row = $row;
                                break;
                            }
                            // Also match wildcard: *.domain.tld belongs to domain.tld
                            if ($is_wildcard_fqdn && $primary_fqdn === '*.' . $row['domain_name']) {
                                $primary_domain_row = $row;
                                break;
                            }
                        }
                        if (!$primary_domain_row) {
                            http_response_code(400);
                            echo json_encode([
                                'status' => 'error',
                                'message' => 'Primary domain/subdomain not found or not owned by you.',
                                'debug' => [
                                    'primary_fqdn' => $primary_fqdn,
                                    'all_domains' => $all_domains
                                ]
                            ]);
                            break;
                        }
                        if (!$is_root && $primary_domain_row['username'] !== $site['username']) {
                            http_response_code(403);
                            echo json_encode([
                                'status' => 'error',
                                'message' => 'You can only update sites with your own domains/subdomains',
                                'debug' => [
                                    'primary_fqdn' => $primary_fqdn,
                                    'parent_domain' => $primary_domain_row['domain_name'],
                                    'parent_owner' => $primary_domain_row['username'],
                                    'site_owner' => $site['username']
                                ]
                            ]);
                            break;
                        }
                        // Validate FQDN is a valid A or CNAME for this domain
                        // Wildcard FQDNs skip A/CNAME validation
                        require_once('/docker/whp/web/libs/pdns-integration.php');
                        $PowerDNS = new PowerDNSIntegration($db);
                        $valid_fqdn = false;
                        if ($is_wildcard_fqdn) {
                            $valid_fqdn = true;
                        } else {
                            $fqdn_records = $PowerDNS->getRecords($primary_domain_row['domain_name']);
                            foreach ($fqdn_records as $rec) {
                                if (strcasecmp($rec['name'], $primary_fqdn) === 0 && in_array(strtoupper($rec['type']), ['A','CNAME'])) {
                                    $valid_fqdn = true;
                                    break;
                                }
                            }
                        }
                        if (!$valid_fqdn) {
                            http_response_code(400);
                            echo json_encode([
                                'status' => 'error',
                                'message' => 'Selected primary FQDN is not a valid A or CNAME record.',
                                'debug' => [
                                    'primary_fqdn' => $primary_fqdn,
                                    'parent_domain' => $primary_domain_row['domain_name'],
                                    'dns_records' => $fqdn_records
                                ]
                            ]);
                            break;
                        }
                        // Update site table
                        $stmt = $db->prepare("UPDATE whp.sites SET site_name=?, primary_domain_id=?, container_type_id=?, container_mode=?, container_count=?, cpu_per_container=?, memory_per_container=?, user_variables=?, auto_recreate_on_update=? WHERE id=?");
                        $stmt->execute([
                            $site_name,
                            $primary_domain_row['id'],
                            $container_type_id,
                            $container_mode,
                            $container_count,
                            $cpu_per_container,
                            $memory_per_container,
                            $user_variables,
                            !empty($input['auto_recreate_on_update']) ? 1 : 0,
                            $site_id
                        ]);
                        // Update site domains - only change what's different
                        $current_domains = [];
                        $stmt = $db->prepare("SELECT domain_id, fqdn, is_primary FROM whp.site_domains WHERE site_id = ?");
                        $stmt->execute([$site_id]);
                        $current_domains = $stmt->fetchAll();
                        
                        // Build target domains list
                        $target_domains = [];
                        $target_domains[] = ['domain_id' => $primary_domain_row['id'], 'fqdn' => $primary_fqdn, 'is_primary' => 1];
                        foreach ($additional_fqdns as $fqdn) {
                            $fqdn = trim($fqdn);
                            if ($fqdn === $primary_fqdn) continue;
                            $parent_row = null;
                            foreach ($all_domains as $row) {
                                if ($fqdn === $row['domain_name'] || (str_ends_with($fqdn, '.' . $row['domain_name']))) {
                                    $parent_row = $row;
                                    break;
                                }
                            }
                            if (!$parent_row) continue;
                            if (!$is_root && $parent_row['username'] !== $site['username']) continue;
                            $valid_fqdn = false;
                            $fqdn_records = $PowerDNS->getRecords($parent_row['domain_name']);
                            foreach ($fqdn_records as $rec) {
                                if (strcasecmp($rec['name'], $fqdn) === 0 && in_array(strtoupper($rec['type']), ['A','CNAME'])) {
                                    $valid_fqdn = true;
                                    break;
                                }
                            }
                            if (!$valid_fqdn) continue;
                            $target_domains[] = ['domain_id' => $parent_row['id'], 'fqdn' => $fqdn, 'is_primary' => 0];
                        }
                        
                        // Remove domains that are no longer needed
                        foreach ($current_domains as $current) {
                            $found = false;
                            foreach ($target_domains as $target) {
                                if ($current['domain_id'] == $target['domain_id'] && $current['fqdn'] == $target['fqdn']) {
                                    $found = true;
                                    break;
                                }
                            }
                            if (!$found) {
                                $stmt = $db->prepare("DELETE FROM whp.site_domains WHERE site_id = ? AND domain_id = ? AND fqdn = ?");
                                $stmt->execute([$site_id, $current['domain_id'], $current['fqdn']]);
                            }
                        }
                        
                        // Add new domains
                        foreach ($target_domains as $target) {
                            $found = false;
                            foreach ($current_domains as $current) {
                                if ($current['domain_id'] == $target['domain_id'] && $current['fqdn'] == $target['fqdn']) {
                                    $found = true;
                                    break;
                                }
                            }
                            if (!$found) {
                                $stmt = $db->prepare("INSERT INTO whp.site_domains (site_id, domain_id, fqdn, is_primary) VALUES (?, ?, ?, ?)");
                                $stmt->execute([$site_id, $target['domain_id'], $target['fqdn'], $target['is_primary']]);
                            }
                        }
                        // Check if containers need to be recreated
                        // Recreate if container count, type, CPU, or memory changes
                        // Note: Docker doesn't allow updating container labels on running containers,
                        // so if only the site name changes, the container labels will retain the old site name.
                        // This is acceptable as the containers still function correctly and HAProxy is updated.
                        $needs_container_recreation = false;
                        $old_container_count = $site['container_count'];
                        $old_container_type_id = $site['container_type_id'];
                        $old_cpu_per_container = floatval($site['cpu_per_container']);
                        $old_memory_per_container = intval($site['memory_per_container']);

                        // Check if container count, type, CPU, or memory has changed
                        if ($container_count != $old_container_count ||
                            $container_type_id != $old_container_type_id ||
                            $cpu_per_container != $old_cpu_per_container ||
                            $memory_per_container != $old_memory_per_container) {
                            $needs_container_recreation = true;

                            // Log the reason for container recreation
                            error_log("Container recreation triggered for site $site_id:");
                            if ($container_count != $old_container_count) {
                                error_log("  - Container count changed from $old_container_count to $container_count");
                            }
                            if ($container_type_id != $old_container_type_id) {
                                error_log("  - Container type changed from $old_container_type_id to $container_type_id");
                            }
                            if ($cpu_per_container != $old_cpu_per_container) {
                                error_log("  - CPU per container changed from $old_cpu_per_container to $cpu_per_container");
                            }
                            if ($memory_per_container != $old_memory_per_container) {
                                error_log("  - Memory per container changed from {$old_memory_per_container}MB to {$memory_per_container}MB");
                            }
                        }
                        
                        if ($needs_container_recreation) {
                            // Remove old containers from DB and Docker
                            $stmt = $db->prepare("SELECT container_id FROM whp.site_containers WHERE site_id=?");
                            $stmt->execute([$site_id]);
                            $old_containers = $stmt->fetchAll();
                            foreach ($old_containers as $c) {
                                $DockerAPI->remove_container($c['container_id']);
                            }
                            $stmt = $db->prepare("DELETE FROM whp.site_containers WHERE site_id=?");
                            $stmt->execute([$site_id]);
                        }
                        $created_containers = [];
                        $errors = [];
                        
                        if ($needs_container_recreation) {
                            // Get updated site info for container creation
                            $stmt = $db->prepare("
                                SELECT s.*, COALESCE(sd.fqdn, d.domain_name) as primary_domain, ct.base_image, ct.startup_env, ct.mount_options
                                FROM whp.sites s
                                JOIN whp.domains d ON s.primary_domain_id = d.id
                                LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                                JOIN whp.container_types ct ON s.container_type_id = ct.id
                                WHERE s.id = ?
                            ");
                            $stmt->execute([$site_id]);
                            $site = $stmt->fetch();
                            
                            // primary_domain now contains the FQDN
                            
                            // Create new containers (same as create_containers logic)
                            // Sanitize wildcard FQDNs for Docker container names (no * allowed)
                            $safe_primary = str_starts_with($site['primary_domain'], '*.')
                                ? str_replace('*.', 'wildcard.', $site['primary_domain'])
                                : $site['primary_domain'];
                            $uid = trim(shell_exec("id -u " . escapeshellarg($site['username'])));
                            for ($i = 1; $i <= $site['container_count']; $i++) {
                            $container_name = $safe_primary . '-' . str_pad($i, 2, '0', STR_PAD_LEFT);
                            $env_vars = [
                                'WHP_USER=' . $site['username'],
                                'WHP_UID=' . $uid,
                                'WHP_DOMAIN=' . $site['primary_domain'],
                                'WHP_CONTAINER_NUMBER=' . $i
                            ];
                            if (!empty($site['user_variables'])) {
                                $user_vars = json_decode($site['user_variables'], true);
                                foreach ($user_vars as $var_name => $var_value) {
                                    $env_vars[] = $var_name . '=' . $var_value;
                                }
                            }
                            if (!empty($site['startup_env'])) {
                                $startup_env = json_decode($site['startup_env'], true);
                                if ($startup_env) {
                                    foreach ($startup_env as $var_name => $var_value) {
                                        // Replace dynamic variables using centralized function
                                        $var_value = $MySQLMgmt->replaceDynamicVariables($var_value, $site['username'], $site['primary_domain'], $uid, $i, $site['listen_port'] ?? 80);
                                        $env_vars[] = $var_name . '=' . $var_value;
                                    }
                                }
                            }
                            $volumes = [];
                            if (!empty($site['mount_options'])) {
                                $mount_options = json_decode($site['mount_options'], true);
                                if ($mount_options && isset($mount_options['volumes'])) {
                                    foreach ($mount_options['volumes'] as $volume) {
                                        // Replace dynamic variables in both source and target paths
                                                                                    $source = $MySQLMgmt->replaceDynamicVariables($volume['source'], $site['username'], $site['primary_domain'], $uid, $i, $site['listen_port'] ?? 80);
                                            $target = $MySQLMgmt->replaceDynamicVariables($volume['target'], $site['username'], $site['primary_domain'], $uid, $i, $site['listen_port'] ?? 80);
                                        $volumes[] = $source . ':' . $target;
                                    }
                                }
                            }
                            $container_config = [
                                'image' => $site['base_image'],
                                'name' => $container_name,
                                'labels' => [
                                    'whp.user' => $site['username'],
                                    'whp.domain' => $site['primary_domain'],
                                    'whp.site_id' => strval($site_id),
                                    'whp.container_number' => strval($i)
                                ],
                                'env' => $env_vars,
                                'volumes' => $volumes,
                                'cpu' => $site['cpu_per_container'],
                                'memory' => $site['memory_per_container'],
                                'network' => 'client-net'
                            ];
                            $result = $DockerAPI->create_container($container_config);
                            if ($result['success']) {
                                $stmt = $db->prepare("INSERT INTO whp.site_containers (site_id, container_id, container_number, status) VALUES (?, ?, ?, 'running')");
                                $stmt->execute([$site_id, $result['container_id'], $i]);
                                $created_containers[] = [
                                    'container_number' => $i,
                                    'container_id' => $result['container_id']
                                ];
                                @whp_valkey_attach_if_enabled($db, $site['username'], $container_name);
                            } else {
                                $errors[] = "Failed to create container $i: " . $result['error'];
                            }
                        }

                        }

                        // Always regenerate shared httpd vhost for shared_httpd sites
                        // (picks up domain alias changes, container count changes, etc.)
                        if ($container_mode === 'shared_httpd') {
                            require_once('/docker/whp/web/libs/shared_httpd_manager.php');
                            $shared_httpd = new shared_httpd_manager();

                            // Ensure primary_domain FQDN is populated. The initial $site SELECT (line ~1167)
                            // is plain `SELECT * FROM whp.sites` which has no FQDN string column; the enriched
                            // query that JOINs the domains table only runs inside the container-recreation
                            // branch above. Without this lookup, addon-domain edits hit addVhost('') and write
                            // a malformed file at /docker/shared-httpd/vhosts/.conf that Apache silently skips.
                            if (empty($site['primary_domain'])) {
                                $stmt_pd = $db->prepare("
                                    SELECT COALESCE(sd.fqdn, d.domain_name) as primary_domain
                                    FROM whp.sites s
                                    JOIN whp.domains d ON s.primary_domain_id = d.id
                                    LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                                    WHERE s.id = ?
                                ");
                                $stmt_pd->execute([$site_id]);
                                $row_pd = $stmt_pd->fetch();
                                if ($row_pd && !empty($row_pd['primary_domain'])) {
                                    $site['primary_domain'] = $row_pd['primary_domain'];
                                }
                            }

                            // Build list of all FPM container names for load balancing
                            $safe_primary = str_starts_with($site['primary_domain'], '*.')
                                ? str_replace('*.', 'wildcard.', $site['primary_domain'])
                                : $site['primary_domain'];
                            $fpm_containers = [];
                            for ($ci = 1; $ci <= $site['container_count']; $ci++) {
                                $fpm_containers[] = $safe_primary . '-' . str_pad($ci, 2, '0', STR_PAD_LEFT);
                            }

                            $alias_domains = [];
                            $stmt_aliases = $db->prepare("
                                SELECT COALESCE(sd.fqdn, d.domain_name) as fqdn
                                FROM whp.site_domains sd
                                JOIN whp.domains d ON sd.domain_id = d.id
                                WHERE sd.site_id = ? AND sd.is_primary = 0
                            ");
                            $stmt_aliases->execute([$site_id]);
                            foreach ($stmt_aliases->fetchAll() as $alias_row) {
                                $alias_domains[] = $alias_row['fqdn'];
                            }

                            $vhost_result = $shared_httpd->addVhost(
                                $site['primary_domain'], $site['username'],
                                $fpm_containers, 9000, $alias_domains
                            );
                            if (!$vhost_result['success']) {
                                $errors[] = "Shared httpd vhost config failed: " . ($vhost_result['error'] ?? 'unknown');
                            } else {
                                $shared_httpd->reloadHttpd();
                            }
                        } elseif ($needs_container_recreation && $container_mode === 'standalone') {
                            // Switching back to standalone — remove vhost if it exists
                            require_once('/docker/whp/web/libs/shared_httpd_manager.php');
                            $shared_httpd = new shared_httpd_manager();
                            $shared_httpd->removeVhost($site['primary_domain']);
                            $shared_httpd->reloadHttpd();
                        }

                        // Configure HAProxy if containers were created successfully
                        if (!empty($created_containers)) {
                            require_once('/docker/whp/web/libs/haproxy_manager.php');
                            $haproxy_manager = new haproxy_manager();
                            $haproxy_result = $haproxy_manager->configureSite($site_id, $site);

                            if (!$haproxy_result['success']) {
                                $errors[] = "HAProxy configuration failed: " . $haproxy_result['error'];
                            }
                        }

                        // Always configure HAProxy after site update (regardless of container recreation)
                        require_once('/docker/whp/web/libs/haproxy_manager.php');
                        $haproxy_manager = new haproxy_manager();
                        
                        // Get updated site info for HAProxy configuration
                        $stmt = $db->prepare("
                            SELECT s.*, COALESCE(sd.fqdn, d.domain_name) as primary_domain, ct.base_image, ct.startup_env, ct.mount_options
                            FROM whp.sites s
                            JOIN whp.domains d ON s.primary_domain_id = d.id
                            LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                            JOIN whp.container_types ct ON s.container_type_id = ct.id
                            WHERE s.id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $updated_site = $stmt->fetch();
                        
                        $haproxy_result = $haproxy_manager->configureSite($site_id, $updated_site);

                        if (!$haproxy_result['success']) {
                            $errors[] = "HAProxy configuration failed: " . $haproxy_result['error'];
                        }

                        // SSL — when the edit form's "Enable HTTPS" is checked, run the
                        // orchestrator's per-site bundle issuance synchronously so the
                        // operator gets immediate feedback (success → cert live, failure →
                        // explanation in errors).
                        if (!empty($input['enable_ssl'])) {
                            require_once('/docker/whp/web/libs/ssl_renewal_orchestrator.php');
                            $orch = new ssl_renewal_orchestrator($db);
                            // Reset per-domain state for this site so the orchestrator treats
                            // it as a fresh attempt (clears stale next_renewal_attempt_at).
                            $stmt_reset = $db->prepare("
                                UPDATE whp.haproxy_domains hd
                                JOIN whp.site_domains sd ON COALESCE(sd.fqdn, '') = hd.domain_name
                                SET hd.renewal_state = 'pending',
                                    hd.consecutive_failures = 0,
                                    hd.next_renewal_attempt_at = NOW(),
                                    hd.dns_validation = ?
                                WHERE sd.site_id = ?
                            ");
                            $stmt_reset->execute([!empty($input['dns_validation']) ? 1 : 0, $site_id]);

                            $stmt_rows = $db->prepare("
                                SELECT hd.id, hd.domain_name, hd.is_wildcard, hd.dns_validation,
                                       hd.renewal_state, hd.ssl_enabled, hd.ssl_last_renewal,
                                       hd.consecutive_failures, hd.last_renewal_error
                                FROM whp.haproxy_domains hd
                                JOIN whp.site_domains sd ON COALESCE(sd.fqdn, '') = hd.domain_name
                                WHERE sd.site_id = ?
                            ");
                            $stmt_rows->execute([$site_id]);
                            $site_rows = $stmt_rows->fetchAll(PDO::FETCH_ASSOC);

                            if (!empty($site_rows)) {
                                $ssl_result = $orch->processSite($site_id, $site_rows);
                                if (empty($ssl_result['bundle_success'])) {
                                    $errors[] = "SSL bundle issuance did not complete; will retry in the next sweep";
                                }
                            }
                        }

                        if (empty($errors)) {
                            echo json_encode(['status' => 'success', 'message' => 'Site updated and HAProxy configured', 'containers' => $created_containers]);
                        } else {
                            echo json_encode(['status' => 'partial', 'message' => 'Site updated but some operations failed', 'containers' => $created_containers, 'errors' => $errors]);
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(['status' => 'error', 'message' => 'Missing required fields for update_site']);
                }
                break;
                
            case 'recreate_container':
                // Recreate a specific container for a site with the latest image
                if (!empty($input['site_id']) && !empty($input['container_id'])) {
                    $site_id = intval($input['site_id']);
                    $container_id = $input['container_id'];
                    
                    global $db;
                    try {
                        // Get site details
                        $stmt = $db->prepare("
                            SELECT 
                                s.*,
                                COALESCE(sd.fqdn, d.domain_name) as primary_domain,
                                ct.base_image,
                                ct.startup_env,
                                ct.mount_options
                            FROM whp.sites s
                            JOIN whp.domains d ON s.primary_domain_id = d.id
                            LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                            JOIN whp.container_types ct ON s.container_type_id = ct.id
                            WHERE s.id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();
                        
                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Site not found'
                            ));
                            break;
                        }
                        
                        // Check ownership
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Access denied'
                            ));
                            break;
                        }
                        
                        // Get container details from database
                        $stmt = $db->prepare("SELECT * FROM whp.site_containers WHERE site_id = ? AND container_id = ?");
                        $stmt->execute([$site_id, $container_id]);
                        $container_info = $stmt->fetch();
                        
                        if (!$container_info) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Container not found'
                            ));
                            break;
                        }
                        
                        // First, pull the latest image
                        $pull_result = $DockerAPI->pull_image($site['base_image']);
                        if (!$pull_result['success']) {
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Failed to pull latest image: ' . $pull_result['error']
                            ));
                            break;
                        }
                        
                        // Stop and remove the old container
                        $stop_result = $DockerAPI->stop_container($container_id);
                        $remove_result = $DockerAPI->remove_container($container_id);
                        
                        // Get user UID
                        $uid = trim(shell_exec("id -u " . escapeshellarg($site['username'])));
                        if (!is_numeric($uid)) {
                            http_response_code(400);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Invalid user'
                            ));
                            break;
                        }
                        
                        // primary_domain now contains the FQDN
                        // Compute safe FQDN for container/filesystem names (no * allowed)
                        $safe_domain = !empty($site['is_wildcard']) ? str_replace('*.', 'wildcard.', $site['primary_domain']) : $site['primary_domain'];

                        // Create new container with same configuration
                        $container_number = $container_info['container_number'];
                        $container_name = $safe_domain . '-' . str_pad($container_number, 2, '0', STR_PAD_LEFT);

                        // Prepare environment variables (WHP_DOMAIN keeps the original FQDN)
                        $env_vars = [
                            'WHP_USER=' . $site['username'],
                            'WHP_UID=' . $uid,
                            'WHP_DOMAIN=' . $site['primary_domain'],
                            'WHP_CONTAINER_NUMBER=' . $container_number
                        ];

                        // Add user variables
                        if (!empty($site['user_variables'])) {
                            $user_vars = json_decode($site['user_variables'], true);
                            foreach ($user_vars as $var_name => $var_value) {
                                $env_vars[] = $var_name . '=' . $var_value;
                            }
                        }

                        // Process startup environment variables
                        if (!empty($site['startup_env'])) {
                            $startup_env = json_decode($site['startup_env'], true);
                            if ($startup_env) {
                                foreach ($startup_env as $var_name => $var_value) {
                                    // Replace dynamic variables using centralized function
                                    $var_value = $MySQLMgmt->replaceDynamicVariables($var_value, $site['username'], $site['primary_domain'], $uid, $container_number, $site['listen_port'] ?? 80);
                                    $env_vars[] = $var_name . '=' . $var_value;
                                }
                            }
                        }

                        // Prepare volumes - only use mount_options from container type
                        $volumes = [];

                        // Process mount options (use safe domain for filesystem paths)
                        if (!empty($site['mount_options'])) {
                            $mount_options = json_decode($site['mount_options'], true);
                            if ($mount_options && isset($mount_options['volumes'])) {
                                foreach ($mount_options['volumes'] as $volume) {
                                    // Replace dynamic variables in both source and target paths
                                    $source = $MySQLMgmt->replaceDynamicVariables($volume['source'], $site['username'], $safe_domain, $uid, $container_number, $site['listen_port'] ?? 80);
                                    $target = $MySQLMgmt->replaceDynamicVariables($volume['target'], $site['username'], $safe_domain, $uid, $container_number, $site['listen_port'] ?? 80);
                                    $volumes[] = $source . ':' . $target;
                                }
                            }
                        }

                        // Create new container config
                        $container_config = [
                            'image' => $site['base_image'],
                            'name' => $container_name,
                            'labels' => [
                                'whp.user' => $site['username'],
                                'whp.domain' => $site['primary_domain'],
                                'whp.site_id' => strval($site_id),
                                'whp.container_number' => strval($container_number)
                            ],
                            'env' => $env_vars,
                            'volumes' => $volumes,
                            'cpu' => $site['cpu_per_container'],
                            'memory' => $site['memory_per_container'],
                            'network' => 'client-net'
                        ];
                        
                        $result = $DockerAPI->create_container($container_config);

                        if ($result['success']) {
                            // Update container info in database
                            $stmt = $db->prepare("UPDATE whp.site_containers SET container_id = ?, status = 'running' WHERE site_id = ? AND container_number = ?");
                            $stmt->execute([$result['container_id'], $site_id, $container_number]);
                            @whp_valkey_attach_if_enabled($db, $site['username'], $container_name);

                            // Configure HAProxy after container recreation
                            require_once('/docker/whp/web/libs/haproxy_manager.php');
                            $haproxy_manager = new haproxy_manager();
                            $haproxy_result = $haproxy_manager->configureSite($site_id, $site);
                            
                            if ($haproxy_result['success']) {
                                echo json_encode(array(
                                    'status' => 'success',
                                    'message' => 'Container recreated successfully with latest image and HAProxy configured',
                                    'container_id' => $result['container_id']
                                ));
                            } else {
                                echo json_encode(array(
                                    'status' => 'partial',
                                    'message' => 'Container recreated but HAProxy configuration failed: ' . $haproxy_result['error'],
                                    'container_id' => $result['container_id']
                                ));
                            }
                        } else {
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Failed to create new container: ' . $result['error']
                            ));
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID and container ID are required'
                    ));
                }
                break;
                
            case 'recreate_all_containers':
                // Recreate all containers for a site with the latest images
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);
                    
                    global $db;
                    try {
                        // Get site details
                        $stmt = $db->prepare("
                            SELECT 
                                s.*,
                                COALESCE(sd.fqdn, d.domain_name) as primary_domain,
                                ct.base_image,
                                ct.listen_port,
                                ct.startup_env,
                                ct.mount_options
                            FROM whp.sites s
                            JOIN whp.domains d ON s.primary_domain_id = d.id
                            LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                            JOIN whp.container_types ct ON s.container_type_id = ct.id
                            WHERE s.id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();
                        
                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Site not found'
                            ));
                            break;
                        }
                        
                        // Check ownership
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Access denied'
                            ));
                            break;
                        }
                        
                        // Note: We don't pull the image here - manual recreation uses existing image
                        
                        // Get existing containers for this site
                        $stmt = $db->prepare("SELECT * FROM whp.site_containers WHERE site_id = ? ORDER BY container_number ASC");
                        $stmt->execute([$site_id]);
                        $existing_containers = $stmt->fetchAll();
                        
                        // Build array of existing container numbers for easy lookup
                        $existing_container_numbers = [];
                        foreach ($existing_containers as $container) {
                            $existing_container_numbers[] = $container['container_number'];
                        }
                        
                        // Determine what containers should exist (1 to container_count)
                        $expected_containers = [];
                        for ($i = 1; $i <= $site['container_count']; $i++) {
                            $expected_containers[] = $i;
                        }
                        
                        // Find missing containers that need to be created
                        $missing_containers = array_diff($expected_containers, $existing_container_numbers);
                        
                        // Get user UID
                        $uid = trim(shell_exec("id -u " . escapeshellarg($site['username'])));
                        if (!is_numeric($uid)) {
                            http_response_code(400);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Invalid user'
                            ));
                            break;
                        }
                        
                        // primary_domain now contains the FQDN
                        // Compute safe FQDN for container/filesystem names (no * allowed)
                        $safe_domain = !empty($site['is_wildcard']) ? str_replace('*.', 'wildcard.', $site['primary_domain']) : $site['primary_domain'];

                        $processed_containers = [];
                        $errors = [];

                        // Process all expected containers (both existing and missing)
                        foreach ($expected_containers as $container_number) {
                            // Check if this container already exists
                            $existing_container = null;
                            foreach ($existing_containers as $container) {
                                if ($container['container_number'] == $container_number) {
                                    $existing_container = $container;
                                    break;
                                }
                            }

                            // If container exists, stop and remove it first (recreate)
                            if ($existing_container) {
                                $stop_result = $DockerAPI->stop_container($existing_container['container_id']);
                                $remove_result = $DockerAPI->remove_container($existing_container['container_id']);
                            }

                            // Create/recreate the container (use safe domain for name)
                            $container_name = $safe_domain . '-' . str_pad($container_number, 2, '0', STR_PAD_LEFT);

                            // Prepare environment variables (WHP_DOMAIN keeps the original FQDN)
                            $env_vars = [
                                'WHP_USER=' . $site['username'],
                                'WHP_UID=' . $uid,
                                'WHP_DOMAIN=' . $site['primary_domain'],
                                'WHP_CONTAINER_NUMBER=' . $container_number
                            ];

                            // Add user variables
                            if (!empty($site['user_variables'])) {
                                $user_vars = json_decode($site['user_variables'], true);
                                foreach ($user_vars as $var_name => $var_value) {
                                    $env_vars[] = $var_name . '=' . $var_value;
                                }
                            }

                            // Process startup environment variables
                            if (!empty($site['startup_env'])) {
                                $startup_env = json_decode($site['startup_env'], true);
                                if ($startup_env) {
                                    foreach ($startup_env as $var_name => $var_value) {
                                        // Replace dynamic variables using centralized function
                                        $var_value = $MySQLMgmt->replaceDynamicVariables($var_value, $site['username'], $site['primary_domain'], $uid, $container_number, $site['listen_port'] ?? 80);
                                        $env_vars[] = $var_name . '=' . $var_value;
                                    }
                                }
                            }

                            // Prepare volumes - only use mount_options from container type
                            $volumes = [];

                            // Process mount options (use safe domain for filesystem paths)
                            if (!empty($site['mount_options'])) {
                                $mount_options = json_decode($site['mount_options'], true);
                                if ($mount_options && isset($mount_options['volumes'])) {
                                    foreach ($mount_options['volumes'] as $volume) {
                                        // Replace dynamic variables in both source and target paths
                                        $source = $MySQLMgmt->replaceDynamicVariables($volume['source'], $site['username'], $safe_domain, $uid, $container_number, $site['listen_port'] ?? 80);
                                        $target = $MySQLMgmt->replaceDynamicVariables($volume['target'], $site['username'], $safe_domain, $uid, $container_number, $site['listen_port'] ?? 80);
                                        $volumes[] = $source . ':' . $target;
                                    }
                                }
                            }

                            // Create new container config
                            $container_config = [
                                'image' => $site['base_image'],
                                'name' => $container_name,
                                'labels' => [
                                    'whp.user' => $site['username'],
                                    'whp.domain' => $site['primary_domain'],
                                    'whp.site_id' => strval($site_id),
                                    'whp.container_number' => strval($container_number)
                                ],
                                'env' => $env_vars,
                                'volumes' => $volumes,
                                'cpu' => $site['cpu_per_container'],
                                'memory' => $site['memory_per_container'],
                                'network' => 'client-net'
                            ];
                            
                            $result = $DockerAPI->create_container($container_config);

                            if ($result['success']) {
                                // Update or insert container info in database
                                if ($existing_container) {
                                    // Update existing container record
                                    $stmt = $db->prepare("UPDATE whp.site_containers SET container_id = ?, status = 'running' WHERE site_id = ? AND container_number = ?");
                                    $stmt->execute([$result['container_id'], $site_id, $container_number]);
                                } else {
                                    // Insert new container record
                                    $stmt = $db->prepare("INSERT INTO whp.site_containers (site_id, container_id, container_number, status) VALUES (?, ?, ?, 'running')");
                                    $stmt->execute([$site_id, $result['container_id'], $container_number]);
                                }
                                @whp_valkey_attach_if_enabled($db, $site['username'], $container_name);

                                $processed_containers[] = [
                                    'container_number' => $container_number,
                                    'container_id' => $result['container_id'],
                                    'action' => $existing_container ? 'recreated' : 'created'
                                ];
                            } else {
                                $action = $existing_container ? 'recreate' : 'create';
                                $errors[] = "Failed to $action container $container_number: " . $result['error'];
                            }
                        }
                        
                        // Configure HAProxy if containers were processed successfully
                        if (!empty($processed_containers)) {
                            require_once('/docker/whp/web/libs/haproxy_manager.php');
                            $haproxy_manager = new haproxy_manager();
                            $haproxy_result = $haproxy_manager->configureSite($site_id, $site);
                            
                            if (!$haproxy_result['success']) {
                                $errors[] = "HAProxy configuration failed: " . $haproxy_result['error'];
                            }
                        }
                        
                        if (empty($errors)) {
                            // Count created vs recreated containers for better messaging
                            $created_count = count(array_filter($processed_containers, function($c) { return $c['action'] === 'created'; }));
                            $recreated_count = count(array_filter($processed_containers, function($c) { return $c['action'] === 'recreated'; }));
                            
                            $message = '';
                            if ($created_count > 0 && $recreated_count > 0) {
                                $message = "Created $created_count missing container(s) and recreated $recreated_count existing container(s) with latest images and HAProxy configured";
                            } else if ($created_count > 0) {
                                $message = "Created $created_count missing container(s) with latest images and HAProxy configured";
                            } else {
                                $message = "Recreated $recreated_count container(s) with latest images and HAProxy configured";
                            }
                            
                            echo json_encode(array(
                                'status' => 'success',
                                'message' => $message,
                                'containers' => $processed_containers
                            ));
                        } else {
                            echo json_encode(array(
                                'status' => 'partial',
                                'message' => 'Some containers processed with errors',
                                'containers' => $processed_containers,
                                'errors' => $errors
                            ));
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;
                
            case 'validate_dns':
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);
                    
                    global $db;
                    try {
                        // Get site info and domains
                        $stmt = $db->prepare("
                            SELECT s.username, d.domain_name 
                            FROM whp.sites s
                            JOIN whp.domains d ON s.primary_domain_id = d.id
                            WHERE s.id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();
                        
                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Site not found'
                            ));
                            break;
                        }
                        
                        // Check ownership
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Access denied'
                            ));
                            break;
                        }
                        
                        // Get all domains for this site
                        $stmt = $db->prepare("
                            SELECT d.domain_name 
                            FROM whp.site_domains sd
                            JOIN whp.domains d ON sd.domain_id = d.id
                            WHERE sd.site_id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $site_domains = $stmt->fetchAll(PDO::FETCH_COLUMN);
                        
                        // Add primary domain if not already in list
                        if (!in_array($site['domain_name'], $site_domains)) {
                            $site_domains[] = $site['domain_name'];
                        }
                        
                        // Use DNS validator for robust validation
                        require_once('/docker/whp/web/libs/dns_validator.php');
                        $dns_validator = new dns_validator();
                        $validation_results = $dns_validator->validateDomains($site_domains);
                        
                        echo json_encode(array(
                            'status' => 'success',
                            'all_valid' => $validation_results['all_valid'],
                            'results' => $validation_results['domains'],
                            'server_ip' => $validation_results['server_ip']
                        ));
                        
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;
                
            // SSL/HAProxy Management Endpoints
            case 'ssl_retry_domain':
                // Triggered by the "Retry SSL" button on the per-domain SSL state badge.
                // Resets the orchestrator state for one domain and runs an immediate
                // attempt so the customer doesn't have to wait for the next 30-min sweep.
                if (!empty($input['domain'])) {
                    $domain = trim($input['domain']);
                    global $db;
                    try {
                        // Look up the haproxy_domains row + verify owner.
                        $stmt = $db->prepare("
                            SELECT hd.id, hd.domain_name, hd.is_wildcard, hd.dns_validation,
                                   hd.renewal_state, hd.ssl_enabled, hd.ssl_last_renewal,
                                   hd.consecutive_failures, hd.last_renewal_error, s.username
                            FROM whp.haproxy_domains hd
                            LEFT JOIN whp.site_domains sd ON COALESCE(sd.fqdn, '') = hd.domain_name
                            LEFT JOIN whp.sites s ON s.id = sd.site_id
                            WHERE hd.domain_name = ?
                            LIMIT 1
                        ");
                        $stmt->execute([$domain]);
                        $row = $stmt->fetch(PDO::FETCH_ASSOC);
                        if (!$row) {
                            http_response_code(404);
                            echo json_encode(['status' => 'error', 'message' => 'Domain not found in haproxy_domains']);
                            break;
                        }
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && (empty($row['username']) || $row['username'] !== AUTH_USER)) {
                            http_response_code(403);
                            echo json_encode(['status' => 'error', 'message' => 'Access denied']);
                            break;
                        }

                        // Reset state so the orchestrator treats this as a fresh attempt.
                        $db->prepare("
                            UPDATE whp.haproxy_domains
                            SET renewal_state = 'pending',
                                consecutive_failures = 0,
                                next_renewal_attempt_at = NOW()
                            WHERE id = ?
                        ")->execute([$row['id']]);

                        // Run the orchestrator inline for this one domain so the user gets
                        // immediate feedback rather than waiting for the cron sweep.
                        require_once('/docker/whp/web/libs/ssl_renewal_orchestrator.php');
                        $orch = new ssl_renewal_orchestrator($db);
                        // Reload row with the just-reset state so processDomain sees it.
                        $row['renewal_state'] = 'pending';
                        $row['consecutive_failures'] = 0;
                        $result = $orch->processDomain($row);

                        echo json_encode([
                            'status' => 'success',
                            'message' => 'SSL retry initiated for ' . $domain,
                            'result' => $result,
                        ]);
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(['status' => 'error', 'message' => 'domain is required']);
                }
                break;

            case 'ssl_enable':
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);

                    global $db;
                    try {
                        // Get site info including wildcard flag
                        $stmt = $db->prepare("SELECT username, is_wildcard FROM whp.sites WHERE id = ?");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();

                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Site not found'
                            ));
                            break;
                        }

                        // Check ownership
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Access denied'
                            ));
                            break;
                        }

                        require_once('/docker/whp/web/libs/ssl_manager.php');
                        $ssl_manager = new ssl_manager();

                        // Wildcard sites use DNS-01 challenge flow
                        if (!empty($site['is_wildcard'])) {
                            // Get wildcard FQDN
                            $stmt = $db->prepare("SELECT fqdn FROM whp.site_domains WHERE site_id = ? AND is_primary = 1");
                            $stmt->execute([$site_id]);
                            $wildcard_fqdn = $stmt->fetchColumn();

                            $result = $ssl_manager->enable_wildcard_ssl($site_id, $wildcard_fqdn);

                            if ($result['success']) {
                                echo json_encode(array(
                                    'status' => 'success',
                                    'message' => 'DNS-01 challenge initiated for wildcard certificate',
                                    'challenge' => [
                                        'id' => $result['challenge_id'],
                                        'fqdn' => $result['challenge_fqdn'],
                                        'token' => $result['challenge_token'],
                                        'status' => $result['status']
                                    ]
                                ));
                            } else {
                                http_response_code(500);
                                echo json_encode(array(
                                    'status' => 'error',
                                    'message' => $result['error']
                                ));
                            }
                            break;
                        }

                        // Standard sites use HTTP-01 validation
                        // Get all domains for this site
                        $stmt = $db->prepare("
                            SELECT d.domain_name
                            FROM whp.site_domains sd
                            JOIN whp.domains d ON sd.domain_id = d.id
                            WHERE sd.site_id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $site_domains = $stmt->fetchAll(PDO::FETCH_COLUMN);

                        // Add primary domain if not already in list
                        $stmt = $db->prepare("SELECT domain_name FROM whp.domains WHERE id = (SELECT primary_domain_id FROM whp.sites WHERE id = ?)");
                        $stmt->execute([$site_id]);
                        $primary_domain = $stmt->fetchColumn();
                        if (!in_array($primary_domain, $site_domains)) {
                            $site_domains[] = $primary_domain;
                        }

                        // Use DNS validator for robust validation
                        require_once('/docker/whp/web/libs/dns_validator.php');
                        $dns_validator = new dns_validator();
                        $validation_results = $dns_validator->validateDomains($site_domains);

                        // Separate valid and invalid domains
                        $valid_domains = [];
                        $invalid_domains = [];
                        foreach ($validation_results['domains'] as $domain => $result) {
                            if ($result['valid']) {
                                $valid_domains[] = $domain;
                            } else {
                                $invalid_domains[] = $domain;
                            }
                        }

                        // Enable SSL (it will only succeed for valid domains)
                        $result = $ssl_manager->enable_ssl($site_id, 'letsencrypt');

                        if ($result['success']) {
                            // Build response message
                            $message = 'SSL enabled successfully';
                            $response = array(
                                'status' => 'success',
                                'message' => $message
                            );

                            // Add warning if some domains are invalid
                            if (!empty($invalid_domains)) {
                                $response['warning'] = 'The following domains are not pointing to our server and will not have SSL until DNS is configured: ' . implode(', ', $invalid_domains) . '. Please configure DNS to point to ' . $validation_results['server_ip'] . ' and try enabling SSL again.';
                                $response['invalid_domains'] = $invalid_domains;
                                $response['valid_domains'] = $valid_domains;
                                $response['server_ip'] = $validation_results['server_ip'];
                            }

                            echo json_encode($response);
                        } else {
                            http_response_code(500);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => $result['error'],
                                'invalid_domains' => $invalid_domains,
                                'valid_domains' => $valid_domains
                            ));
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;
                

            case 'ssl_dns_challenge_status':
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);

                    global $db;
                    try {
                        // Check ownership
                        $stmt = $db->prepare("SELECT username FROM whp.sites WHERE id = ?");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();
                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(['status' => 'error', 'message' => 'Site not found']);
                            break;
                        }
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(['status' => 'error', 'message' => 'Access denied']);
                            break;
                        }

                        $stmt = $db->prepare("
                            SELECT id, domain_name, challenge_fqdn, status, error_message, requested_at, completed_at
                            FROM whp.dns_challenges WHERE site_id = ? ORDER BY requested_at DESC
                        ");
                        $stmt->execute([$site_id]);
                        $challenges = $stmt->fetchAll(PDO::FETCH_ASSOC);

                        echo json_encode(['status' => 'success', 'challenges' => $challenges]);
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(['status' => 'error', 'message' => 'Site ID is required']);
                }
                break;

            case 'ssl_dns_challenge_verify':
                if (!empty($input['challenge_id'])) {
                    $challenge_id = intval($input['challenge_id']);

                    global $db;
                    try {
                        // Get challenge and verify ownership
                        $stmt = $db->prepare("
                            SELECT dc.*, s.username FROM whp.dns_challenges dc
                            JOIN whp.sites s ON dc.site_id = s.id
                            WHERE dc.id = ?
                        ");
                        $stmt->execute([$challenge_id]);
                        $challenge = $stmt->fetch();

                        if (!$challenge) {
                            http_response_code(404);
                            echo json_encode(['status' => 'error', 'message' => 'Challenge not found']);
                            break;
                        }
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $challenge['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(['status' => 'error', 'message' => 'Access denied']);
                            break;
                        }

                        require_once('/docker/whp/web/libs/ssl_manager.php');
                        $ssl_manager = new ssl_manager();
                        $result = $ssl_manager->verify_dns_challenge($challenge_id);

                        if ($result['success']) {
                            echo json_encode(['status' => 'success', 'message' => $result['message']]);
                        } else {
                            http_response_code(500);
                            echo json_encode(['status' => 'error', 'message' => $result['error']]);
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(['status' => 'error', 'message' => 'Challenge ID is required']);
                }
                break;

            case 'ssl_renew':
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);
                    
                    global $db;
                    try {
                        // Get site info
                        $stmt = $db->prepare("SELECT username FROM whp.sites WHERE id = ?");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();
                        
                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Site not found'
                            ));
                            break;
                        }
                        
                        // Check ownership
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Access denied'
                            ));
                            break;
                        }
                        
                        // Renew SSL
                        require_once('/docker/whp/web/libs/ssl_manager.php');
                        $ssl_manager = new ssl_manager();
                        $result = $ssl_manager->renew_ssl($site_id);
                        
                        if ($result['success']) {
                            echo json_encode(array(
                                'status' => 'success',
                                'message' => 'SSL certificates renewed successfully'
                            ));
                        } else {
                            http_response_code(500);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => $result['error']
                            ));
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;

            case 'restart_site':
                // Restart all containers for a site
                if (!empty($input['site_id'])) {
                    $site_id = intval($input['site_id']);

                    try {
                        // Get site details to verify ownership
                        $stmt = $db->prepare("
                            SELECT s.*, d.domain_name
                            FROM whp.sites s
                            JOIN whp.domains d ON s.primary_domain_id = d.id
                            WHERE s.id = ?
                        ");
                        $stmt->execute([$site_id]);
                        $site = $stmt->fetch();

                        if (!$site) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Site not found'
                            ));
                            break;
                        }

                        // Check ownership
                        $is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
                        if (!$is_root && $site['username'] !== AUTH_USER) {
                            http_response_code(403);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Access denied'
                            ));
                            break;
                        }

                        // Get all containers for this site
                        $stmt = $db->prepare("
                            SELECT container_id
                            FROM whp.site_containers
                            WHERE site_id = ?
                            ORDER BY container_number
                        ");
                        $stmt->execute([$site_id]);
                        $containers = $stmt->fetchAll(PDO::FETCH_COLUMN);

                        if (empty($containers)) {
                            http_response_code(404);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'No containers found for this site'
                            ));
                            break;
                        }

                        // Restart each container
                        $failed_containers = [];
                        foreach ($containers as $container_id) {
                            $result = shell_exec("docker restart " . escapeshellarg($container_id) . " 2>&1");
                            if (strpos($result, 'Error') !== false) {
                                $failed_containers[] = $container_id;
                            }
                        }

                        if (!empty($failed_containers)) {
                            http_response_code(500);
                            echo json_encode(array(
                                'status' => 'error',
                                'message' => 'Failed to restart containers: ' . implode(', ', $failed_containers)
                            ));
                        } else {
                            echo json_encode(array(
                                'status' => 'success',
                                'message' => 'All containers restarted successfully',
                                'containers_restarted' => count($containers)
                            ));
                        }
                    } catch (PDOException $e) {
                        http_response_code(500);
                        echo json_encode(array(
                            'status' => 'error',
                            'message' => 'Database error: ' . $e->getMessage()
                        ));
                    }
                } else {
                    http_response_code(400);
                    echo json_encode(array(
                        'status' => 'error',
                        'message' => 'Site ID is required'
                    ));
                }
                break;

            case 'haproxy_configure':
                // TODO: Implement HAProxy configuration
                // This endpoint will configure HAProxy backends for the site
                http_response_code(501);
                echo json_encode(array(
                    'status' => 'error',
                    'message' => 'HAProxy configuration not yet implemented'
                ));
                break;
                
            default:
                http_response_code(400);
                echo json_encode(array(
                    'status' => 'error',
                    'message' => 'Invalid action'
                ));
                break;
        }
        break;
        
    default:
        http_response_code(405);
        echo json_encode(array(
            'status' => 'error',
            'message' => 'Method not allowed'
        ));
        break;
}
?> 