<?php
/**
 * MySQL Management Library for Web Hosting Panel
 * Handles MySQL user creation, database management, and permissions
 */

class mysqlmgmt {
    
    private $db_host = '127.0.0.1';
    private $db_port = 3306;
    private $root_user = 'root';
    private $root_password = '';
    
    public function __construct() {
        // Use the MYSQL_PASS constant from config.php (loaded via auto-prepend)
        if (defined('MYSQL_PASS')) {
            $this->root_password = trim(MYSQL_PASS);
        } else {
            throw new \Exception('MYSQL_PASS constant is not defined. Please check your configuration.');
        }
    }
    
    /**
     * Get MySQL connection using root credentials
     */
    public function getMySQLConnection() {
        try {
            // Force TCP connection since MySQL is running in Docker
            $dsn = "mysql:host={$this->db_host};port={$this->db_port};charset=utf8mb4";
            $pdo = new PDO($dsn, $this->root_user, $this->root_password, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false
            ]);
            return $pdo;
        } catch (PDOException $e) {
            error_log("MySQL connection failed: " . $e->getMessage());
            return false;
        }
    }
    
    private function escapeIdent($str) {
        // Escape MySQL identifiers (user, host, db, table)
        return str_replace(["`", "'", "\\"], ["``", "''", "\\\\"], $str);
    }
    
    /**
     * Create a new MySQL user for a hosting panel user
     * 
     * @param string $username The username for the MySQL user
     * @param string $password The password for the MySQL user
     * @param string $host The host from which the user can connect (default: '%')
     * @return array Status array with success/error information
     */
    public function createMySQLUser($username, $password, $host = '%') {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        $user_esc = addslashes($username);
        $host_esc = addslashes($host);
        try {
            // Check if user already exists
            $stmt = $pdo->prepare("SELECT User FROM mysql.user WHERE User = ? AND Host = ?");
            $stmt->execute([$username, $host]);
            if ($stmt->fetch()) {
                return ['status' => '1', 'msg' => 'MySQL user already exists'];
            }
            
            // Create the user (identifiers and password must be interpolated)
            $password_esc = addslashes($password);
            $sql = "CREATE USER '" . $user_esc . "'@'" . $host_esc . "' IDENTIFIED BY '" . $password_esc . "'";
            $pdo->exec($sql);
            
            // Grant privileges on databases with user prefix
            $prefix = $this->escapeIdent($username) . '_%';
            $sql = "GRANT ALL PRIVILEGES ON `{$prefix}`.* TO '" . $user_esc . "'@'" . $host_esc . "'";
            $pdo->exec($sql);
            
            // Grant privileges on the user's own database (if it exists)
            $sql = "GRANT ALL PRIVILEGES ON `" . $this->escapeIdent($username) . "`.* TO '" . $user_esc . "'@'" . $host_esc . "'";
            $pdo->exec($sql);
            
            // Flush privileges
            $pdo->exec("FLUSH PRIVILEGES");
            
            return ['status' => '0', 'msg' => 'MySQL user created successfully'];
            
        } catch (PDOException $e) {
            error_log("MySQL user creation failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to create MySQL user: ' . $e->getMessage()];
        }
    }
    
    /**
     * Delete a MySQL user
     * 
     * @param string $username The username to delete
     * @param string $host The host (default: '%')
     * @return array Status array with success/error information
     */
    public function deleteMySQLUser($username, $host = '%') {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        $user_esc = addslashes($username);
        $host_esc = addslashes($host);
        try {
            // Check if user exists
            $stmt = $pdo->prepare("SELECT User FROM mysql.user WHERE User = ? AND Host = ?");
            $stmt->execute([$username, $host]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'MySQL user does not exist'];
            }
            
            // Drop the user
            $sql = "DROP USER '" . $user_esc . "'@'" . $host_esc . "'";
            $pdo->exec($sql);
            
            // Flush privileges
            $pdo->exec("FLUSH PRIVILEGES");
            
            return ['status' => '0', 'msg' => 'MySQL user deleted successfully'];
            
        } catch (PDOException $e) {
            error_log("MySQL user deletion failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to delete MySQL user: ' . $e->getMessage()];
        }
    }
    
    /**
     * Change MySQL user password
     * 
     * @param string $username The username
     * @param string $new_password The new password
     * @param string $host The host (default: '%')
     * @return array Status array with success/error information
     */
    public function changeMySQLPassword($username, $new_password, $host = '%') {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        $user_esc = addslashes($username);
        $host_esc = addslashes($host);
        try {
            // Check if user exists
            $stmt = $pdo->prepare("SELECT User FROM mysql.user WHERE User = ? AND Host = ?");
            $stmt->execute([$username, $host]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'MySQL user does not exist'];
            }
            
            // Change password
            $password_esc = addslashes($new_password);
            $sql = "ALTER USER '" . $user_esc . "'@'" . $host_esc . "' IDENTIFIED BY '" . $password_esc . "'";
            $pdo->exec($sql);
            
            // Flush privileges
            $pdo->exec("FLUSH PRIVILEGES");
            
            return ['status' => '0', 'msg' => 'MySQL password changed successfully'];
            
        } catch (PDOException $e) {
            error_log("MySQL password change failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to change MySQL password: ' . $e->getMessage()];
        }
    }
    
    /**
     * Create a new database
     * 
     * @param string $database_name The name of the database to create
     * @param string $owner_username The username who will own this database
     * @return array Status array with success/error information
     */
    public function createDatabase($database_name, $owner_username) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        $db_esc = $this->escapeIdent($database_name);
        $owner_esc = addslashes($owner_username);
        try {
            // Check if database already exists
            $stmt = $pdo->prepare("SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?");
            $stmt->execute([$database_name]);
            if ($stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Database already exists'];
            }
            
            // Create the database
            $sql = "CREATE DATABASE `{$db_esc}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
            $pdo->exec($sql);
            
            // Pre-flight: only issue GRANT to an owner that actually exists in
            // mysql.user. Historically this function unconditionally issued a
            // GRANT against '$owner'@'%', which lands in mysql.db even when no
            // matching mysql.user row exists -- producing "orphan grant" rows
            // that confuse audits and (worst case) silently inherit privileges
            // if a future user is created with the same name. The orphan
            // problem was most visible on cpanel imports, where the panel
            // username (e.g. "<cpuser>") is passed as the owner but the real
            // MySQL accounts are sub-users like <cpuser>_wpuser. Host MUST be
            // '%' (panel connections come from the docker-bridge IP, never
            // localhost) -- see feedback_whp_mysql_user_host_pattern.
            $check = $pdo->prepare("SELECT 1 FROM mysql.user WHERE User = ? AND Host = '%' LIMIT 1");
            $check->execute([$owner_username]);
            $ownerExists = (bool) $check->fetchColumn();

            if ($ownerExists) {
                $grantSql = "GRANT ALL PRIVILEGES ON `{$db_esc}`.* TO '" . $owner_esc . "'@'%'";
                $pdo->exec($grantSql);
                $pdo->exec("FLUSH PRIVILEGES");
                return ['status' => '0', 'msg' => 'Database created successfully'];
            }

            // No matching mysql.user row -- skip the GRANT rather than create
            // an orphan in mysql.db. The caller is expected to create the user
            // separately (e.g. via createMySQLUser or createDatabaseUser) and
            // grant scoped privileges then.
            error_log(sprintf(
                "mysqlmgmt::createDatabase: GRANT skipped for db '%s' -- owner '%s'@'%%' does not exist in mysql.user (would have produced an orphan grant). Caller must create the user separately.",
                $database_name,
                $owner_username
            ));
            return ['status' => '0', 'msg' => 'Database created successfully (grant skipped: owner user does not exist)'];
            
        } catch (PDOException $e) {
            error_log("Database creation failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to create database: ' . $e->getMessage()];
        }
    }
    
    /**
     * Delete a database
     * 
     * @param string $database_name The name of the database to delete
     * @return array Status array with success/error information
     */
    public function deleteDatabase($database_name) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        $db_esc = $this->escapeIdent($database_name);
        try {
            // Check if database exists
            $stmt = $pdo->prepare("SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?");
            $stmt->execute([$database_name]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Database does not exist'];
            }
            
            // Drop the database
            $sql = "DROP DATABASE `{$db_esc}`";
            $pdo->exec($sql);
            
            return ['status' => '0', 'msg' => 'Database deleted successfully'];
            
        } catch (PDOException $e) {
            error_log("Database deletion failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to delete database: ' . $e->getMessage()];
        }
    }
    
    /**
     * Create a new database user (sub-user for a hosting panel user)
     * 
     * @param string $username The username for the database user
     * @param string $password The password for the database user
     * @param string $database_name The database this user will have access to
     * @param array $privileges Array of privileges to grant (default: ['ALL'])
     * @param string $host The host (default: '%')
     * @return array Status array with success/error information
     */
    public function createDatabaseUser($username, $password, $database_name, $privileges = ['ALL'], $host = '%') {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        $user_esc = addslashes($username);
        $host_esc = addslashes($host);
        $db_esc = $this->escapeIdent($database_name);
        try {
            // Check if database exists
            $stmt = $pdo->prepare("SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?");
            $stmt->execute([$database_name]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Database does not exist'];
            }

            // Always drop user if exists to avoid 1396 error
            $sql = "DROP USER IF EXISTS '" . $user_esc . "'@'" . $host_esc . "'";
            $pdo->exec($sql);

            // Create the user
            $password_esc = addslashes($password);
            $sql = "CREATE USER '" . $user_esc . "'@'" . $host_esc . "' IDENTIFIED BY '" . $password_esc . "'";
            $pdo->exec($sql);

            // Grant privileges
            $privileges = array_map('strtoupper', $privileges);
            if (in_array('ALL', $privileges)) {
                $privilege_list = 'ALL PRIVILEGES';
            } else {
                $privilege_list = implode(', ', $privileges);
            }
            $sql = "GRANT {$privilege_list} ON `{$db_esc}`.* TO '" . $user_esc . "'@'" . $host_esc . "'";
            $pdo->exec($sql);

            // Flush privileges
            $pdo->exec("FLUSH PRIVILEGES");

            return ['status' => '0', 'msg' => 'Database user created successfully'];

        } catch (PDOException $e) {
            error_log("Database user creation failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to create database user: ' . $e->getMessage()];
        }
    }
    
    /**
     * Delete a database user
     * 
     * @param string $username The username to delete
     * @param string $host The host (default: '%')
     * @return array Status array with success/error information
     */
    public function deleteDatabaseUser($username, $host = '%') {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        $user_esc = addslashes($username);
        $host_esc = addslashes($host);
        try {
            // Check if user exists
            $stmt = $pdo->prepare("SELECT User FROM mysql.user WHERE User = ? AND Host = ?");
            $stmt->execute([$username, $host]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Database user does not exist'];
            }
            
            // Drop the user
            $sql = "DROP USER '" . $user_esc . "'@'" . $host_esc . "'";
            $pdo->exec($sql);
            
            // Flush privileges
            $pdo->exec("FLUSH PRIVILEGES");
            
            return ['status' => '0', 'msg' => 'Database user deleted successfully'];
            
        } catch (PDOException $e) {
            error_log("Database user deletion failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to delete database user: ' . $e->getMessage()];
        }
    }
    
    /**
     * Update database user privileges
     * 
     * @param string $username The username
     * @param string $database_name The database name
     * @param array $privileges Array of privileges to grant
     * @param string $host The host (default: '%')
     * @return array Status array with success/error information
     */
    public function updateDatabaseUserPrivileges($username, $database_name, $privileges, $host = '%') {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        $user_esc = addslashes($username);
        $host_esc = addslashes($host);
        $db_esc = $this->escapeIdent($database_name);
        try {
            // Check if user exists
            $stmt = $pdo->prepare("SELECT User FROM mysql.user WHERE User = ? AND Host = ?");
            $stmt->execute([$username, $host]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Database user does not exist'];
            }
            
            // Check if database exists
            $stmt = $pdo->prepare("SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?");
            $stmt->execute([$database_name]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Database does not exist'];
            }
            
            // Revoke all privileges first
            $sql = "REVOKE ALL PRIVILEGES ON `{$db_esc}`.* FROM '" . $user_esc . "'@'" . $host_esc . "'";
            $pdo->exec($sql);
            
            // Grant new privileges
            $privileges = array_map('strtoupper', $privileges);
            if (in_array('ALL', $privileges)) {
                $privilege_list = 'ALL PRIVILEGES';
            } else {
                $privilege_list = implode(', ', $privileges);
            }
            $sql = "GRANT {$privilege_list} ON `{$db_esc}`.* TO '" . $user_esc . "'@'" . $host_esc . "'";
            $pdo->exec($sql);
            
            // Flush privileges
            $pdo->exec("FLUSH PRIVILEGES");
            
            return ['status' => '0', 'msg' => 'Database user privileges updated successfully'];
            
        } catch (PDOException $e) {
            error_log("Database user privileges update failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to update database user privileges: ' . $e->getMessage()];
        }
    }
    
    /**
     * List all databases for a user
     * 
     * @param string $username The username
     * @return array Array of databases the user has access to
     */
    public function listUserDatabases($username) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            if ($username === 'root') {
                // Root can see all databases except system databases
                $stmt = $pdo->prepare("
                    SELECT SCHEMA_NAME 
                    FROM information_schema.SCHEMATA 
                    WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
                    ORDER BY SCHEMA_NAME
                ");
                $stmt->execute();
            } else {
                // Regular users can only see their own prefixed databases
                $user_prefix = $username . '_';
                $stmt = $pdo->prepare("
                    SELECT SCHEMA_NAME 
                    FROM information_schema.SCHEMATA 
                    WHERE SCHEMA_NAME LIKE ? AND SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
                    ORDER BY SCHEMA_NAME
                ");
                $stmt->execute([$user_prefix . '%']);
            }
            
            $databases = $stmt->fetchAll();
            
            return ['status' => '0', 'databases' => array_column($databases, 'SCHEMA_NAME')];
            
        } catch (PDOException $e) {
            error_log("Database listing failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to list databases: ' . $e->getMessage()];
        }
    }
    
    /**
     * List all database users for a specific database
     * 
     * @param string $database_name The database name
     * @return array Array of users with access to the database
     */
    public function listDatabaseUsers($database_name) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            $stmt = $pdo->prepare("
                SELECT DISTINCT u.User, u.Host, d.Db_priv, d.Select_priv, d.Insert_priv, d.Update_priv, d.Delete_priv, d.Create_priv, d.Drop_priv, d.Grant_priv
                FROM mysql.user u
                JOIN mysql.db d ON u.User = d.User AND u.Host = d.Host
                WHERE d.Db = ? AND u.User != 'root'
                ORDER BY u.User, u.Host
            ");
            $stmt->execute([$database_name]);
            $users = $stmt->fetchAll();
            
            return ['status' => '0', 'users' => $users];
            
        } catch (PDOException $e) {
            error_log("Database users listing failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to list database users: ' . $e->getMessage()];
        }
    }
    
    /**
     * List database users that a specific user can manage
     * 
     * @param string $username The username requesting the list
     * @return array Array of database users the user can manage
     */
    public function listManageableDatabaseUsers($username) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            if ($username === 'root') {
                // Root can see all database users except root itself and system users
                $stmt = $pdo->prepare("SELECT User, Host FROM mysql.user WHERE User != 'root' AND User NOT IN ('mysql.session', 'mysql.sys', 'debian-sys-maint') ORDER BY User, Host");
                $stmt->execute();
            } else {
                // Regular users can only see their own prefixed database users (not their main account)
                $user_prefix = $username . '_';
                $stmt = $pdo->prepare("SELECT User, Host FROM mysql.user WHERE User LIKE ? AND User != ? AND User != 'root' ORDER BY User, Host");
                $stmt->execute([$user_prefix . '%', $username]);
            }
            
            $users = $stmt->fetchAll();
            
            // Get database access for each user
            foreach ($users as &$user) {
                $stmt = $pdo->prepare("SELECT Db FROM mysql.db WHERE User = ? AND Host = ? ORDER BY Db");
                $stmt->execute([$user['User'], $user['Host']]);
                $databases = $stmt->fetchAll();
                $user['Databases'] = implode(', ', array_column($databases, 'Db'));
            }
            
            return ['status' => '0', 'users' => $users];
            
        } catch (PDOException $e) {
            error_log("Manageable database users listing failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to list manageable database users: ' . $e->getMessage()];
        }
    }
    
    /**
     * Check if a user can manage a specific database user
     * 
     * @param string $requesting_user The user making the request
     * @param string $target_user The database user to check
     * @return bool True if the user can manage the target user
     */
    public function canManageDatabaseUser($requesting_user, $target_user) {
        // Root can manage any database user (except root itself)
        if ($requesting_user === 'root') {
            return $target_user !== 'root';
        }
        
        // Regular users can only manage their own prefixed database users (not their main account)
        if (strpos($target_user, $requesting_user . '_') === 0 && $target_user !== $requesting_user) {
            return true;
        }
        
        return false;
    }
    
    /**
     * List databases that a specific user can manage (for deletion)
     * 
     * @param string $username The username requesting the list
     * @return array Array of databases the user can manage
     */
    public function listManageableDatabases($username) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            if ($username === 'root') {
                // Root can see all databases except system databases
                $stmt = $pdo->prepare("
                    SELECT SCHEMA_NAME 
                    FROM information_schema.SCHEMATA 
                    WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
                    ORDER BY SCHEMA_NAME
                ");
                $stmt->execute();
            } else {
                // Regular users can only see their own prefixed databases
                $user_prefix = $username . '_';
                $stmt = $pdo->prepare("
                    SELECT SCHEMA_NAME 
                    FROM information_schema.SCHEMATA 
                    WHERE SCHEMA_NAME LIKE ? AND SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
                    ORDER BY SCHEMA_NAME
                ");
                $stmt->execute([$user_prefix . '%']);
            }
            
            $databases = $stmt->fetchAll();
            
            return ['status' => '0', 'databases' => array_column($databases, 'SCHEMA_NAME')];
            
        } catch (PDOException $e) {
            error_log("Manageable databases listing failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to list manageable databases: ' . $e->getMessage()];
        }
    }
    
    /**
     * Check if a user can manage a specific database
     * 
     * @param string $requesting_user The user making the request
     * @param string $target_database The database to check
     * @return bool True if the user can manage the target database
     */
    public function canManageDatabase($requesting_user, $target_database) {
        // Root can manage any database
        if ($requesting_user === 'root') {
            return true;
        }
        
        // Regular users can only manage their own databases
        if (strpos($target_database, $requesting_user . '_') === 0) {
            return true;
        }
        
        return false;
    }
    
    /**
     * Get database size information
     * 
     * @param string $database_name The database name
     * @return array Database size information
     */
    public function getDatabaseSize($database_name) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            $stmt = $pdo->prepare("
                SELECT 
                    ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'size_mb',
                    ROUND(SUM(data_length) / 1024 / 1024, 2) AS 'data_size_mb',
                    ROUND(SUM(index_length) / 1024 / 1024, 2) AS 'index_size_mb',
                    COUNT(*) AS 'table_count'
                FROM information_schema.tables 
                WHERE table_schema = ?
            ");
            $stmt->execute([$database_name]);
            $result = $stmt->fetch();
            
            return ['status' => '0', 'size_info' => $result];
            
        } catch (PDOException $e) {
            error_log("Database size query failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to get database size: ' . $e->getMessage()];
        }
    }
    
    /**
     * Test MySQL connection with given credentials
     * 
     * @param string $username The username to test
     * @param string $password The password to test
     * @param string $database The database to connect to (optional)
     * @return array Status array with success/error information
     */
    public function testConnection($username, $password, $database = null) {
        try {
            $dsn = "mysql:host={$this->db_host};port={$this->db_port};charset=utf8mb4";
            if ($database) {
                $dsn .= ";dbname={$database}";
            }
            
            $pdo = new PDO($dsn, $username, $password, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false
            ]);
            
            return ['status' => '0', 'msg' => 'Connection successful'];
            
        } catch (PDOException $e) {
            return ['status' => '1', 'msg' => 'Connection failed: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get or create client resource allowances
     * 
     * @param string $username The system username
     * @return array Resource allowance information
     */
    public function getClientAllowances($username) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            // Check if allowances exist for this user
            $stmt = $pdo->prepare("SELECT * FROM whp.client_allowances WHERE username = ?");
            $stmt->execute([$username]);
            $allowances = $stmt->fetch();
            
            if (!$allowances) {
                // Create default allowances
                $stmt = $pdo->prepare("
                    INSERT INTO whp.client_allowances (username, max_cpu_allowance, max_memory_allowance, max_domains, max_disk_space, max_email_accounts, max_email_storage_mb) 
                    VALUES (?, 0.5, 512, 1, 1000, 5, 1000)
                ");
                $stmt->execute([$username]);
                
                // Fetch the newly created allowances
                $stmt = $pdo->prepare("SELECT * FROM whp.client_allowances WHERE username = ?");
                $stmt->execute([$username]);
                $allowances = $stmt->fetch();
            }
            
            return ['status' => '0', 'allowances' => $allowances];
            
        } catch (PDOException $e) {
            error_log("Get client allowances failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to get client allowances: ' . $e->getMessage()];
        }
    }
    
    /**
     * Update client resource allowances
     * 
     * @param string $username The system username
     * @param float $max_cpu_allowance Maximum CPU cores
     * @param int $max_memory_allowance Maximum memory in MB
     * @param int $max_domains Maximum number of domains (optional)
     * @param int $max_disk_space Maximum disk space in MB (optional)
     * @param int $max_email_accounts Maximum email accounts (optional)
     * @param int $max_email_storage_mb Maximum email storage in MB (optional)
     * @param string $account_type Account type ('full' | 'domain_dns' | 'mail_dns')
     * @param int $max_archived_mailboxes Max Bichon archival slots (optional)
     * @param int|null $uncapped_resources When 1, CPU/memory caps are not enforced for this user. When null, the column is not written (caller doesn't care). When 0 or 1, the value is persisted.
     * @return array Status array with success/error information
     */
    public function updateClientAllowances($username, $max_cpu_allowance, $max_memory_allowance, $max_domains = null, $max_disk_space = null, $max_email_accounts = null, $max_email_storage_mb = null, $account_type = 'full', $max_archived_mailboxes = null, $uncapped_resources = null, $sitesmith_enabled = null) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }

        try {
            // Validate inputs
            if ($max_cpu_allowance < 0.25 || $max_cpu_allowance > 8) {
                return ['status' => '1', 'msg' => 'CPU allowance must be between 0.25 and 8 cores'];
            }

            if ($max_memory_allowance < 256 || $max_memory_allowance > 8192) {
                return ['status' => '1', 'msg' => 'Memory allowance must be between 256 MB and 8 GB'];
            }

            // Validate account_type
            if (!in_array($account_type, ['full', 'domain_dns', 'mail_dns'])) {
                $account_type = 'full';
            }

            // Build column/value lists dynamically so $uncapped_resources can be
            // omitted (null = "don't touch") without a separate code path.
            $columns = ['username', 'max_cpu_allowance', 'max_memory_allowance', 'max_domains', 'max_disk_space', 'max_email_accounts', 'max_email_storage_mb', 'account_type', 'max_archived_mailboxes'];
            $placeholders = ['?', '?', '?', '1', '?', '?', '?', '?', '?'];
            $params = [
                $username,
                $max_cpu_allowance,
                $max_memory_allowance,
                $max_disk_space ?? 1000,
                $max_email_accounts ?? 0,
                $max_email_storage_mb ?? 1000,
                $account_type,
                max(0, intval($max_archived_mailboxes ?? 0)),
            ];
            $updates = [
                'max_cpu_allowance = VALUES(max_cpu_allowance)',
                'max_memory_allowance = VALUES(max_memory_allowance)',
                'max_disk_space = VALUES(max_disk_space)',
                'max_email_accounts = VALUES(max_email_accounts)',
                'max_email_storage_mb = VALUES(max_email_storage_mb)',
                'account_type = VALUES(account_type)',
                'max_archived_mailboxes = VALUES(max_archived_mailboxes)',
            ];

            if ($uncapped_resources !== null) {
                $columns[] = 'uncapped_resources';
                $placeholders[] = '?';
                $params[] = $uncapped_resources ? 1 : 0;
                $updates[] = 'uncapped_resources = VALUES(uncapped_resources)';
            }

            if ($sitesmith_enabled !== null) {
                $columns[] = 'sitesmith_enabled';
                $placeholders[] = '?';
                $params[] = $sitesmith_enabled ? 1 : 0;
                $updates[] = 'sitesmith_enabled = VALUES(sitesmith_enabled)';
            }

            $sql = 'INSERT INTO whp.client_allowances (' . implode(', ', $columns) . ') '
                 . 'VALUES (' . implode(', ', $placeholders) . ') '
                 . 'ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            
            // Update filesystem quota to match disk space allocation
            require_once('/docker/whp/web/libs/usermgmt.php');
            $UserMgmt = new usermgmt();
            $quota_result = $UserMgmt->setUserQuota($username, $max_disk_space ?? 1000);
            
            if (!$quota_result) {
                error_log("Failed to update filesystem quota for $username");
            }
            
            return ['status' => '0', 'msg' => 'Client allowances updated successfully'];
            
        } catch (PDOException $e) {
            error_log("Update client allowances failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to update client allowances: ' . $e->getMessage()];
        }
    }

    /**
     * Effective CPU/memory ceilings for a user. When the user is uncapped,
     * ceilings come from host totals; otherwise from the stored caps.
     *
     * @param string $username
     * @return array ['cpu_limit' => float, 'memory_limit' => int, 'uncapped' => bool]
     */
    public function getEffectiveLimits(string $username): array {
        require_once('/docker/whp/web/libs/host_resources.php');

        // Root is the panel admin, not a hosting customer — it should never
        // be subject to client_allowances caps (or to the 0.5-core / 512-MB
        // safety fallback below when no row exists). Treat it as uncapped
        // host totals regardless of DB state. Customer admins remain bound
        // by their allowance row.
        if ($username === 'root') {
            return [
                'cpu_limit'    => (float) whp_host_cpu_cores(),
                'memory_limit' => whp_host_memory_mb(),
                'uncapped'     => true,
            ];
        }

        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return [
                'cpu_limit'    => 0.5,
                'memory_limit' => 512,
                'uncapped'     => false,
            ];
        }

        $stmt = $pdo->prepare(
            "SELECT max_cpu_allowance, max_memory_allowance, uncapped_resources
               FROM whp.client_allowances
              WHERE username = ?"
        );
        $stmt->execute([$username]);
        $row = $stmt->fetch();

        if (!$row) {
            return [
                'cpu_limit'    => 0.5,
                'memory_limit' => 512,
                'uncapped'     => false,
            ];
        }

        $uncapped = !empty($row['uncapped_resources']);
        return [
            'cpu_limit'    => $uncapped ? (float)whp_host_cpu_cores() : (float)$row['max_cpu_allowance'],
            'memory_limit' => $uncapped ? whp_host_memory_mb()         : (int)$row['max_memory_allowance'],
            'uncapped'     => $uncapped,
        ];
    }

    /**
     * Get all system users with their resource allocations
     *
     * @return array List of system users with their resource information
     */
    public function getAllSystemUsersWithResources() {
        try {
            // Get all system users from /etc/passwd (excluding system accounts)
            $users = [];
            $passwd_content = file_get_contents('/etc/passwd');
            $lines = explode("\n", $passwd_content);
            
            // Define system users that should be excluded from hosting user management
            $excluded_system_users = [
                'nobody', 'whp', 'root', 'daemon', 'bin', 'sys', 'sync', 'games', 'man', 'lp', 'mail',
                'news', 'uucp', 'proxy', 'www-data', 'backup', 'list', 'irc', 'gnats', 'systemd-network',
                'systemd-resolve', 'systemd-timesync', 'messagebus', 'syslog', '_apt', 'tss', 'uuidd',
                'tcpdump', 'avahi-autoipd', 'usbmux', 'rtkit', 'dnsmasq', 'cups-pk-helper', 
                'speech-dispatcher', 'avahi', 'kernoops', 'saned', 'nm-openvpn', 'hplip', 'whoopsie',
                'colord', 'geoclue', 'pulse', 'gnome-initial-setup', 'gdm', 'mysql', 'postgres',
                'redis', 'mongodb', 'nginx', 'apache', 'www', 'httpd', 'php-fpm', 'docker'
            ];
            
            foreach ($lines as $line) {
                if (empty($line)) continue;
                
                $parts = explode(':', $line);
                if (count($parts) >= 6) {
                    $username = $parts[0];
                    $uid = intval($parts[2]);
                    $home_dir = $parts[5];
                    
                    // Skip system users
                    if (in_array($username, $excluded_system_users)) {
                        continue;
                    }
                    
                    // Only include hosting users with UID >= 1000 or specifically created hosting users
                    if ($uid >= 1000 || strpos($home_dir, '/docker/users/') === 0) {
                        $users[] = [
                            'username' => $username,
                            'uid' => $uid,
                            'home_dir' => $home_dir,
                            'created_at' => date('Y-m-d H:i:s') // We can't get real creation time from passwd
                        ];
                    }
                }
            }
            
            // Now get resource allowances for each user
            $pdo = $this->getMySQLConnection();
            if ($pdo) {
                foreach ($users as &$user) {
                    $stmt = $pdo->prepare("SELECT * FROM whp.client_allowances WHERE username = ?");
                    $stmt->execute([$user['username']]);
                    $allowances = $stmt->fetch();
                    
                    if ($allowances) {
                        $user = array_merge($user, $allowances);
                    } else {
                        // Set default values
                        $user['max_cpu_allowance'] = 0.5;
                        $user['max_memory_allowance'] = 512;
                        $user['max_domains'] = 1;
                        $user['max_disk_space'] = 1000;
                        $user['max_email_accounts'] = 0;
                        $user['max_email_storage_mb'] = 1000;
                        $user['max_archived_mailboxes'] = 0;
                    }
                }
            }
            
            return ['status' => '0', 'users' => $users];
            
        } catch (Exception $e) {
            error_log("Get system users failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to get system users: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get current resource usage for a user
     * 
     * @param string $username The system username
     * @return array Current resource usage information
     */
    public function getUserResourceUsage($username) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return [
                'containers' => 0,
                'cpu_used' => 0,
                'memory_used' => 0,
                'domains' => 0
            ];
        }
        
        try {
            // Get resource usage from sites table (new sites management) - count all container resources
            $stmt = $pdo->prepare("
                SELECT 
                    COUNT(*) as site_count,
                    COALESCE(SUM(container_count * cpu_per_container), 0) as site_cpu_used,
                    COALESCE(SUM(container_count * memory_per_container), 0) as site_memory_used
                FROM whp.sites 
                WHERE username = ? AND active = 1
            ");
            $stmt->execute([$username]);
            $site_usage = $stmt->fetch();
            
            // Get resource usage from legacy containers (domains with containers but not assigned to sites)
            $stmt = $pdo->prepare("
                SELECT 
                    COALESCE(SUM(d.cpu_allowance), 0) as legacy_cpu_used,
                    COALESCE(SUM(d.memory_allowance), 0) as legacy_memory_used
                FROM whp.domains d
                INNER JOIN whp.containers c ON d.id = c.domain_id
                WHERE d.username = ?
                  AND d.id NOT IN (SELECT DISTINCT domain_id FROM whp.site_domains)
            ");
            $stmt->execute([$username]);
            $legacy_usage = $stmt->fetch();
            
            // Get total domain count (including those in sites)
            $stmt = $pdo->prepare("
                SELECT COUNT(*) as total_domains
                FROM whp.domains 
                WHERE username = ?
            ");
            $stmt->execute([$username]);
            $total_domains = $stmt->fetch();
            
            // Get running container count from both legacy containers and site containers
            $stmt = $pdo->prepare("
                SELECT 
                    (SELECT COUNT(*) FROM whp.containers c
                     INNER JOIN whp.domains d ON c.domain_id = d.id
                     WHERE d.username = ? AND c.status = 'running') +
                    (SELECT COUNT(*) FROM whp.site_containers sc
                     INNER JOIN whp.sites s ON sc.site_id = s.id
                     WHERE s.username = ? AND sc.status = 'running') as total_containers
            ");
            $stmt->execute([$username, $username]);
            $container_info = $stmt->fetch();
            
            // Calculate total resource usage (only count container resources, not domains without containers)
            $total_cpu_used = floatval($site_usage['site_cpu_used'] ?? 0) + floatval($legacy_usage['legacy_cpu_used'] ?? 0);
            $total_memory_used = intval($site_usage['site_memory_used'] ?? 0) + intval($legacy_usage['legacy_memory_used'] ?? 0);

            // Add Valkey memory (if enabled) to the RAM quota sum.
            // Spec: docs/superpowers/specs/2026-04-20-customer-valkey-design.md
            $valkey_mem = 0;
            try {
                $vStmt = $pdo->prepare("SELECT COALESCE(memory_mb, 0) AS mem FROM whp.customer_valkey WHERE username = ? AND enabled = 1");
                $vStmt->execute([$username]);
                $vRow = $vStmt->fetch(PDO::FETCH_ASSOC);
                if ($vRow) $valkey_mem = intval($vRow['mem']);
            } catch (PDOException $e) {
                // customer_valkey table may not exist yet during migration; treat as 0.
                error_log("valkey quota read: " . $e->getMessage());
            }
            $total_memory_used += $valkey_mem;

            // Get filesystem quota usage
            require_once('/docker/whp/web/libs/usermgmt.php');
            $UserMgmt = new usermgmt();
            $quota_usage = $UserMgmt->getUserQuotaUsage($username);
            
            // Get email account count and storage usage
            $stmt = $pdo->prepare("
                SELECT
                    COUNT(*) as email_count,
                    COALESCE(SUM(quota_mb), 0) as email_storage_used_mb,
                    SUM(CASE WHEN archival_enabled = 1 THEN 1 ELSE 0 END) as archived_count
                FROM whp.email_accounts
                WHERE username = ? AND active = 1
            ");
            $stmt->execute([$username]);
            $email_info = $stmt->fetch();

            return [
                'containers' => intval($container_info['total_containers'] ?? 0),
                'cpu_used' => $total_cpu_used,
                'memory_used' => $total_memory_used,
                'domains' => intval($total_domains['total_domains'] ?? 0),
                'sites' => intval($site_usage['site_count'] ?? 0),
                'email_accounts' => intval($email_info['email_count'] ?? 0),
                'email_storage_mb' => floatval($email_info['email_storage_used_mb'] ?? 0),
                'archived_mailboxes' => intval($email_info['archived_count'] ?? 0),
                'disk_used_mb' => $quota_usage['used_mb'],
                'disk_used_kb' => $quota_usage['used_kb'],
                'disk_quota_mb' => $quota_usage['hard_limit_mb'],
                'disk_quota_kb' => $quota_usage['hard_limit_kb']
            ];
            
        } catch (PDOException $e) {
            error_log("Get user resource usage failed: " . $e->getMessage());
            return [
                'containers' => 0,
                'cpu_used' => 0,
                'memory_used' => 0,
                'domains' => 0,
                'sites' => 0
            ];
        }
    }
    
    /**
     * List all container types with usage count
     * 
     * @return array Status array with container types data
     */
    public function listContainerTypes() {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            $stmt = $pdo->query("
                SELECT ct.*, 
                       (COUNT(DISTINCT d.id) + COUNT(DISTINCT s.id)) as usage_count,
                       CASE 
                           WHEN ct.last_image_pull IS NULL THEN 'Never'
                           ELSE DATE_FORMAT(ct.last_image_pull, '%Y-%m-%d %H:%i')
                       END as last_pull_formatted
                FROM whp.container_types ct
                LEFT JOIN whp.domains d ON ct.id = d.container_type_id
                LEFT JOIN whp.sites s ON ct.id = s.container_type_id
                GROUP BY ct.id
                ORDER BY ct.name ASC
            ");
            $container_types = $stmt->fetchAll();
            
            return ['status' => '0', 'container_types' => $container_types];
            
        } catch (PDOException $e) {
            error_log("List container types failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to list container types: ' . $e->getMessage()];
        }
    }
    
    /**
     * Create a new container type
     * 
     * @param string $name Container type name
     * @param string $description Container type description
     * @param string $base_image Docker base image
     * @param float $min_cpu Minimum CPU cores required
     * @param int $min_memory Minimum memory in MB required
     * @param array $startup_env Environment variables for startup
     * @param array $user_variables User configurable variables
     * @param array $mount_options Mount options and volumes
     * @param int $listen_port The HAProxy listen port for this container type
     * @param int $listen_port_tls The HAProxy TLS listen port for this container type
     * @param int $is_premium 1 if this type is gated behind client_allowances.litespeed_enabled
     * @return array Status array with success/error information
     */
    public function createContainerType($name, $description, $base_image, $min_cpu, $min_memory, $startup_env = [], $user_variables = [], $mount_options = [], $listen_port = 80, $listen_port_tls = 0, $is_premium = 0) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            // Validate inputs
            if (empty($name) || empty($base_image)) {
                return ['status' => '1', 'msg' => 'Name and base image are required'];
            }
            
            if ($min_cpu < 0.25 || $min_cpu > 8) {
                return ['status' => '1', 'msg' => 'Minimum CPU must be between 0.25 and 8 cores'];
            }
            
            if ($min_memory < 128 || $min_memory > 8192) {
                return ['status' => '1', 'msg' => 'Minimum memory must be between 128 MB and 8 GB'];
            }
            
            if ($listen_port < 1 || $listen_port > 65535) {
                return ['status' => '1', 'msg' => 'Listen port must be between 1 and 65535'];
            }
            
            // Check if container type name already exists
            $stmt = $pdo->prepare("SELECT id FROM whp.container_types WHERE name = ?");
            $stmt->execute([$name]);
            if ($stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Container type name already exists'];
            }
            
            $stmt = $pdo->prepare("
                INSERT INTO whp.container_types (name, description, base_image, min_cpu, min_memory, startup_env, user_variables, mount_options, listen_port, listen_port_tls, is_premium, active)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
            ");
            $stmt->execute([
                $name,
                $description,
                $base_image,
                $min_cpu,
                $min_memory,
                json_encode($startup_env),
                json_encode($user_variables),
                json_encode($mount_options),
                $listen_port,
                $listen_port_tls,
                $is_premium ? 1 : 0
            ]);
            
            $container_type_id = $pdo->lastInsertId();
            
            return ['status' => '0', 'msg' => 'Container type created successfully', 'id' => $container_type_id];
            
        } catch (PDOException $e) {
            error_log("Create container type failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to create container type: ' . $e->getMessage()];
        }
    }
    
    /**
     * Update an existing container type
     * 
     * @param int $id Container type ID
     * @param array $updates Array of fields to update
     * @return array Status array with success/error information
     */
    public function updateContainerType($id, $updates) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            // Check if container type exists
            $stmt = $pdo->prepare("SELECT id FROM whp.container_types WHERE id = ?");
            $stmt->execute([$id]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Container type not found'];
            }
            
            $allowed_fields = ['name', 'description', 'base_image', 'min_cpu', 'min_memory', 'startup_env', 'user_variables', 'mount_options', 'listen_port', 'listen_port_tls', 'is_premium', 'active'];
            $update_parts = [];
            $params = [];
            
            foreach ($updates as $field => $value) {
                if (in_array($field, $allowed_fields)) {
                    $update_parts[] = "$field = ?";
                    if (in_array($field, ['startup_env', 'user_variables', 'mount_options']) && is_array($value)) {
                        $params[] = json_encode($value);
                    } else {
                        $params[] = $value;
                    }
                }
            }
            
            if (empty($update_parts)) {
                return ['status' => '1', 'msg' => 'No valid fields to update'];
            }
            
            $params[] = $id;
            $sql = "UPDATE whp.container_types SET " . implode(", ", $update_parts) . " WHERE id = ?";
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            
            return ['status' => '0', 'msg' => 'Container type updated successfully'];
            
        } catch (PDOException $e) {
            error_log("Update container type failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to update container type: ' . $e->getMessage()];
        }
    }
    
    /**
     * Delete a container type
     * 
     * @param int $id Container type ID
     * @return array Status array with success/error information
     */
    public function deleteContainerType($id) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            // Check if container type is in use by domains or sites
            $stmt = $pdo->prepare("
                SELECT 
                    (SELECT COUNT(*) FROM whp.domains WHERE container_type_id = ?) as domain_count,
                    (SELECT COUNT(*) FROM whp.sites WHERE container_type_id = ?) as site_count
            ");
            $stmt->execute([$id, $id]);
            $usage = $stmt->fetch();
            
            $total_usage = intval($usage['domain_count']) + intval($usage['site_count']);
            if ($total_usage > 0) {
                $usage_details = [];
                if ($usage['domain_count'] > 0) {
                    $usage_details[] = $usage['domain_count'] . ' domain(s)';
                }
                if ($usage['site_count'] > 0) {
                    $usage_details[] = $usage['site_count'] . ' site(s)';
                }
                return ['status' => '1', 'msg' => 'Cannot delete container type that is in use by ' . implode(' and ', $usage_details)];
            }
            
            // Delete the container type
            $stmt = $pdo->prepare("DELETE FROM whp.container_types WHERE id = ?");
            $stmt->execute([$id]);
            
            if ($stmt->rowCount() === 0) {
                return ['status' => '1', 'msg' => 'Container type not found'];
            }
            
            return ['status' => '0', 'msg' => 'Container type deleted successfully'];
            
        } catch (PDOException $e) {
            error_log("Delete container type failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to delete container type: ' . $e->getMessage()];
        }
    }
    
    /**
     * Toggle container type active status
     * 
     * @param int $id Container type ID
     * @return array Status array with success/error information
     */
    public function toggleContainerTypeStatus($id) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            $stmt = $pdo->prepare("UPDATE whp.container_types SET active = NOT active WHERE id = ?");
            $stmt->execute([$id]);
            
            if ($stmt->rowCount() === 0) {
                return ['status' => '1', 'msg' => 'Container type not found'];
            }
            
            return ['status' => '0', 'msg' => 'Container type status updated successfully'];
            
        } catch (PDOException $e) {
            error_log("Toggle container type status failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to update container type status: ' . $e->getMessage()];
        }
    }
    
    /**
     * Update image pull timestamp for a container type
     * 
     * @param string $base_image Base image name
     * @return array Status array with success/error information
     */
    public function updateImagePullTime($base_image) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            $stmt = $pdo->prepare("UPDATE whp.container_types SET last_image_pull = CURRENT_TIMESTAMP WHERE base_image = ?");
            $stmt->execute([$base_image]);
            
            return ['status' => '0', 'msg' => 'Image pull time updated successfully'];
            
        } catch (PDOException $e) {
            error_log("Update image pull time failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to update image pull time: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get container type by ID
     * 
     * @param int $id Container type ID
     * @return array Status array with container type data
     */
    public function getContainerType($id) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            $stmt = $pdo->prepare("SELECT * FROM whp.container_types WHERE id = ?");
            $stmt->execute([$id]);
            $container_type = $stmt->fetch();
            
            if (!$container_type) {
                return ['status' => '1', 'msg' => 'Container type not found'];
            }
            
            // Decode JSON fields
            if (!empty($container_type['startup_env'])) {
                $decoded = json_decode($container_type['startup_env'], true);
                $container_type['startup_env'] = $decoded !== null ? $decoded : [];
            } else {
                $container_type['startup_env'] = [];
            }
            
            if (!empty($container_type['user_variables'])) {
                $decoded = json_decode($container_type['user_variables'], true);
                $container_type['user_variables'] = $decoded !== null ? $decoded : [];
            } else {
                $container_type['user_variables'] = [];
            }
            
            if (!empty($container_type['mount_options'])) {
                $decoded = json_decode($container_type['mount_options'], true);
                $container_type['mount_options'] = $decoded !== null ? $decoded : [];
            } else {
                $container_type['mount_options'] = [];
            }
            
            return ['status' => '0', 'container_type' => $container_type];
            
        } catch (PDOException $e) {
            error_log("Get container type failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to get container type: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get container type usage count
     * 
     * @param int $id Container type ID
     * @return array Usage count information
     */
    public function getContainerTypeUsage($id) {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            $stmt = $pdo->prepare("
                SELECT 
                    COUNT(DISTINCT d.id) as domain_count,
                    COUNT(DISTINCT s.id) as site_count,
                    COUNT(c.id) as container_count
                FROM whp.container_types ct
                LEFT JOIN whp.domains d ON ct.id = d.container_type_id
                LEFT JOIN whp.sites s ON ct.id = s.container_type_id
                LEFT JOIN whp.containers c ON d.id = c.domain_id
                WHERE ct.id = ?
                GROUP BY ct.id
            ");
            $stmt->execute([$id]);
            $usage = $stmt->fetch();
            
            if (!$usage) {
                return ['status' => '0', 'domain_count' => 0, 'site_count' => 0, 'container_count' => 0];
            }
            
            return [
                'status' => '0', 
                'domain_count' => intval($usage['domain_count']),
                'site_count' => intval($usage['site_count']),
                'container_count' => intval($usage['container_count'])
            ];
            
        } catch (PDOException $e) {
            error_log("Get container type usage failed: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to get container type usage: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get available dynamic variables for container environments
     * 
     * @return array List of available dynamic variables with descriptions
     */
    public function getDynamicVariables() {
        $pdo = $this->getMySQLConnection();
        if (!$pdo) {
            return ['status' => '1', 'msg' => 'Failed to connect to MySQL server'];
        }
        
        try {
            $stmt = $pdo->query("
                SELECT variable_name, description, example_value, category 
                FROM whp.container_env_templates 
                ORDER BY category, variable_name
            ");
            $variables = $stmt->fetchAll();
            
            return ['status' => '0', 'variables' => $variables];
            
        } catch (PDOException $e) {
            // If table doesn't exist yet, return hardcoded list
            $default_variables = [
                ['variable_name' => '${WHP_USER}', 'description' => 'System username of the container owner', 'example_value' => 'john', 'category' => 'user'],
                ['variable_name' => '${WHP_UID}', 'description' => 'User ID (UID) of the container owner', 'example_value' => '1001', 'category' => 'user'],
                ['variable_name' => '${WHP_GID}', 'description' => 'Group ID (GID) of the container owner', 'example_value' => '1001', 'category' => 'user'],
                ['variable_name' => '${WHP_HOME}', 'description' => 'Home directory path of the user', 'example_value' => '/docker/users/john', 'category' => 'path'],
                ['variable_name' => '${WHP_DOMAIN}', 'description' => 'Primary domain name assigned to the container', 'example_value' => 'example.com', 'category' => 'domain'],
                ['variable_name' => '${WHP_SUBDOMAIN}', 'description' => 'Subdomain (if applicable)', 'example_value' => 'www', 'category' => 'domain'],
                ['variable_name' => '${WHP_DOMAIN_LIST}', 'description' => 'Comma-separated list of all domains for this user', 'example_value' => 'example.com,test.com', 'category' => 'domain'],
                ['variable_name' => '${WHP_CONTAINER_NAME}', 'description' => 'Unique container name', 'example_value' => 'john-example-com', 'category' => 'system'],
                ['variable_name' => '${WHP_PUBLIC_HTML}', 'description' => 'Public HTML directory path', 'example_value' => '/docker/users/john/public_html', 'category' => 'path'],
                ['variable_name' => '${WHP_LOGS_DIR}', 'description' => 'Logs directory path', 'example_value' => '/docker/users/john/logs', 'category' => 'path'],
                ['variable_name' => '${WHP_DATA_DIR}', 'description' => 'User data directory path', 'example_value' => '/docker/users/john/data', 'category' => 'path'],
                ['variable_name' => '${WHP_LISTEN_PORT}', 'description' => 'HAProxy listen port for this container type', 'example_value' => '80', 'category' => 'system']
            ];
            
            return ['status' => '0', 'variables' => $default_variables];
        }
    }
    
    /**
     * Replace dynamic variables in a string with actual values
     * 
     * @param string $text The text containing variables to replace
     * @param string $username The username for replacements
     * @param string $domain The domain name for replacements
     * @param string $uid The user ID for replacements
     * @param int $container_number The container number for replacements
     * @param int $listen_port The HAProxy listen port for replacements
     * @return string The text with variables replaced
     */
    public function replaceDynamicVariables($text, $username, $domain, $uid, $container_number = 1, $listen_port = 80) {
        $replacements = [
            '${WHP_USER}' => $username,
            '${WHP_UID}' => $uid,
            '${WHP_GID}' => $uid, // Assuming GID = UID for simplicity
            '${WHP_HOME}' => '/docker/users/' . $username,
            '${WHP_DOMAIN}' => $domain,
            '${WHP_CONTAINER_NUMBER}' => $container_number,
            '${WHP_PUBLIC_HTML}' => '/docker/users/' . $username . '/public_html',
            '${WHP_LOGS_DIR}' => '/docker/users/' . $username . '/logs',
            '${WHP_DATA_DIR}' => '/docker/users/' . $username . '/data',
            '${WHP_CONTAINER_NAME}' => $username . '-' . str_replace('.', '-', $domain),
            '${WHP_LISTEN_PORT}' => $listen_port
        ];
        
        return str_replace(array_keys($replacements), array_values($replacements), $text);
    }
}
?> 