<?php
/**
 * Shared httpd Manager
 *
 * Manages Apache vhost configuration files for the shared httpd container(s).
 * Vhost configs are written to a host directory that is volume-mounted into
 * all shared-httpd instances. After writing/removing configs, a graceful
 * reload is triggered on every running shared-httpd container.
 */
class shared_httpd_manager {

    private $vhost_dir;
    private $template_path;

    public function __construct() {
        $this->vhost_dir = '/docker/shared-httpd/vhosts';
        $this->template_path = '/docker/whp/web/configs/shared-vhost-template.tpl';

        if (!is_dir($this->vhost_dir)) {
            mkdir($this->vhost_dir, 0755, true);
        }
    }

    /**
     * Generate and write a vhost config for a domain
     *
     * @param string $domain        Primary domain (FQDN)
     * @param string $user          System username (site owner)
     * @param string $fpm_container PHP-FPM container name (DNS-resolvable on client-net)
     * @param int    $fpm_port      PHP-FPM listen port (default 9000)
     * @param array  $aliases          Additional ServerAlias domains
     * @return array                   Result with success/error
     */
    public function addVhost($domain, $user, $fpm_containers, $fpm_port = 9000, $aliases = []) {
        try {
            if (!is_string($domain) || trim($domain) === '') {
                error_log('shared_httpd_manager::addVhost called with empty domain — refusing to write malformed vhost. user=' . var_export($user, true));
                return ['success' => false, 'error' => 'addVhost: empty domain'];
            }
            if (!file_exists($this->template_path)) {
                return ['success' => false, 'error' => 'Vhost template not found: ' . $this->template_path];
            }

            // Accept single string or array of container names
            if (is_string($fpm_containers)) {
                $fpm_containers = [$fpm_containers];
            }

            $template = file_get_contents($this->template_path);

            // Build ServerAlias block
            $alias_block = '';
            if (!empty($aliases)) {
                foreach ($aliases as $alias) {
                    $alias_block .= '    ServerAlias ' . $alias . "\n";
                }
            }

            // Build the proxy block based on number of FPM backends
            $proxy_block = $this->buildProxyBlock($domain, $user, $fpm_containers, $fpm_port);

            // Replace template placeholders
            $config = str_replace(
                ['~~domain~~', '~~user~~', '~~alias_block~~', '~~proxy_block~~'],
                [$domain, $user, $alias_block, $proxy_block],
                $template
            );

            // Use a safe filename (replace dots with underscores for wildcard domains)
            $safe_domain = str_replace(['*', '.'], ['wildcard', '_'], $domain);
            $config_path = $this->vhost_dir . '/' . $safe_domain . '.conf';

            if (file_put_contents($config_path, $config) === false) {
                return ['success' => false, 'error' => 'Failed to write vhost config: ' . $config_path];
            }

            $backend_list = implode(', ', $fpm_containers);
            error_log("Shared httpd: wrote vhost config for $domain -> [{$backend_list}]:$fpm_port");
            return ['success' => true, 'config_path' => $config_path];

        } catch (Exception $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

    /**
     * Build the Apache proxy block for PHP-FPM backends
     *
     * Single container: direct SetHandler proxy:fcgi
     * Multiple containers: mod_proxy_balancer with BalancerMember per container
     */
    private function buildProxyBlock($domain, $user, $fpm_containers, $fpm_port) {
        $doc_root = "/home/{$user}/public_html";
        $env_lines = "    ProxyFCGISetEnvIf \"true\" DOCUMENT_ROOT \"{$doc_root}\"\n"
                   . "    ProxyFCGISetEnvIf \"true\" SCRIPT_FILENAME \"{$doc_root}%{reqenv:SCRIPT_NAME}\"";

        if (count($fpm_containers) === 1) {
            // Single backend — direct proxy
            $host = $fpm_containers[0];
            return "<FilesMatch \\.php$>\n"
                 . "        SetHandler \"proxy:fcgi://{$host}:{$fpm_port}\"\n"
                 . "    </FilesMatch>\n\n"
                 . $env_lines;
        }

        // Multiple backends — use mod_proxy_balancer
        $safe_name = str_replace(['*', '.', '-'], ['wildcard', '_', '_'], $domain);
        $balancer_name = "fpm_{$safe_name}";

        $block = "<Proxy \"balancer://{$balancer_name}\">\n";
        foreach ($fpm_containers as $host) {
            $block .= "        BalancerMember \"fcgi://{$host}:{$fpm_port}\"\n";
        }
        $block .= "        ProxySet lbmethod=byrequests\n";
        $block .= "    </Proxy>\n\n";
        $block .= "    <FilesMatch \\.php$>\n";
        $block .= "        SetHandler \"proxy:balancer://{$balancer_name}\"\n";
        $block .= "    </FilesMatch>\n\n";
        $block .= $env_lines;

        return $block;
    }

    /**
     * Remove a vhost config for a domain
     *
     * @param string $domain Domain to remove
     * @return array         Result with success/error
     */
    public function removeVhost($domain) {
        if (!is_string($domain) || trim($domain) === '') {
            error_log('shared_httpd_manager::removeVhost called with empty domain — refusing.');
            return ['success' => false, 'error' => 'removeVhost: empty domain'];
        }
        $safe_domain = str_replace(['*', '.'], ['wildcard', '_'], $domain);
        $config_path = $this->vhost_dir . '/' . $safe_domain . '.conf';

        if (file_exists($config_path)) {
            if (unlink($config_path)) {
                error_log("Shared httpd: removed vhost config for $domain");
                return ['success' => true];
            } else {
                return ['success' => false, 'error' => 'Failed to remove vhost config: ' . $config_path];
            }
        }

        return ['success' => true, 'message' => 'Config did not exist'];
    }

    /**
     * Graceful reload all running shared-httpd instances
     *
     * @return array Result with success/error and per-instance results
     */
    public function reloadHttpd() {
        $results = [];
        $instances = $this->getRunningInstances();

        if (empty($instances)) {
            error_log("Shared httpd: no running shared-httpd instances to reload");
            return ['success' => true, 'message' => 'No shared-httpd instances running', 'results' => []];
        }

        $all_success = true;
        foreach ($instances as $instance) {
            $output = [];
            $return_code = 0;
            exec("docker exec $instance httpd -k graceful 2>&1", $output, $return_code);

            $instance_result = [
                'instance' => $instance,
                'success' => ($return_code === 0),
                'output' => implode("\n", $output)
            ];

            if ($return_code !== 0) {
                $all_success = false;
                error_log("Shared httpd: failed to reload $instance: " . implode(' ', $output));
            } else {
                error_log("Shared httpd: reloaded $instance");
            }

            $results[] = $instance_result;
        }

        return ['success' => $all_success, 'results' => $results];
    }

    /**
     * List all configured vhosts
     *
     * @return array List of domain names with their config file paths
     */
    public function listVhosts() {
        $vhosts = [];
        $files = glob($this->vhost_dir . '/*.conf');

        foreach ($files as $file) {
            $basename = basename($file, '.conf');
            // Reverse the safe filename transformation
            $domain = str_replace('_', '.', $basename);
            $domain = str_replace('wildcard', '*', $domain);
            $vhosts[] = [
                'domain' => $domain,
                'config_path' => $file,
                'size' => filesize($file),
                'modified' => filemtime($file)
            ];
        }

        return $vhosts;
    }

    /**
     * Get list of running shared-httpd container names
     *
     * @return array Container names
     */
    public function getRunningInstances() {
        $output = [];
        exec("docker ps --filter 'name=shared-httpd' --filter 'status=running' --format '{{.Names}}' 2>/dev/null", $output);
        return array_filter($output);
    }

    /**
     * Map a standalone PHP container type name to its FPM equivalent
     * e.g., "PHP 8.3" -> "PHP-FPM 8.3"
     *
     * @param string $standalone_name The standalone container type name
     * @return string|null            The FPM type name, or null if not a PHP type
     */
    public static function mapToFpmType($standalone_name) {
        if (preg_match('/^PHP (\d+\.\d+)$/', $standalone_name, $matches)) {
            return 'PHP-FPM ' . $matches[1];
        }
        return null;
    }

    /**
     * Map a FPM PHP container type name back to its standalone equivalent
     * e.g., "PHP-FPM 8.3" -> "PHP 8.3"
     *
     * @param string $fpm_name The FPM container type name
     * @return string|null     The standalone type name, or null if not a FPM type
     */
    public static function mapToStandaloneType($fpm_name) {
        if (preg_match('/^PHP-FPM (\d+\.\d+)$/', $fpm_name, $matches)) {
            return 'PHP ' . $matches[1];
        }
        return null;
    }

    /**
     * Check if a container type name is a PHP type (supports shared httpd mode)
     *
     * @param string $type_name Container type name
     * @return bool
     */
    public static function isPhpType($type_name) {
        return (bool)preg_match('/^PHP[\s-]/', $type_name);
    }
}
