<?php
/**
 * Email Management Library for Web Hosting Panel
 * Handles email account CRUD operations, forwarding, and aliases
 */

require_once 'mailserver-client.php';
require_once __DIR__ . '/BichonClient.php';

class emailmgmt {
    
    private $db;
    private $mail_client;
    
    public function __construct() {
        $this->db = $this->get_mysql_connection();
        
        // Initialize mail server client
        try {
            $this->mail_client = MailServerClient::createFromConfig();
        } catch (Exception $e) {
            // Log the error but don't fail the constructor
            error_log("Mail server client initialization failed: " . $e->getMessage());
            $this->mail_client = null;
        }
    }
    
    /**
     * Get MySQL connection
     */
    private function get_mysql_connection() {
        try {
            require_once('/docker/whp/web/libs/mysqlmgmt.php');
            $MySQLMgmt = new mysqlmgmt();
            $pdo = $MySQLMgmt->getMySQLConnection();
            
            if ($pdo) {
                $pdo->exec("USE whp");
                return $pdo;
            }
        } catch (Exception $e) {
            error_log("Email management database connection failed: " . $e->getMessage());
        }
        return null;
    }
    
    /**
     * Get all email accounts for a user (or all users if root)
     */
    public function getEmailAccounts($username = null) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            if ($username && $username !== 'root') {
                // Regular user - see their own email accounts (both active and inactive)
                $stmt = $this->db->prepare("
                    SELECT ea.*, d.domain_name as domain_display_name
                    FROM whp.email_accounts ea
                    LEFT JOIN whp.domains d ON ea.domain_name = d.domain_name
                    WHERE ea.username = ?
                    ORDER BY ea.active DESC, ea.email_address
                ");
                $stmt->execute([$username]);
            } else {
                // Root user - see all email accounts (both active and inactive)
                $stmt = $this->db->prepare("
                    SELECT ea.*, d.domain_name as domain_display_name
                    FROM whp.email_accounts ea
                    LEFT JOIN whp.domains d ON ea.domain_name = d.domain_name
                    ORDER BY ea.username, ea.active DESC, ea.email_address
                ");
                $stmt->execute();
            }
            
            $accounts = $stmt->fetchAll(PDO::FETCH_ASSOC);
            return ['status' => '0', 'accounts' => $accounts];
            
        } catch (Exception $e) {
            error_log("Failed to get email accounts: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to retrieve email accounts'];
        }
    }
    
    /**
     * Get user's available domains for email creation
     */
    public function getUserDomains($username) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            if ($username === 'root') {
                // Root can see all domains
                $stmt = $this->db->prepare("
                    SELECT domain_name, username, active
                    FROM whp.domains
                    WHERE active = 1
                    ORDER BY domain_name
                ");
                $stmt->execute();
            } else {
                // Regular users see only their own domains
                $stmt = $this->db->prepare("
                    SELECT domain_name, active
                    FROM whp.domains
                    WHERE username = ? AND active = 1
                    ORDER BY domain_name
                ");
                $stmt->execute([$username]);
            }
            
            $domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
            return ['status' => '0', 'domains' => $domains];
            
        } catch (Exception $e) {
            error_log("Failed to get user domains: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to retrieve domains'];
        }
    }
    
    /**
     * Get all domains from WHP database (for admin users)
     */
    public function getAllWhpDomains() {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            $stmt = $this->db->prepare("
                SELECT domain_name, username, active, created_at
                FROM whp.domains
                WHERE active = 1
                ORDER BY domain_name
            ");
            $stmt->execute();
            
            $domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
            return ['status' => '0', 'domains' => $domains];
            
        } catch (Exception $e) {
            error_log("Failed to get all WHP domains: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to retrieve domains'];
        }
    }
    
    /**
     * Check if user can create more email accounts
     */
    public function canCreateEmailAccount($username) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Get user's email allowance
            $stmt = $this->db->prepare("
                SELECT max_email_accounts
                FROM whp.client_allowances
                WHERE username = ?
            ");
            $stmt->execute([$username]);
            $allowance = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$allowance) {
                return ['status' => '1', 'msg' => 'User allowance not found'];
            }
            
            // Count current email accounts
            $stmt = $this->db->prepare("
                SELECT COUNT(*) as count
                FROM whp.email_accounts
                WHERE username = ? AND active = 1
            ");
            $stmt->execute([$username]);
            $current = $stmt->fetch(PDO::FETCH_ASSOC);
            
            $can_create = $current['count'] < $allowance['max_email_accounts'];
            $remaining = max(0, $allowance['max_email_accounts'] - $current['count']);
            
            return [
                'status' => '0',
                'can_create' => $can_create,
                'current_count' => $current['count'],
                'max_allowed' => $allowance['max_email_accounts'],
                'remaining' => $remaining
            ];
            
        } catch (Exception $e) {
            error_log("Failed to check email account creation: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to check email account limits'];
        }
    }
    
    /**
     * Check if user can create an email account with given quota without exceeding storage limit
     */
    public function checkEmailStorageQuota($username, $new_quota_mb) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Get user's email storage allowance
            $stmt = $this->db->prepare("
                SELECT max_email_storage_mb
                FROM whp.client_allowances
                WHERE username = ?
            ");
            $stmt->execute([$username]);
            $allowance = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$allowance) {
                return ['status' => '1', 'msg' => 'User allowance not found'];
            }
            
            // Get current email storage usage
            $stmt = $this->db->prepare("
                SELECT COALESCE(SUM(quota_mb), 0) as current_storage
                FROM whp.email_accounts
                WHERE username = ? AND active = 1
            ");
            $stmt->execute([$username]);
            $current = $stmt->fetch(PDO::FETCH_ASSOC);
            
            $max_storage = $allowance['max_email_storage_mb'];
            $current_storage = $current['current_storage'];
            $new_total = $current_storage + $new_quota_mb;
            
            if ($new_total > $max_storage) {
                $available = max(0, $max_storage - $current_storage);
                return [
                    'status' => '1', 
                    'msg' => sprintf(
                        'Email storage quota exceeded. You are using %d MB of %d MB. This account requires %d MB, but only %d MB is available.',
                        $current_storage,
                        $max_storage,
                        $new_quota_mb,
                        $available
                    )
                ];
            }
            
            return [
                'status' => '0',
                'current_storage' => $current_storage,
                'max_storage' => $max_storage,
                'new_total' => $new_total,
                'remaining' => $max_storage - $new_total
            ];
            
        } catch (Exception $e) {
            error_log("Failed to check email storage quota: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to check email storage quota'];
        }
    }
    
    /**
     * Create a new email account
     */
    public function createEmailAccount($username, $email_address, $password, $domain_name, $quota_mb = 100) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Validate email format
            if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) {
                return ['status' => '1', 'msg' => 'Invalid email address format'];
            }
            
            // Check if email already exists
            $stmt = $this->db->prepare("
                SELECT id FROM whp.email_accounts WHERE email_address = ?
            ");
            $stmt->execute([$email_address]);
            if ($stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Email address already exists'];
            }
            
            // Check if user owns the domain
            $stmt = $this->db->prepare("
                SELECT id FROM whp.domains 
                WHERE domain_name = ? AND username = ? AND active = 1
            ");
            $stmt->execute([$domain_name, $username]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Domain not found or not owned by user'];
            }
            
            // Check if user can create more email accounts
            $can_create = $this->canCreateEmailAccount($username);
            if ($can_create['status'] !== '0') {
                return $can_create;
            }
            if (!$can_create['can_create']) {
                return ['status' => '1', 'msg' => 'Email account limit reached'];
            }
            
            // Check email storage quota
            $storage_check = $this->checkEmailStorageQuota($username, $quota_mb);
            if ($storage_check['status'] !== '0') {
                return $storage_check;
            }
            
            // Hash password
            $password_hash = password_hash($password, PASSWORD_DEFAULT);
            
            // Create email account on mail server if client is available
            if ($this->mail_client) {
                try {
                    $username_part = explode('@', $email_address)[0];
                    $this->mail_client->createEmailAccount($email_address, $password, $username_part, $quota_mb);
                } catch (Exception $e) {
                    error_log("Failed to create email account on mail server: " . $e->getMessage());
                    return ['status' => '1', 'msg' => 'Failed to create email account on mail server: ' . $e->getMessage()];
                }
            }
            
            // Create email account in database
            $stmt = $this->db->prepare("
                INSERT INTO whp.email_accounts (email_address, username, domain_name, password_hash, quota_mb)
                VALUES (?, ?, ?, ?, ?)
            ");
            $stmt->execute([$email_address, $username, $domain_name, $password_hash, $quota_mb]);

            return [
                'status'   => '0',
                'msg'      => 'Email account created successfully',
                'email_id' => (int)$this->db->lastInsertId(),
                'username' => $username,
            ];
            
        } catch (Exception $e) {
            error_log("Failed to create email account: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to create email account'];
        }
    }
    
    /**
     * Update email account
     */
    public function updateEmailAccount($email_id, $username, $password = null, $quota_mb = null, $active = null) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Check if email account exists and user has permission
            $stmt = $this->db->prepare("
                SELECT id, username FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            $account = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$account) {
                return ['status' => '1', 'msg' => 'Email account not found'];
            }
            
            // Check permission (user can only edit their own accounts, root can edit all)
            if ($account['username'] !== $username && $username !== 'root') {
                return ['status' => '1', 'msg' => 'Permission denied'];
            }
            
            // Build update query
            $updates = [];
            $params = [];
            
            if ($password !== null) {
                $updates[] = "password_hash = ?";
                $params[] = password_hash($password, PASSWORD_DEFAULT);
            }
            
            if ($quota_mb !== null) {
                // Check storage quota for updates (excluding current account's quota)
                $stmt = $this->db->prepare("SELECT quota_mb FROM whp.email_accounts WHERE id = ?");
                $stmt->execute([$email_id]);
                $current_quota = $stmt->fetch(PDO::FETCH_ASSOC);
                
                if ($current_quota) {
                    $quota_difference = $quota_mb - $current_quota['quota_mb'];
                    if ($quota_difference > 0) {
                        // Only check if increasing quota
                        $storage_check = $this->checkEmailStorageQuota($account['username'], $quota_difference);
                        if ($storage_check['status'] !== '0') {
                            return $storage_check;
                        }
                    }
                }
                
                $updates[] = "quota_mb = ?";
                $params[] = $quota_mb;
            }
            
            if ($active !== null) {
                $updates[] = "active = ?";
                $params[] = $active ? 1 : 0;
            }
            
            if (empty($updates)) {
                return ['status' => '1', 'msg' => 'No changes specified'];
            }
            
            // Get email address for mail server updates
            $stmt = $this->db->prepare("
                SELECT email_address FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            $email_data = $stmt->fetch(PDO::FETCH_ASSOC);
            
            // Update on mail server if client is available
            if ($this->mail_client && $email_data) {
                try {
                    $mail_updates = [];
                    
                    if ($password !== null) {
                        $mail_updates['passwordPlaintext'] = $password;
                    }
                    
                    if ($quota_mb !== null) {
                        // Update quota separately using the quota endpoint
                        $this->mail_client->updateEmailQuota($email_data['email_address'], $quota_mb);
                    }
                    
                    if (!empty($mail_updates)) {
                        $this->mail_client->patchEmailAccount($email_data['email_address'], $mail_updates);
                    }
                } catch (Exception $e) {
                    error_log("Failed to update email account on mail server: " . $e->getMessage());
                    // Continue with database update even if mail server fails
                }
            }
            
            $params[] = $email_id;
            $sql = "UPDATE whp.email_accounts SET " . implode(', ', $updates) . " WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute($params);
            
            return ['status' => '0', 'msg' => 'Email account updated successfully'];
            
        } catch (Exception $e) {
            error_log("Failed to update email account: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to update email account'];
        }
    }
    
    /**
     * Delete email account
     */
    public function deleteEmailAccount($email_id, $username) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Check if email account exists and user has permission
            $stmt = $this->db->prepare("
                SELECT id, username, email_address, archival_enabled, bichon_account_id, bichon_user_id
                FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            $account = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$account) {
                return ['status' => '1', 'msg' => 'Email account not found'];
            }

            // Check permission (user can only delete their own accounts, root can delete all)
            if ($account['username'] !== $username && $username !== 'root') {
                return ['status' => '1', 'msg' => 'Permission denied'];
            }

            // Best-effort Bichon cleanup before local delete. Mailbox is being
            // permanently removed from WHP, so skip the 14-day soft-delete
            // grace period and hard-delete both the user and the account.
            if (!empty($account['bichon_account_id']) && BichonClient::isConfigured()) {
                try {
                    $bc = new BichonClient();
                    if (!empty($account['bichon_user_id'])) {
                        $bc->deleteUser((string)$account['bichon_user_id']); // non-fatal
                    }
                    $bc->deleteAccount($account['bichon_account_id']); // failures are non-fatal
                    $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                        (email_account_id, username, event_type, bichon_account_id, actor)
                        VALUES (?, ?, 'deleted', ?, ?)");
                    $stmt->execute([$email_id, $account['username'], $account['bichon_account_id'], $username]);
                } catch (Throwable $e) {
                    error_log("emailmgmt::deleteEmailAccount Bichon cleanup failed: " . $e->getMessage());
                }
            }

            // Reuse already-fetched email_address for mail server deletion
            $email_data = ['email_address' => $account['email_address']];

            // Delete from mail server if client is available
            if ($this->mail_client && $email_data) {
                try {
                    $this->mail_client->deleteEmailAccount($email_data['email_address']);
                } catch (Exception $e) {
                    error_log("Failed to delete email account from mail server: " . $e->getMessage());
                    // Continue with database deletion even if mail server fails
                }
            }
            
            // Hard delete from database
            $stmt = $this->db->prepare("
                DELETE FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            
            return ['status' => '0', 'msg' => 'Email account deleted successfully'];
            
        } catch (Exception $e) {
            error_log("Failed to delete email account: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to delete email account'];
        }
    }
    
    /**
     * Get email forwarders for a user
     */
    public function getEmailForwarders($username = null) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            if ($username && $username !== 'root') {
                $stmt = $this->db->prepare("
                    SELECT * FROM whp.email_forwarders 
                    WHERE username = ?
                    ORDER BY active DESC, source_email
                ");
                $stmt->execute([$username]);
            } else {
                $stmt = $this->db->prepare("
                    SELECT * FROM whp.email_forwarders 
                    ORDER BY username, active DESC, source_email
                ");
                $stmt->execute();
            }
            
            $forwarders = $stmt->fetchAll(PDO::FETCH_ASSOC);
            return ['status' => '0', 'forwarders' => $forwarders];
            
        } catch (Exception $e) {
            error_log("Failed to get email forwarders: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to retrieve email forwarders'];
        }
    }
    
    /**
     * Create email forwarder
     */
    public function createEmailForwarder($username, $source_email, $destination_email) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Validate email formats
            if (!filter_var($source_email, FILTER_VALIDATE_EMAIL)) {
                return ['status' => '1', 'msg' => 'Invalid source email address format'];
            }
            
            if (!filter_var($destination_email, FILTER_VALIDATE_EMAIL)) {
                return ['status' => '1', 'msg' => 'Invalid destination email address format'];
            }
            
            // Verify that the source email domain belongs to the user
            $domain = explode('@', $source_email)[1];
            $stmt = $this->db->prepare("
                SELECT id FROM whp.domains 
                WHERE domain_name = ? AND username = ? AND active = 1
            ");
            $stmt->execute([$domain, $username]);
            if (!$stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Source email domain not found or not owned by user'];
            }
            
            // Check if this specific destination already exists
            $stmt = $this->db->prepare("
                SELECT id FROM whp.email_forwarders 
                WHERE source_email = ? AND destination_email = ?
            ");
            $stmt->execute([$source_email, $destination_email]);
            if ($stmt->fetch()) {
                return ['status' => '1', 'msg' => 'This forwarder destination already exists'];
            }
            
            // Get all existing destinations for this source email (including the new one we're about to add)
            $stmt = $this->db->prepare("
                SELECT destination_email FROM whp.email_forwarders 
                WHERE source_email = ? AND active = 1
            ");
            $stmt->execute([$source_email]);
            $existing_destinations = $stmt->fetchAll(PDO::FETCH_COLUMN);
            
            // Add the new destination to the array
            $all_destinations = $existing_destinations;
            $all_destinations[] = $destination_email;
            
            // Update forwarder on mail server with all destinations
            if ($this->mail_client) {
                try {
                    $username_part = explode('@', $source_email)[0];
                    
                    // If there are existing destinations, we need to delete the forwarder first
                    if (!empty($existing_destinations)) {
                        try {
                            $this->mail_client->deleteEmailForwarder($source_email);
                        } catch (Exception $e) {
                            // Ignore deletion errors - the forwarder might not exist on mail server
                            error_log("Note: Could not delete existing forwarder (might not exist): " . $e->getMessage());
                        }
                    }
                    
                    // Now create the forwarder with all destinations
                    $this->mail_client->createEmailForwarder($source_email, $all_destinations, $username_part);
                } catch (Exception $e) {
                    error_log("Failed to update email forwarder on mail server: " . $e->getMessage());
                    return ['status' => '1', 'msg' => 'Failed to update email forwarder on mail server: ' . $e->getMessage()];
                }
            }
            
            // Add new destination to database
            $stmt = $this->db->prepare("
                INSERT INTO whp.email_forwarders (source_email, destination_email, username)
                VALUES (?, ?, ?)
            ");
            $stmt->execute([$source_email, $destination_email, $username]);
            
            $msg = empty($existing_destinations) ? 
                   'Email forwarder created successfully' : 
                   'Email forwarder destination added successfully';
            
            return ['status' => '0', 'msg' => $msg];
            
        } catch (Exception $e) {
            error_log("Failed to create email forwarder: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to create email forwarder'];
        }
    }
    
    /**
     * Delete email forwarder
     */
    public function deleteEmailForwarder($forwarder_id, $username) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Check if forwarder exists and user has permission
            $stmt = $this->db->prepare("
                SELECT id, username, source_email, destination_email FROM whp.email_forwarders WHERE id = ?
            ");
            $stmt->execute([$forwarder_id]);
            $forwarder = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$forwarder) {
                return ['status' => '1', 'msg' => 'Email forwarder not found'];
            }
            
            // Check permission
            if ($forwarder['username'] !== $username && $username !== 'root') {
                return ['status' => '1', 'msg' => 'Permission denied'];
            }
            
            // Delete this specific destination from database
            $stmt = $this->db->prepare("
                DELETE FROM whp.email_forwarders WHERE id = ?
            ");
            $stmt->execute([$forwarder_id]);
            
            // Check if there are other destinations remaining for this source email
            $stmt = $this->db->prepare("
                SELECT destination_email FROM whp.email_forwarders 
                WHERE source_email = ? AND active = 1
            ");
            $stmt->execute([$forwarder['source_email']]);
            $remaining_destinations = $stmt->fetchAll(PDO::FETCH_COLUMN);
            
            // Update mail server
            if ($this->mail_client && $forwarder['source_email']) {
                try {
                    // First, always try to delete the existing forwarder
                    try {
                        $this->mail_client->deleteEmailForwarder($forwarder['source_email']);
                    } catch (Exception $e) {
                        // Ignore deletion errors - the forwarder might not exist on mail server
                        error_log("Note: Could not delete existing forwarder (might not exist): " . $e->getMessage());
                    }
                    
                    // If there are remaining destinations, recreate the forwarder with them
                    if (!empty($remaining_destinations)) {
                        $username_part = explode('@', $forwarder['source_email'])[0];
                        $this->mail_client->createEmailForwarder($forwarder['source_email'], $remaining_destinations, $username_part);
                    }
                } catch (Exception $e) {
                    error_log("Failed to update email forwarder on mail server: " . $e->getMessage());
                    // Continue even if mail server update fails
                }
            }
            
            $msg = empty($remaining_destinations) ? 
                   'Email forwarder deleted successfully' : 
                   'Email forwarder destination removed successfully';
            
            return ['status' => '0', 'msg' => $msg];
            
        } catch (Exception $e) {
            error_log("Failed to delete email forwarder: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to delete email forwarder'];
        }
    }
    
    /**
     * Get email aliases for a user
     */
    public function getEmailAliases($username = null) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            if ($username && $username !== 'root') {
                $stmt = $this->db->prepare("
                    SELECT * FROM whp.email_aliases 
                    WHERE username = ?
                    ORDER BY active DESC, alias_email
                ");
                $stmt->execute([$username]);
            } else {
                $stmt = $this->db->prepare("
                    SELECT * FROM whp.email_aliases 
                    ORDER BY username, active DESC, alias_email
                ");
                $stmt->execute();
            }
            
            $aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
            return ['status' => '0', 'aliases' => $aliases];
            
        } catch (Exception $e) {
            error_log("Failed to get email aliases: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to retrieve email aliases'];
        }
    }
    
    /**
     * Create email alias
     */
    public function createEmailAlias($username, $alias_email, $target_email) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Validate email formats
            if (!filter_var($alias_email, FILTER_VALIDATE_EMAIL)) {
                return ['status' => '1', 'msg' => 'Invalid alias email address format'];
            }
            
            if (!filter_var($target_email, FILTER_VALIDATE_EMAIL)) {
                return ['status' => '1', 'msg' => 'Invalid target email address format'];
            }
            
            // Check if alias already exists
            $stmt = $this->db->prepare("
                SELECT id FROM whp.email_aliases WHERE alias_email = ?
            ");
            $stmt->execute([$alias_email]);
            if ($stmt->fetch()) {
                return ['status' => '1', 'msg' => 'Email alias already exists'];
            }
            
            // Create alias on mail server if client is available
            if ($this->mail_client) {
                try {
                    $this->mail_client->createEmailAlias($alias_email, $target_email);
                } catch (Exception $e) {
                    error_log("Failed to create email alias on mail server: " . $e->getMessage());
                    return ['status' => '1', 'msg' => 'Failed to create email alias on mail server: ' . $e->getMessage()];
                }
            }
            
            // Create alias in database
            $stmt = $this->db->prepare("
                INSERT INTO whp.email_aliases (alias_email, target_email, username)
                VALUES (?, ?, ?)
            ");
            $stmt->execute([$alias_email, $target_email, $username]);
            
            return ['status' => '0', 'msg' => 'Email alias created successfully'];
            
        } catch (Exception $e) {
            error_log("Failed to create email alias: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to create email alias'];
        }
    }
    
    /**
     * Delete email alias
     */
    public function deleteEmailAlias($alias_id, $username) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Check if alias exists and user has permission
            $stmt = $this->db->prepare("
                SELECT id, username FROM whp.email_aliases WHERE id = ?
            ");
            $stmt->execute([$alias_id]);
            $alias = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$alias) {
                return ['status' => '1', 'msg' => 'Email alias not found'];
            }
            
            // Check permission
            if ($alias['username'] !== $username && $username !== 'root') {
                return ['status' => '1', 'msg' => 'Permission denied'];
            }
            
            // Get alias email for mail server deletion
            $stmt = $this->db->prepare("
                SELECT alias_email FROM whp.email_aliases WHERE id = ?
            ");
            $stmt->execute([$alias_id]);
            $alias_data = $stmt->fetch(PDO::FETCH_ASSOC);
            
            // Delete from mail server if client is available
            if ($this->mail_client && $alias_data) {
                try {
                    $this->mail_client->deleteEmailAlias($alias_data['alias_email']);
                } catch (Exception $e) {
                    error_log("Failed to delete email alias from mail server: " . $e->getMessage());
                    // Continue with database deletion even if mail server fails
                }
            }
            
            // Hard delete from database
            $stmt = $this->db->prepare("
                DELETE FROM whp.email_aliases WHERE id = ?
            ");
            $stmt->execute([$alias_id]);
            
            return ['status' => '0', 'msg' => 'Email alias deleted successfully'];
            
        } catch (Exception $e) {
            error_log("Failed to delete email alias: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to delete email alias'];
        }
    }
    
    /**
     * Get email usage statistics for a user
     */
    public function getEmailUsageStats($username) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Get user's email allowance
            $stmt = $this->db->prepare("
                SELECT max_email_accounts
                FROM whp.client_allowances
                WHERE username = ?
            ");
            $stmt->execute([$username]);
            $allowance = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$allowance) {
                return ['status' => '1', 'msg' => 'User allowance not found'];
            }
            
            // Count current email accounts
            $stmt = $this->db->prepare("
                SELECT COUNT(*) as count
                FROM whp.email_accounts
                WHERE username = ? AND active = 1
            ");
            $stmt->execute([$username]);
            $accounts = $stmt->fetch(PDO::FETCH_ASSOC);
            
            // Count forwarders
            $stmt = $this->db->prepare("
                SELECT COUNT(*) as count
                FROM whp.email_forwarders
                WHERE username = ? AND active = 1
            ");
            $stmt->execute([$username]);
            $forwarders = $stmt->fetch(PDO::FETCH_ASSOC);
            
            // Count aliases
            $stmt = $this->db->prepare("
                SELECT COUNT(*) as count
                FROM whp.email_aliases
                WHERE username = ? AND active = 1
            ");
            $stmt->execute([$username]);
            $aliases = $stmt->fetch(PDO::FETCH_ASSOC);
            
            return [
                'status' => '0',
                'max_accounts' => $allowance['max_email_accounts'],
                'current_accounts' => $accounts['count'],
                'forwarders' => $forwarders['count'],
                'aliases' => $aliases['count'],
                'remaining' => max(0, $allowance['max_email_accounts'] - $accounts['count'])
            ];
            
        } catch (Exception $e) {
            error_log("Failed to get email usage stats: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to retrieve email usage statistics'];
        }
    }
    
    /**
     * Test mail server connection
     */
    public function testMailServerConnection() {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        try {
            $result = $this->mail_client->testConnection();
            return $result;
        } catch (Exception $e) {
            return ['status' => 'error', 'message' => $e->getMessage()];
        }
    }
    
    /**
     * Create domain on mail server
     */
    public function createMailServerDomain($domain_name, $options = []) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        try {
            $result = $this->mail_client->createDomain($domain_name, $options);
            return ['status' => '0', 'msg' => 'Domain created on mail server successfully'];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to create domain on mail server: ' . $e->getMessage()];
        }
    }
    
    /**
     * Delete domain from mail server
     */
    public function deleteMailServerDomain($domain_name) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        try {
            $result = $this->mail_client->deleteDomain($domain_name);
            return ['status' => '0', 'msg' => 'Domain deleted from mail server successfully'];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to delete domain from mail server: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get domain information from mail server
     */
    public function getMailServerDomain($domain_name) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        try {
            $result = $this->mail_client->getDomain($domain_name);
            return ['status' => '0', 'domain' => $result];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to get domain from mail server: ' . $e->getMessage()];
        }
    }
    
    /**
     * Update domain settings on mail server
     */
    public function updateMailServerDomain($domain_name, $updates = []) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        try {
            $result = $this->mail_client->updateDomain($domain_name, $updates);
            return ['status' => '0', 'msg' => 'Domain updated on mail server successfully'];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to update domain on mail server: ' . $e->getMessage()];
        }
    }
    
    /**
     * Create DKIM records for a domain on mail server with retry logic
     */
    public function createDkimRecordsWithRetry($domain_name, $username = null, $max_retries = 3, $delay_seconds = 2) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        // Check domain ownership if username is provided
        if ($username && $username !== 'root') {
            $ownership_result = $this->checkDomainOwnership($domain_name, $username);
            if ($ownership_result['status'] !== '0') {
                return $ownership_result;
            }
        }
        
        // Try to create DKIM records with retries
        for ($attempt = 1; $attempt <= $max_retries; $attempt++) {
            try {
                // Add delay before first attempt and between retries
                if ($attempt > 1) {
                    sleep($delay_seconds);
                }
                
                $result = $this->mail_client->createDkimRecords($domain_name);
                
                if ($result && isset($result['selector']) && isset($result['public'])) {
                    return ['status' => '0', 'msg' => 'DKIM records created successfully', 'dkim' => $result];
                }
                
                // If we get here, the request succeeded but didn't return expected data
                return ['status' => '0', 'msg' => 'DKIM records created successfully', 'dkim' => $result];
                
            } catch (Exception $e) {
                $error_msg = $e->getMessage();
                
                // If this is the last attempt, return the error
                if ($attempt >= $max_retries) {
                    return ['status' => '1', 'msg' => "Failed to create DKIM records after $max_retries attempts: $error_msg"];
                }
                
                // Log the retry attempt
                error_log("DKIM creation attempt $attempt failed for domain $domain_name: $error_msg. Retrying in $delay_seconds seconds...");
            }
        }
        
        return ['status' => '1', 'msg' => 'Failed to create DKIM records after maximum retries'];
    }
    
    /**
     * Create DKIM records for a domain on mail server
     */
    public function createDkimRecords($domain_name, $username = null) {
        // Use the retry version by default
        return $this->createDkimRecordsWithRetry($domain_name, $username);
    }
    
    /**
     * Get DKIM records for a domain from mail server
     */
    public function getDkimRecords($domain_name, $username = null) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        // Check domain ownership if username is provided
        if ($username && $username !== 'root') {
            $ownership_result = $this->checkDomainOwnership($domain_name, $username);
            if ($ownership_result['status'] !== '0') {
                return $ownership_result;
            }
        }
        
        try {
            $result = $this->mail_client->getDkimRecords($domain_name);
            
            // Clean the public key for DNS record display
            if (isset($result['public'])) {
                $cleaned_public_key = str_replace([
                    '-----BEGIN PUBLIC KEY-----',
                    '-----END PUBLIC KEY-----',
                    "\n", "\r", " "
                ], '', $result['public']);
                
                // Add both raw and cleaned versions
                $result['public_cleaned'] = $cleaned_public_key;
                $result['dns_record'] = '"v=DKIM1; k=rsa; p=' . $cleaned_public_key . '"';
            }
            
            return ['status' => '0', 'dkim' => $result];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to get DKIM records: ' . $e->getMessage()];
        }
    }
    
    /**
     * Check if user owns a domain
     */
    private function checkDomainOwnership($domain_name, $username) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            $stmt = $this->db->prepare("SELECT username FROM whp.domains WHERE domain_name = ? AND active = 1");
            $stmt->execute([$domain_name]);
            $domain = $stmt->fetch();
            
            if (!$domain) {
                return ['status' => '1', 'msg' => 'Domain not found or inactive'];
            }
            
            if ($domain['username'] !== $username) {
                return ['status' => '1', 'msg' => 'You do not own this domain'];
            }
            
            return ['status' => '0', 'msg' => 'Domain ownership verified'];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to verify domain ownership: ' . $e->getMessage()];
                }
    }

    /**
     * Create DNS record for existing DKIM data
     * This method should be called from domains.php after DKIM records are created
     */
    public function createDkimDnsRecord($domain_name, $selector = null, $public_key = null, $username = null) {
        try {
            // If selector and public key not provided, get them from mail server
            if (!$selector || !$public_key) {
                $dkim_result = $this->getDkimRecords($domain_name, $username);
                if ($dkim_result['status'] !== '0') {
                    return $dkim_result;
                }
                
                if (!isset($dkim_result['dkim']['selector']) || !isset($dkim_result['dkim']['public'])) {
                    return ['status' => '1', 'msg' => 'DKIM records not found for domain'];
                }
                
                $selector = $dkim_result['dkim']['selector'];
                $public_key = $dkim_result['dkim']['public'];
            }
            
            // Load PowerDNS integration
            require_once('/docker/whp/web/libs/pdns-integration.php');
            
            // Initialize PowerDNS integration
            $pdns = new PowerDNSIntegration($this->db);
            
            // Create DKIM DNS record
            $record_name = $selector . '._domainkey';
            
            // Strip PEM headers and whitespace from public key
            $clean_public_key = str_replace([
                '-----BEGIN PUBLIC KEY-----',
                '-----END PUBLIC KEY-----',
                "\n", "\r", " "
            ], '', $public_key);
            
            $record_content = '"v=DKIM1; k=rsa; p=' . $clean_public_key . '"';
            
            // Log the attempt
            error_log("Attempting to add DKIM DNS record for $domain_name: $record_name TXT $record_content");
            
            $result = $pdns->addRecord($domain_name, $record_name, 'TXT', $record_content, 3600);
            
            if ($result) {
                error_log("Successfully added DKIM DNS record for $domain_name");
                return ['status' => '0', 'msg' => 'DKIM DNS record added successfully'];
            } else {
                error_log("Failed to add DKIM DNS record for $domain_name: PowerDNS returned false");
                return ['status' => '1', 'msg' => 'Failed to add DKIM DNS record to PowerDNS'];
            }
        } catch (Exception $e) {
            error_log("Exception adding DKIM DNS record for $domain_name: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to add DKIM DNS record: ' . $e->getMessage()];
        }
    }
    
    /**
     * List all domains from mail server
     */
    public function listMailServerDomains() {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        try {
            $result = $this->mail_client->listDomains();
            return ['status' => '0', 'domains' => $result];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to list domains from mail server: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get email account statistics from mail server
     */
    public function getEmailStats($email) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        try {
            $result = $this->mail_client->getEmailStats($email);
            return ['status' => '0', 'stats' => $result];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to get email statistics: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get email statistics summary (last 30 days)
     */
    public function getEmailStatsSummary($email) {
        $stats_result = $this->getEmailStats($email);
        if ($stats_result['status'] !== '0') {
            return $stats_result;
        }
        
        $stats = $stats_result['stats'];
        $total_in = 0;
        $total_out = 0;
        $days_with_activity = 0;
        $last_activity = null;
        
        foreach ($stats as $date => $day_stats) {
            $in_count = $day_stats['in'] ?? 0;
            $out_count = $day_stats['out'] ?? 0;
            
            $total_in += $in_count;
            $total_out += $out_count;
            
            if ($in_count > 0 || $out_count > 0) {
                $days_with_activity++;
                $last_activity = $date;
            }
        }
        
        return [
            'status' => '0',
            'summary' => [
                'total_in' => $total_in,
                'total_out' => $total_out,
                'total_emails' => $total_in + $total_out,
                'days_with_activity' => $days_with_activity,
                'last_activity' => $last_activity,
                'avg_in_per_day' => count($stats) > 0 ? round($total_in / count($stats), 1) : 0,
                'avg_out_per_day' => count($stats) > 0 ? round($total_out / count($stats), 1) : 0
            ],
            'daily_stats' => $stats
        ];
    }
    
    /**
     * Get mail server statistics
     */
    public function getMailServerStats() {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        try {
            $stats = $this->mail_client->getStatistics();
            return ['status' => '0', 'stats' => $stats];
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Failed to get mail server statistics: ' . $e->getMessage()];
        }
    }
    
    /**
     * Clean up all email database entries for a domain when the domain is deleted
     * This should be called when a domain is being deleted to ensure no orphaned email records remain
     * 
     * @param string $domain_name The domain name to clean up
     * @return array Result array with status and cleanup information
     */
    public function cleanupDomainEmailEntries($domain_name) {
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            $cleanup_results = [
                'accounts_deleted' => 0,
                'forwarders_deleted' => 0,
                'aliases_deleted' => 0,
                'errors' => []
            ];
            
            // Delete email accounts for this domain
            $stmt = $this->db->prepare("DELETE FROM whp.email_accounts WHERE domain_name = ?");
            if ($stmt->execute([$domain_name])) {
                $cleanup_results['accounts_deleted'] = $stmt->rowCount();
            } else {
                $cleanup_results['errors'][] = 'Failed to delete email accounts';
            }
            
            // Delete email forwarders for this domain (source emails ending with @domain)
            $stmt = $this->db->prepare("DELETE FROM whp.email_forwarders WHERE source_email LIKE ?");
            if ($stmt->execute(['%@' . $domain_name])) {
                $cleanup_results['forwarders_deleted'] = $stmt->rowCount();
            } else {
                $cleanup_results['errors'][] = 'Failed to delete email forwarders';
            }
            
            // Delete email aliases for this domain (alias emails ending with @domain)
            $stmt = $this->db->prepare("DELETE FROM whp.email_aliases WHERE alias_email LIKE ?");
            if ($stmt->execute(['%@' . $domain_name])) {
                $cleanup_results['aliases_deleted'] = $stmt->rowCount();
            } else {
                $cleanup_results['errors'][] = 'Failed to delete email aliases';
            }
            
            // Log the cleanup results
            $total_deleted = $cleanup_results['accounts_deleted'] + $cleanup_results['forwarders_deleted'] + $cleanup_results['aliases_deleted'];
            if ($total_deleted > 0) {
                error_log("Email cleanup for domain {$domain_name}: {$cleanup_results['accounts_deleted']} accounts, {$cleanup_results['forwarders_deleted']} forwarders, {$cleanup_results['aliases_deleted']} aliases deleted");
            }
            
            if (empty($cleanup_results['errors'])) {
                return [
                    'status' => '0',
                    'msg' => "Email database cleanup completed for domain {$domain_name}",
                    'results' => $cleanup_results
                ];
            } else {
                return [
                    'status' => '1',
                    'msg' => 'Email database cleanup completed with errors: ' . implode(', ', $cleanup_results['errors']),
                    'results' => $cleanup_results
                ];
            }
            
        } catch (PDOException $e) {
            error_log("Email cleanup error for domain {$domain_name}: " . $e->getMessage());
            return [
                'status' => '1',
                'msg' => 'Email database cleanup failed: ' . $e->getMessage(),
                'results' => ['errors' => [$e->getMessage()]]
            ];
        }
    }

    /**
     * Rotate DKIM records for a domain
     * This will delete existing DKIM records and create new ones
     */
    public function rotateDkimRecords($domain_name, $username = null) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        // Check domain ownership if username is provided
        if ($username && $username !== 'root') {
            $ownership_result = $this->checkDomainOwnership($domain_name, $username);
            if ($ownership_result['status'] !== '0') {
                return $ownership_result;
            }
        }
        
        try {
            // Step 1: Get existing DKIM records to find the selector
            $existing_dkim = $this->getDkimRecords($domain_name, $username);
            $old_selector = null;
            
            if ($existing_dkim['status'] === '0' && isset($existing_dkim['dkim']['selector'])) {
                $old_selector = $existing_dkim['dkim']['selector'];
            }
            
            // Step 2: Delete DKIM records from mail server
            try {
                $this->mail_client->deleteDkimRecords($domain_name);
                error_log("Successfully deleted DKIM records from mail server for domain: $domain_name");
            } catch (Exception $e) {
                // Log but continue - the records might not exist
                error_log("Note: Could not delete DKIM from mail server for $domain_name: " . $e->getMessage());
            }
            
            // Step 3: Delete DKIM DNS record if it exists
            if ($old_selector) {
                try {
                    require_once('/docker/whp/web/libs/pdns-integration.php');
                    $pdns = new PowerDNSIntegration($this->db);
                    
                    $record_name = $old_selector . '._domainkey';
                    $delete_result = $pdns->deleteRecord($domain_name, $record_name, 'TXT');
                    
                    if ($delete_result) {
                        error_log("Successfully deleted DKIM DNS record for domain: $domain_name");
                    } else {
                        error_log("Could not delete DKIM DNS record for domain: $domain_name");
                    }
                } catch (Exception $e) {
                    error_log("Error deleting DKIM DNS record for $domain_name: " . $e->getMessage());
                }
            }
            
            // Step 4: Wait a moment for the deletion to propagate
            sleep(2);
            
            // Step 5: Create new DKIM records with retry logic
            $result = $this->createDkimRecordsWithRetry($domain_name, $username, 3, 2);
            
            if ($result['status'] === '0') {
                // Step 6: Create the DNS record for the new DKIM
                if (isset($result['dkim']['selector']) && isset($result['dkim']['public'])) {
                    $dns_result = $this->createDkimDnsRecord(
                        $domain_name, 
                        $result['dkim']['selector'], 
                        $result['dkim']['public'], 
                        $username
                    );
                    
                    if ($dns_result['status'] === '0') {
                        return [
                            'status' => '0', 
                            'msg' => 'DKIM records rotated successfully. DNS records have been updated.',
                            'dkim' => $result['dkim']
                        ];
                    } else {
                        return [
                            'status' => '0', 
                            'msg' => 'DKIM records rotated but DNS update failed: ' . $dns_result['msg'],
                            'dkim' => $result['dkim']
                        ];
                    }
                }
                
                return [
                    'status' => '0', 
                    'msg' => 'DKIM records rotated successfully',
                    'dkim' => $result['dkim'] ?? []
                ];
            } else {
                return [
                    'status' => '1', 
                    'msg' => 'Failed to create new DKIM records after rotation: ' . $result['msg']
                ];
            }
            
        } catch (Exception $e) {
            error_log("Failed to rotate DKIM records for domain $domain_name: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to rotate DKIM records: ' . $e->getMessage()];
        }
    }
    
    /**
     * Manually retry DKIM creation for an existing domain
     * This is useful for domains that were created before the timing fix
     */
    public function retryDkimCreation($domain_name, $username = null, $max_retries = 5, $delay_seconds = 3) {
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not available'];
        }
        
        // Check domain ownership if username is provided
        if ($username && $username !== 'root') {
            $ownership_result = $this->checkDomainOwnership($domain_name, $username);
            if ($ownership_result['status'] !== '0') {
                return $ownership_result;
            }
        }
        
        // First, check if domain exists on mail server
        try {
            $domain_check = $this->mail_client->getDomain($domain_name);
            if (!$domain_check) {
                return ['status' => '1', 'msg' => 'Domain not found on mail server'];
            }
        } catch (Exception $e) {
            return ['status' => '1', 'msg' => 'Domain not found on mail server: ' . $e->getMessage()];
        }
        
        // Try to create DKIM records with extended retry logic
        for ($attempt = 1; $attempt <= $max_retries; $attempt++) {
            try {
                // Add delay before first attempt and between retries
                if ($attempt > 1) {
                    sleep($delay_seconds);
                }
                
                $result = $this->mail_client->createDkimRecords($domain_name);
                
                if ($result && isset($result['selector']) && isset($result['public'])) {
                    // Success! Now try to create the DNS record
                    $dns_result = $this->createDkimDnsRecord($domain_name, $result['selector'], $result['public'], $username);
                    if ($dns_result['status'] === '0') {
                        return ['status' => '0', 'msg' => 'DKIM records created and DNS record added successfully', 'dkim' => $result];
                    } else {
                        return ['status' => '0', 'msg' => 'DKIM records created but DNS record failed: ' . $dns_result['msg'], 'dkim' => $result];
                    }
                }
                
                // If we get here, the request succeeded but didn't return expected data
                return ['status' => '0', 'msg' => 'DKIM records created successfully', 'dkim' => $result];
                
            } catch (Exception $e) {
                $error_msg = $e->getMessage();
                
                // If this is the last attempt, return the error
                if ($attempt >= $max_retries) {
                    return ['status' => '1', 'msg' => "Failed to create DKIM records after $max_retries attempts: $error_msg"];
                }
                
                // Log the retry attempt
                error_log("DKIM retry attempt $attempt failed for domain $domain_name: $error_msg. Retrying in $delay_seconds seconds...");
            }
        }
        
        return ['status' => '1', 'msg' => 'Failed to create DKIM records after maximum retries'];
    }
    
    /**
     * Import existing email accounts from mail server (root users only)
     * @param string $domain Domain to import accounts from
     * @return array Result with status and imported accounts
     */
    public function importEmailAccountsFromMailServer($domain) {
        // Only root users can import email accounts
        if (AUTH_USER !== 'root') {
            return ['status' => '1', 'msg' => 'Only root users can import email accounts'];
        }
        
        if (!$this->mail_client) {
            return ['status' => '1', 'msg' => 'Mail server client not configured'];
        }
        
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        // Validate that the domain exists on this server
        try {
            $stmt = $this->db->prepare("SELECT username FROM whp.domains WHERE domain_name = ? AND active = 1");
            $stmt->execute([$domain]);
            $domain_info = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$domain_info) {
                return ['status' => '1', 'msg' => 'Domain not found on this server'];
            }
            
            $domain_owner = $domain_info['username'];
            
        } catch (Exception $e) {
            error_log("Failed to validate domain: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to validate domain'];
        }
        
        try {
            // Fetch email accounts from mail server
            $response = $this->mail_client->getEmailAccounts($domain);
            
            if (!isset($response['results'])) {
                return ['status' => '1', 'msg' => 'Invalid response from mail server'];
            }
            
            $imported_count = 0;
            $skipped_count = 0;
            $failed_count = 0;
            $imported_accounts = [];
            $imported_forwarders = [];
            $skipped_accounts = [];
            $errors = [];
            
            foreach ($response['results'] as $account) {
                // Skip if it's a forwarder-only account (no actual mailbox)
                if ($account['redirect_only'] === true && !empty($account['redirect_to'])) {
                    // Import as forwarder
                    $result = $this->importEmailForwarderFromMailServer($account, $domain_owner);
                    if ($result['status'] === '0') {
                        $imported_count++;
                        $imported_forwarders[] = $account['address'];
                    } else if (strpos($result['msg'], 'already exists') !== false) {
                        $skipped_count++;
                        $skipped_accounts[] = $account['address'] . ' (forwarder)';
                    } else {
                        $failed_count++;
                        $errors[] = "Forwarder {$account['address']}: " . $result['msg'];
                    }
                    continue;
                }
                
                // Skip disabled accounts unless they're actual mailboxes
                if ($account['disabled'] === true && $account['redirect_only'] === true) {
                    $skipped_count++;
                    continue;
                }
                
                // Check if account already exists in our database
                try {
                    $stmt = $this->db->prepare("SELECT id FROM whp.email_accounts WHERE email_address = ?");
                    $stmt->execute([$account['address']]);
                    if ($stmt->fetch()) {
                        $skipped_count++;
                        $skipped_accounts[] = $account['address'] . ' (email account)';
                        continue; // Account already exists
                    }
                } catch (Exception $e) {
                    error_log("Failed to check existing account: " . $e->getMessage());
                    $failed_count++;
                    $errors[] = "{$account['address']}: Failed to check existing account";
                    continue;
                }
                
                // Import the email account
                $result = $this->importEmailAccount($account, $domain_owner);
                if ($result['status'] === '0') {
                    $imported_count++;
                    $imported_accounts[] = $account['address'];
                } else {
                    $failed_count++;
                    $errors[] = "{$account['address']}: " . $result['msg'];
                }
            }
            
            $message = "Import complete: $imported_count imported, $skipped_count skipped, $failed_count failed";
            
            // Add details about what was imported
            if (!empty($imported_accounts)) {
                $message .= "\n\nImported email accounts: " . implode(", ", array_slice($imported_accounts, 0, 5));
                if (count($imported_accounts) > 5) {
                    $message .= " and " . (count($imported_accounts) - 5) . " more...";
                }
            }
            
            if (!empty($imported_forwarders)) {
                $message .= "\n\nImported forwarders: " . implode(", ", array_slice($imported_forwarders, 0, 5));
                if (count($imported_forwarders) > 5) {
                    $message .= " and " . (count($imported_forwarders) - 5) . " more...";
                }
            }
            
            // Add details about what was skipped (if re-running import)
            if (!empty($skipped_accounts)) {
                $message .= "\n\nSkipped (already exist): " . implode(", ", array_slice($skipped_accounts, 0, 5));
                if (count($skipped_accounts) > 5) {
                    $message .= " and " . (count($skipped_accounts) - 5) . " more...";
                }
            }
            
            // Add error details
            if (!empty($errors)) {
                $message .= "\n\nErrors: " . implode("; ", array_slice($errors, 0, 5));
                if (count($errors) > 5) {
                    $message .= " and " . (count($errors) - 5) . " more...";
                }
            }
            
            return [
                'status' => '0',
                'msg' => $message,
                'imported' => array_merge($imported_accounts, $imported_forwarders),
                'imported_accounts' => $imported_accounts,
                'imported_forwarders' => $imported_forwarders,
                'skipped' => $skipped_accounts,
                'imported_count' => $imported_count,
                'skipped_count' => $skipped_count,
                'failed_count' => $failed_count
            ];
            
        } catch (Exception $e) {
            error_log("Failed to import email accounts: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to fetch accounts from mail server: ' . $e->getMessage()];
        }
    }
    
    /**
     * Import a single email account from mail server data
     * @param array $account Account data from mail server
     * @param string $username Owner username
     * @return array Result with status
     */
    private function importEmailAccount($account, $username) {
        try {
            // Extract domain from email address
            $email_parts = explode('@', $account['address']);
            if (count($email_parts) !== 2) {
                return ['status' => '1', 'msg' => 'Invalid email address format'];
            }
            $domain = $email_parts[1];
            
            // Generate a random password (user will need to reset it)
            $temp_password = bin2hex(random_bytes(16));
            
            // Get storage quota from mail server if available
            $quota_mb = 100; // Default quota
            
            // Insert into database
            $stmt = $this->db->prepare("
                INSERT INTO whp.email_accounts 
                (email_address, username, domain_name, password_hash, quota_mb, active, created_at, updated_at)
                VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())
            ");
            
            $stmt->execute([
                $account['address'],
                $username,
                $domain,
                password_hash($temp_password, PASSWORD_DEFAULT),
                $quota_mb,
                !$account['disabled'] // Convert disabled to active
            ]);
            
            return ['status' => '0', 'msg' => 'Account imported successfully'];
            
        } catch (Exception $e) {
            error_log("Failed to import email account {$account['address']}: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to import account: ' . $e->getMessage()];
        }
    }
    
    /**
     * Import email forwarder from mail server data
     * @param array $forwarder Forwarder data from mail server
     * @param string $username Owner username
     * @return array Result with status
     */
    private function importEmailForwarderFromMailServer($forwarder, $username) {
        try {
            $imported_destinations = 0;
            $skipped_destinations = 0;
            
            // Import each redirect_to address
            foreach ($forwarder['redirect_to'] as $destination) {
                // Check if this specific forwarder rule already exists
                $stmt = $this->db->prepare("
                    SELECT id FROM whp.email_forwarders 
                    WHERE source_email = ? AND destination_email = ?
                ");
                $stmt->execute([$forwarder['address'], $destination]);
                
                if ($stmt->fetch()) {
                    $skipped_destinations++;
                    continue; // Skip this destination, it already exists
                }
                
                // Check if the table has destination_type column
                $stmt = $this->db->prepare("
                    SELECT COLUMN_NAME 
                    FROM INFORMATION_SCHEMA.COLUMNS 
                    WHERE TABLE_SCHEMA = 'whp' 
                    AND TABLE_NAME = 'email_forwarders' 
                    AND COLUMN_NAME = 'destination_type'
                ");
                $stmt->execute();
                $has_destination_type = $stmt->fetch() !== false;
                
                // Build the insert query based on table structure
                if ($has_destination_type) {
                    $stmt = $this->db->prepare("
                        INSERT INTO whp.email_forwarders 
                        (source_email, destination_email, destination_type, username, active, created_at, updated_at)
                        VALUES (?, ?, 'email', ?, ?, NOW(), NOW())
                    ");
                } else {
                    $stmt = $this->db->prepare("
                        INSERT INTO whp.email_forwarders 
                        (source_email, destination_email, username, active, created_at, updated_at)
                        VALUES (?, ?, ?, ?, NOW(), NOW())
                    ");
                }
                
                $stmt->execute([
                    $forwarder['address'],
                    $destination,
                    $username,
                    !$forwarder['disabled']
                ]);
                
                $imported_destinations++;
            }
            
            if ($imported_destinations > 0) {
                return ['status' => '0', 'msg' => "Forwarder imported successfully ({$imported_destinations} destinations)"];
            } else if ($skipped_destinations > 0) {
                return ['status' => '1', 'msg' => 'Forwarder already exists'];
            } else {
                return ['status' => '1', 'msg' => 'No destinations to import'];
            }
            
        } catch (Exception $e) {
            error_log("Failed to import forwarder {$forwarder['address']}: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to import forwarder: ' . $e->getMessage()];
        }
    }
    
    /**
     * Get list of domains available for import (root only)
     * @return array Result with domains that have email accounts on mail server
     */
    public function getDomainsAvailableForImport() {
        if (AUTH_USER !== 'root') {
            return ['status' => '1', 'msg' => 'Only root users can access this function'];
        }
        
        if (!$this->db) {
            return ['status' => '1', 'msg' => 'Database connection failed'];
        }
        
        try {
            // Get all active domains on this server
            $stmt = $this->db->prepare("
                SELECT domain_name, username 
                FROM whp.domains 
                WHERE active = 1 
                ORDER BY domain_name
            ");
            $stmt->execute();
            $domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
            
            return ['status' => '0', 'domains' => $domains];

        } catch (Exception $e) {
            error_log("Failed to get domains for import: " . $e->getMessage());
            return ['status' => '1', 'msg' => 'Failed to retrieve domains'];
        }
    }

    // === Bichon archival ===

    /**
     * Check whether the user has remaining archival slots.
     *
     * @param string $username
     * @return array ['can_enable'=>bool, 'current_count'=>int, 'max_allowed'=>int, 'remaining'=>int]
     */
    public function canEnableArchival($username) {
        $result = [
            'can_enable'    => false,
            'current_count' => 0,
            'max_allowed'   => 0,
            'remaining'     => 0,
        ];

        if (!$this->db) {
            return $result;
        }

        try {
            $stmt = $this->db->prepare("SELECT max_archived_mailboxes FROM whp.client_allowances WHERE username = ?");
            $stmt->execute([$username]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            $max_allowed = $row ? (int)$row['max_archived_mailboxes'] : 0;

            $stmt = $this->db->prepare("SELECT COUNT(*) AS c FROM whp.email_accounts WHERE username = ? AND archival_enabled = 1");
            $stmt->execute([$username]);
            $cnt = $stmt->fetch(PDO::FETCH_ASSOC);
            $current = $cnt ? (int)$cnt['c'] : 0;

            $remaining = max(0, $max_allowed - $current);

            return [
                'can_enable'    => $remaining > 0,
                'current_count' => $current,
                'max_allowed'   => $max_allowed,
                'remaining'     => $remaining,
            ];
        } catch (Exception $e) {
            error_log("emailmgmt::canEnableArchival failed: " . $e->getMessage());
            return $result;
        }
    }

    /**
     * Bichon RBAC role IDs. These are stable, built-in roles in Bichon.
     *   - member        (global): allows the user to log in to the WebUI
     *   - account_viewer (per-account): read-only access to one archive
     */
    const BICHON_ROLE_MEMBER         = 100200000000000;
    const BICHON_ROLE_ACCOUNT_VIEWER = 200200000000000;

    /**
     * Enable Bichon archival for an email account.
     *
     * Three starting states are handled:
     *   1. archival_enabled=1                    -> idempotency error
     *   2. archival_enabled=0, has bichon_account_id, archival_purge_at>NOW()
     *      -> grace-period resume: update existing Bichon account's IMAP creds,
     *         re-create Bichon user, clear purge timestamp.
     *   3. otherwise (never enabled, or grace expired)
     *      -> fresh enable: create Bichon account + Bichon user + map.
     *
     * SECURITY: $imapPassword and $bichonPassword must NEVER be logged. They
     * are forwarded to Bichon once and then go out of scope.
     *
     * @param int         $email_id
     * @param string      $username       caller's WHP username (may be 'root')
     * @param string      $imapPassword   mailbox password — used for IMAP login
     * @param string|null $bichonPassword Bichon WebUI login password. If null,
     *                                    falls back to $imapPassword (single-
     *                                    credential "create-with-archival" flow).
     * @return array ['status'=>'success'|'error', 'message'=>string, 'bichon_account_id'=>?string, 'remaining'=>?int]
     */
    public function enableArchival($email_id, $username, $imapPassword, $bichonPassword = null) {
        if (!$this->db) {
            return ['status' => 'error', 'message' => 'Database connection failed', 'bichon_account_id' => null, 'remaining' => null];
        }

        // Effective Bichon password (never logged).
        $effectiveBichonPassword = ($bichonPassword === null || $bichonPassword === '')
            ? $imapPassword
            : $bichonPassword;

        try {
            // 1. Ownership check
            $stmt = $this->db->prepare("
                SELECT id, email_address, username, archival_enabled, bichon_account_id,
                       archival_purge_at,
                       (archival_purge_at IS NOT NULL AND archival_purge_at > NOW()) AS in_grace
                FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$row) {
                return ['status' => 'error', 'message' => 'Permission denied', 'bichon_account_id' => null, 'remaining' => null];
            }
            if ($username !== 'root' && $row['username'] !== $username) {
                return ['status' => 'error', 'message' => 'Permission denied', 'bichon_account_id' => null, 'remaining' => null];
            }

            // 2. Idempotency
            if (!empty($row['archival_enabled'])) {
                return ['status' => 'error', 'message' => 'Archival is already enabled for this mailbox', 'bichon_account_id' => $row['bichon_account_id'], 'remaining' => null];
            }

            // 3. Bichon configured?
            if (!BichonClient::isConfigured()) {
                return ['status' => 'error', 'message' => 'Email archival is not configured on this server', 'bichon_account_id' => null, 'remaining' => null];
            }

            // 4. Entitlement gate (use the row's owner, not the caller)
            $email_user = $row['username'];
            $gate = $this->canEnableArchival($email_user);
            if (empty($gate['can_enable'])) {
                return [
                    'status'            => 'error',
                    'message'           => 'No archival slots remaining (' . (int)$gate['current_count'] . ' of ' . (int)$gate['max_allowed'] . ' used)',
                    'bichon_account_id' => null,
                    'remaining'         => (int)$gate['remaining'],
                ];
            }

            // 5. IMAP host from settings.json
            $settings_file = '/docker/whp/settings.json';
            $mail_server = '';
            if (file_exists($settings_file)) {
                $contents = @file_get_contents($settings_file);
                if ($contents !== false) {
                    $settings = json_decode($contents, true);
                    if (is_array($settings) && !empty($settings['mail_server'])) {
                        $mail_server = (string)$settings['mail_server'];
                    }
                }
            }
            if ($mail_server === '') {
                return ['status' => 'error', 'message' => 'Mail server not configured', 'bichon_account_id' => null, 'remaining' => null];
            }

            $imapStruct = [
                'host'       => $mail_server,
                'port'       => 993,
                'encryption' => 'Ssl',
                'auth' => [
                    'auth_type' => 'Password',
                    'password'  => $imapPassword,
                ],
            ];

            $bc = new BichonClient();

            // ===== Branch A: grace-period resume =====
            // bichon_account_id present, purge_at is in the future, archival_enabled=0.
            if (!empty($row['bichon_account_id']) && !empty($row['in_grace'])) {
                $bichon_account_id = (string)$row['bichon_account_id'];

                // A1. Refresh IMAP creds + re-enable on the existing Bichon account.
                $upd = $bc->updateAccount($bichon_account_id, [
                    'imap'    => $imapStruct,
                    'enabled' => true,
                ]);
                if (empty($upd['ok'])) {
                    try {
                        $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                            (email_account_id, username, event_type, bichon_account_id, actor, details)
                            VALUES (?, ?, 'failed', ?, ?, ?)");
                        $stmt->execute([$email_id, $row['username'], $bichon_account_id, $username, substr('Bichon updateAccount failed: ' . (string)$upd['msg'], 0, 250)]);
                    } catch (Exception $e) {
                        error_log("emailmgmt::enableArchival audit log (failed) failed: " . $e->getMessage());
                    }
                    return [
                        'status'            => 'error',
                        'message'           => 'Bichon: ' . (string)$upd['msg'],
                        'bichon_account_id' => null,
                        'remaining'         => null,
                    ];
                }

                // A2. Re-create the Bichon user (it was deleted on soft-disable).
                $u = $bc->createUser([
                    'username'           => $row['email_address'],
                    'email'              => $row['email_address'],
                    'password'           => $effectiveBichonPassword,
                    'global_roles'       => [self::BICHON_ROLE_MEMBER],
                    'account_access_map' => [(string)$bichon_account_id => self::BICHON_ROLE_ACCOUNT_VIEWER],
                ]);
                if (empty($u['ok'])) {
                    try {
                        $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                            (email_account_id, username, event_type, bichon_account_id, actor, details)
                            VALUES (?, ?, 'failed', ?, ?, ?)");
                        $stmt->execute([$email_id, $row['username'], $bichon_account_id, $username, substr('Bichon user create failed (resume): ' . (string)$u['msg'], 0, 250)]);
                    } catch (Exception $e) {
                        error_log("emailmgmt::enableArchival audit log (failed) failed: " . $e->getMessage());
                    }
                    return [
                        'status'            => 'error',
                        'message'           => 'Bichon: ' . (string)$u['msg'],
                        'bichon_account_id' => null,
                        'remaining'         => null,
                    ];
                }

                $bichon_user_id = isset($u['user_id']) ? (string)$u['user_id'] : null;

                // A3. Persist (preserve bichon_account_id).
                $stmt = $this->db->prepare("
                    UPDATE whp.email_accounts
                    SET archival_enabled    = 1,
                        bichon_user_id      = ?,
                        archival_purge_at   = NULL,
                        archival_paused_at  = NULL,
                        archival_last_error = NULL
                    WHERE id = ?
                ");
                $stmt->execute([$bichon_user_id, $email_id]);

                // A4. Audit (resumed).
                $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                    (email_account_id, username, event_type, bichon_account_id, actor, details)
                    VALUES (?, ?, 'resumed', ?, ?, ?)");
                $stmt->execute([$email_id, $row['username'], $bichon_account_id, $username, 'Grace-period resume; existing archive retained']);

                // A5. Re-assert sync_folders coverage (best-effort).
                $this->syncBichonFolders($bichon_account_id);

                $post = $this->canEnableArchival($email_user);

                return [
                    'status'            => 'success',
                    'message'           => 'Archival re-enabled (existing archive retained)',
                    'bichon_account_id' => $bichon_account_id,
                    'remaining'         => isset($post['remaining']) ? (int)$post['remaining'] : null,
                ];
            }

            // ===== Branch B: fresh enable =====
            // 6. Call Bichon (do not log $imapPassword).
            // Schema (Bichon AccountCreateRequest, crates/core/src/account/payload.rs):
            //   - email:        String (required)        — the mailbox address
            //   - login_name:   Option<String>           — IMAP login (same as email for our mail server)
            //   - account_name: Option<String>           — display label
            //   - imap:         Option<ImapConfig>       — host / port / encryption (Ssl|StartTls|None) / auth { auth_type:Password|OAuth2, password }
            //   - enabled:      bool (required)
            //   - account_type: AccountType (required)   — IMAP | NoSync
            //   - use_dangerous: bool (required)
            $r = $bc->createAccount([
                'email'             => $row['email_address'],
                'login_name'        => $row['email_address'],
                'account_name'      => $row['email_address'],
                'imap'              => $imapStruct,
                'enabled'           => true,
                'account_type'      => 'IMAP',
                'sync_interval_min' => 15,
                'use_dangerous'     => false,
            ]);

            if (empty($r['ok'])) {
                // Audit failure
                try {
                    $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                        (email_account_id, username, event_type, bichon_account_id, actor, details)
                        VALUES (?, ?, 'failed', NULL, ?, ?)");
                    $stmt->execute([$email_id, $row['username'], $username, substr((string)$r['msg'], 0, 250)]);
                } catch (Exception $e) {
                    error_log("emailmgmt::enableArchival audit log (failed) failed: " . $e->getMessage());
                }
                return [
                    'status'            => 'error',
                    'message'           => 'Bichon: ' . (string)$r['msg'],
                    'bichon_account_id' => null,
                    'remaining'         => null,
                ];
            }

            $bichon_account_id = isset($r['account_id']) ? (string)$r['account_id'] : null;

            // 7. Create Bichon user mapped to the new account.
            $u = $bc->createUser([
                'username'           => $row['email_address'],
                'email'              => $row['email_address'],
                'password'           => $effectiveBichonPassword,
                'global_roles'       => [self::BICHON_ROLE_MEMBER],
                'account_access_map' => [(string)$bichon_account_id => self::BICHON_ROLE_ACCOUNT_VIEWER],
            ]);
            if (empty($u['ok'])) {
                // Best-effort cleanup: account exists but user creation failed.
                try {
                    $bc->deleteAccount((string)$bichon_account_id);
                } catch (Throwable $e) {
                    error_log("emailmgmt::enableArchival: cleanup deleteAccount threw: " . $e->getMessage());
                }
                try {
                    $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                        (email_account_id, username, event_type, bichon_account_id, actor, details)
                        VALUES (?, ?, 'failed', ?, ?, ?)");
                    $stmt->execute([$email_id, $row['username'], $bichon_account_id, $username, substr('Bichon user create failed: ' . (string)$u['msg'], 0, 250)]);
                } catch (Exception $e) {
                    error_log("emailmgmt::enableArchival audit log (failed) failed: " . $e->getMessage());
                }
                return [
                    'status'            => 'error',
                    'message'           => 'Bichon user create failed: ' . (string)$u['msg'],
                    'bichon_account_id' => null,
                    'remaining'         => null,
                ];
            }

            $bichon_user_id = isset($u['user_id']) ? (string)$u['user_id'] : null;

            // 8. Persist
            $stmt = $this->db->prepare("
                UPDATE whp.email_accounts
                SET archival_enabled = 1,
                    bichon_account_id = ?,
                    bichon_user_id = ?,
                    archival_enabled_at = NOW(),
                    archival_paused_at = NULL,
                    archival_last_error = NULL,
                    archival_purge_at = NULL
                WHERE id = ?
            ");
            $stmt->execute([$bichon_account_id, $bichon_user_id, $email_id]);

            // 8b. Best-effort: ensure Bichon syncs every known folder, not just
            // its INBOX/Sent default. Failure here is non-fatal — the daily
            // reconciliation cron will re-attempt this for any archive whose
            // sync_folders doesn't cover known_folders.
            $this->syncBichonFolders($bichon_account_id);

            // 9. Audit log
            $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                (email_account_id, username, event_type, bichon_account_id, actor)
                VALUES (?, ?, 'enabled', ?, ?)");
            $stmt->execute([$email_id, $row['username'], $bichon_account_id, $username]);

            // 10. Recompute remaining
            $post = $this->canEnableArchival($email_user);

            return [
                'status'            => 'success',
                'message'           => 'Archival enabled',
                'bichon_account_id' => $bichon_account_id,
                'remaining'         => isset($post['remaining']) ? (int)$post['remaining'] : null,
            ];
        } catch (Exception $e) {
            // NOTE: do not include passwords in any log line
            error_log("emailmgmt::enableArchival failed for email_id=" . (int)$email_id . ": " . $e->getMessage());
            return ['status' => 'error', 'message' => 'Failed to enable archival', 'bichon_account_id' => null, 'remaining' => null];
        }
    }

    /**
     * Disable Bichon archival for an email account — SOFT delete.
     *
     * Removes the Bichon RBAC user immediately (so the customer can no longer
     * log into the WebUI), but keeps the Bichon archival ACCOUNT and its
     * archived mail for 14 days in case the customer disabled accidentally.
     * Re-enabling within 14 days resumes the same account (no data loss).
     * After 14 days the daily reconciliation cron purges the Bichon account
     * via purgeExpiredArchives().
     *
     * @param int    $email_id
     * @param string $username     caller's WHP username
     * @param bool   $purgeRemote  retained for backward-compat but no longer
     *                              hard-deletes anything; the only effect now
     *                              is on the audit log message. Hard-deletes
     *                              go through the deleteEmailAccount() hook
     *                              (which is the only path that should ever
     *                              skip the grace period).
     * @return array ['status'=>'success'|'error', 'message'=>string, 'bichon_account_id'=>?string]
     */
    public function disableArchival($email_id, $username, $purgeRemote = true) {
        if (!$this->db) {
            return ['status' => 'error', 'message' => 'Database connection failed', 'bichon_account_id' => null];
        }

        try {
            // 1. Ownership check
            $stmt = $this->db->prepare("
                SELECT id, email_address, username, archival_enabled,
                       bichon_account_id, bichon_user_id
                FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$row) {
                return ['status' => 'error', 'message' => 'Permission denied', 'bichon_account_id' => null];
            }
            if ($username !== 'root' && $row['username'] !== $username) {
                return ['status' => 'error', 'message' => 'Permission denied', 'bichon_account_id' => null];
            }

            // 2. Already disabled?
            if (empty($row['archival_enabled'])) {
                return ['status' => 'error', 'message' => 'Archival is not enabled for this mailbox', 'bichon_account_id' => null];
            }

            // 3. Best-effort: delete the Bichon USER (NOT the account/data).
            if (!empty($row['bichon_user_id']) && BichonClient::isConfigured()) {
                try {
                    $bc = new BichonClient();
                    $del = $bc->deleteUser((string)$row['bichon_user_id']);
                    if (empty($del['ok'])) {
                        error_log("emailmgmt::disableArchival Bichon deleteUser returned non-ok for id=" . $row['bichon_user_id'] . " msg=" . (string)$del['msg']);
                    }
                } catch (Throwable $e) {
                    error_log("emailmgmt::disableArchival Bichon deleteUser threw: " . $e->getMessage());
                }
            }

            // 4. Soft-disable: keep bichon_account_id, schedule purge in 14 days.
            $stmt = $this->db->prepare("
                UPDATE whp.email_accounts
                SET archival_enabled    = 0,
                    bichon_user_id      = NULL,
                    archival_paused_at  = NULL,
                    archival_last_error = NULL,
                    archival_purge_at   = TIMESTAMPADD(DAY, 14, NOW())
                WHERE id = ?
            ");
            $stmt->execute([$email_id]);

            // Read back the resulting purge_at for the audit details.
            $stmt = $this->db->prepare("SELECT archival_purge_at FROM whp.email_accounts WHERE id = ?");
            $stmt->execute([$email_id]);
            $purgeRow = $stmt->fetch(PDO::FETCH_ASSOC);
            $purge_at = $purgeRow['archival_purge_at'] ?? '';

            // 5. Audit (soft_disabled).
            $detailsBase = 'Archive retained until ' . $purge_at;
            // $purgeRemote is retained for backward-compat; surface it in details
            // for traceability without changing behavior.
            $details = $purgeRemote
                ? $detailsBase
                : $detailsBase . ' (purge_remote=false on caller)';
            $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                (email_account_id, username, event_type, bichon_account_id, actor, details)
                VALUES (?, ?, 'soft_disabled', ?, ?, ?)");
            $stmt->execute([$email_id, $row['username'], $row['bichon_account_id'], $username, $details]);

            return [
                'status'            => 'success',
                'message'           => 'Archival disabled. Your archive will be retained until ' . $purge_at . ' in case you change your mind.',
                'bichon_account_id' => $row['bichon_account_id'],
                'purge_at'          => $purge_at,
            ];
        } catch (Exception $e) {
            error_log("emailmgmt::disableArchival failed for email_id=" . (int)$email_id . ": " . $e->getMessage());
            return ['status' => 'error', 'message' => 'Failed to disable archival', 'bichon_account_id' => null];
        }
    }

    /**
     * Reset the customer's Bichon WebUI login password. Does NOT touch the
     * mailbox / IMAP password.
     *
     * SECURITY: $newPassword must NEVER be logged.
     *
     * @param int    $email_id
     * @param string $username     caller's WHP username (may be 'root')
     * @param string $newPassword  new Bichon login password (plaintext)
     * @return array ['status'=>'success'|'error', 'message'=>string]
     */
    public function resetBichonPassword($email_id, $username, $newPassword) {
        if (!$this->db) {
            return ['status' => 'error', 'message' => 'Database connection failed'];
        }
        if (!is_string($newPassword) || $newPassword === '') {
            return ['status' => 'error', 'message' => 'New password is required'];
        }

        try {
            // 1. Ownership check
            $stmt = $this->db->prepare("
                SELECT id, email_address, username, archival_enabled, bichon_user_id
                FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$row) {
                return ['status' => 'error', 'message' => 'Permission denied'];
            }
            if ($username !== 'root' && $row['username'] !== $username) {
                return ['status' => 'error', 'message' => 'Permission denied'];
            }
            if (empty($row['archival_enabled']) || empty($row['bichon_user_id'])) {
                return ['status' => 'error', 'message' => 'Archival is not enabled for this mailbox'];
            }
            if (!BichonClient::isConfigured()) {
                return ['status' => 'error', 'message' => 'Email archival is not configured on this server'];
            }

            // 2. Update on Bichon side
            $bc = new BichonClient();
            $upd = $bc->updateUser((string)$row['bichon_user_id'], ['password' => $newPassword]);
            if (empty($upd['ok'])) {
                try {
                    $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                        (email_account_id, username, event_type, bichon_account_id, actor, details)
                        VALUES (?, ?, 'failed', NULL, ?, ?)");
                    $stmt->execute([$email_id, $row['username'], $username, substr('Password reset failed: ' . (string)$upd['msg'], 0, 250)]);
                } catch (Exception $e) {
                    error_log("emailmgmt::resetBichonPassword audit log (failed) failed: " . $e->getMessage());
                }
                return ['status' => 'error', 'message' => 'Bichon: ' . (string)$upd['msg']];
            }

            // 3. Audit
            $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                (email_account_id, username, event_type, bichon_account_id, actor, details)
                VALUES (?, ?, 'password_reset', NULL, ?, 'Customer reset Bichon login password')");
            $stmt->execute([$email_id, $row['username'], $username]);

            return ['status' => 'success', 'message' => 'Archive password updated'];
        } catch (Exception $e) {
            error_log("emailmgmt::resetBichonPassword failed for email_id=" . (int)$email_id . ": " . $e->getMessage());
            return ['status' => 'error', 'message' => 'Failed to reset archive password'];
        }
    }

    /**
     * Purge Bichon archival accounts whose 14-day soft-delete grace period
     * has elapsed. Called from the daily reconciliation cron.
     *
     * @return array ['purged'=>int, 'failed'=>int, 'errors'=>array<string>]
     */
    public function purgeExpiredArchives() {
        $summary = ['purged' => 0, 'failed' => 0, 'errors' => []];
        if (!$this->db) {
            $summary['errors'][] = 'db unavailable';
            return $summary;
        }
        if (!BichonClient::isConfigured()) {
            // Nothing to do — leave rows in place for when Bichon is re-enabled.
            return $summary;
        }

        try {
            $rows = $this->db->query("
                SELECT id, username, email_address, bichon_account_id
                FROM whp.email_accounts
                WHERE archival_purge_at IS NOT NULL
                  AND archival_purge_at <= NOW()
                  AND bichon_account_id IS NOT NULL
            ")->fetchAll(PDO::FETCH_ASSOC);

            if (empty($rows)) {
                return $summary;
            }

            $bc = new BichonClient();
            foreach ($rows as $r) {
                $bid = (string)$r['bichon_account_id'];
                try {
                    $del = $bc->deleteAccount($bid);
                    if (empty($del['ok'])) {
                        $summary['failed']++;
                        $summary['errors'][] = $r['email_address'] . ': ' . (string)$del['msg'];
                        error_log("emailmgmt::purgeExpiredArchives deleteAccount non-ok for id=" . $bid . " msg=" . (string)$del['msg']);
                        continue;
                    }
                } catch (Throwable $e) {
                    $summary['failed']++;
                    $summary['errors'][] = $r['email_address'] . ': ' . $e->getMessage();
                    error_log("emailmgmt::purgeExpiredArchives deleteAccount threw: " . $e->getMessage());
                    continue;
                }

                // Clear local pointers + audit.
                try {
                    $u = $this->db->prepare("
                        UPDATE whp.email_accounts
                           SET bichon_account_id = NULL,
                               archival_purge_at = NULL
                         WHERE id = ?
                    ");
                    $u->execute([(int)$r['id']]);

                    $a = $this->db->prepare("INSERT INTO whp.email_archival_events
                        (email_account_id, username, event_type, bichon_account_id, actor, details)
                        VALUES (?, ?, 'purged', ?, 'system', '14-day grace expired; archive deleted')");
                    $a->execute([(int)$r['id'], $r['username'], $bid]);

                    $summary['purged']++;
                } catch (Exception $e) {
                    $summary['failed']++;
                    $summary['errors'][] = $r['email_address'] . ' (post-delete bookkeeping): ' . $e->getMessage();
                    error_log("emailmgmt::purgeExpiredArchives bookkeeping failed: " . $e->getMessage());
                }
            }
        } catch (Exception $e) {
            $summary['errors'][] = $e->getMessage();
            error_log("emailmgmt::purgeExpiredArchives failed: " . $e->getMessage());
        }

        return $summary;
    }

    /**
     * Get current archival status for an email account.
     *
     * @param int $email_id
     * @return array
     */
    public function getArchivalStatus($email_id) {
        if (!$this->db) {
            return ['status' => 'error', 'message' => 'Database connection failed'];
        }

        try {
            $stmt = $this->db->prepare("
                SELECT archival_enabled, bichon_account_id, archival_enabled_at,
                       archival_paused_at, archival_last_error
                FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$row) {
                return ['status' => 'error', 'message' => 'Email account not found'];
            }

            return [
                'status'              => 'success',
                'archival_enabled'    => (int)$row['archival_enabled'],
                'bichon_account_id'   => $row['bichon_account_id'],
                'archival_enabled_at' => $row['archival_enabled_at'],
                'archival_paused_at'  => $row['archival_paused_at'],
                'archival_last_error' => $row['archival_last_error'],
            ];
        } catch (Exception $e) {
            error_log("emailmgmt::getArchivalStatus failed for email_id=" . (int)$email_id . ": " . $e->getMessage());
            return ['status' => 'error', 'message' => 'Failed to retrieve archival status'];
        }
    }

    /**
     * Mark an email account's archival as paused. Called by reconciliation cron.
     * No ownership check — internal use only.
     *
     * @param int    $email_id
     * @param string $reason   stored truncated to 250 chars in archival_last_error
     * @return void
     */
    public function markArchivalPaused($email_id, $reason) {
        if (!$this->db) {
            return;
        }

        try {
            $reason_short = substr((string)$reason, 0, 250);

            $stmt = $this->db->prepare("
                UPDATE whp.email_accounts
                SET archival_paused_at = COALESCE(archival_paused_at, NOW()),
                    archival_last_error = ?
                WHERE id = ?
            ");
            $stmt->execute([$reason_short, $email_id]);

            $stmt = $this->db->prepare("INSERT INTO whp.email_archival_events
                (email_account_id, username, event_type, bichon_account_id, actor, details)
                SELECT id, username, 'paused', bichon_account_id, 'system', ?
                FROM whp.email_accounts WHERE id = ?");
            $stmt->execute([$reason_short, $email_id]);
        } catch (Exception $e) {
            error_log("emailmgmt::markArchivalPaused failed for email_id=" . (int)$email_id . ": " . $e->getMessage());
        }
    }

    /**
     * Best-effort: copy Bichon's known_folders into sync_folders and turn on
     * auto_download_new_mailboxes so the customer's archive captures every
     * folder, not just INBOX/Sent. Bichon's create endpoint defaults
     * sync_folders to a small subset; without this fix-up customers complain
     * that "only Inbox and Sent are being archived".
     *
     * Idempotent: skips the update when sync_folders already covers
     * known_folders. Failures are logged but never propagated — the archive
     * continues working with whatever sync_folders Bichon picked.
     *
     * @return bool true if Bichon ended up with a covering sync_folders
     */
    public function syncBichonFolders($bichon_account_id) {
        if (empty($bichon_account_id)) return false;
        try {
            require_once __DIR__ . '/BichonClient.php';
            $bc = new BichonClient();
            $get = $bc->getAccount((string)$bichon_account_id);
            if (empty($get['ok'])) {
                error_log("syncBichonFolders: getAccount failed for $bichon_account_id: " . ($get['msg'] ?? ''));
                return false;
            }
            $known   = $get['raw']['known_folders'] ?? [];
            $current = $get['raw']['sync_folders']  ?? [];
            if (!is_array($known) || empty($known)) {
                return false; // nothing discovered yet, try again later
            }
            // Already covering? No-op the update.
            if (is_array($current) && empty(array_diff($known, $current))) {
                return true;
            }
            $upd = $bc->updateAccount((string)$bichon_account_id, [
                'sync_folders'                => array_values($known),
                'auto_download_new_mailboxes' => true,
            ]);
            if (empty($upd['ok'])) {
                error_log("syncBichonFolders: updateAccount failed for $bichon_account_id: " . ($upd['msg'] ?? ''));
                return false;
            }
            return true;
        } catch (Throwable $e) {
            error_log("syncBichonFolders: exception for $bichon_account_id: " . $e->getMessage());
            return false;
        }
    }

    /**
     * Resume a paused mailbox by re-supplying the IMAP password.
     *
     * State precondition: archival_enabled = 1 AND bichon_account_id IS NOT NULL
     * AND archival_paused_at IS NOT NULL. Updates the Bichon account's IMAP
     * credentials, clears the paused-state markers, audits 'resumed'.
     *
     * Called from the edit-mailbox form when the user re-types their mailbox
     * password while the row is in the paused state.
     */
    public function resumeArchival($email_id, $username, $newImapPassword) {
        if (!$this->db) {
            return ['status' => 'error', 'message' => 'Database connection failed'];
        }

        try {
            $stmt = $this->db->prepare("
                SELECT id, email_address, username, archival_enabled, bichon_account_id, archival_paused_at
                FROM whp.email_accounts WHERE id = ?
            ");
            $stmt->execute([$email_id]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$row) {
                return ['status' => 'error', 'message' => 'Permission denied'];
            }
            if ($username !== 'root' && $row['username'] !== $username) {
                return ['status' => 'error', 'message' => 'Permission denied'];
            }
            if (empty($row['archival_enabled']) || empty($row['bichon_account_id'])) {
                return ['status' => 'error', 'message' => 'Archival is not enabled for this mailbox'];
            }
            if (empty($row['archival_paused_at'])) {
                return ['status' => 'error', 'message' => 'Archival is not paused'];
            }
            if ($newImapPassword === null || $newImapPassword === '') {
                return ['status' => 'error', 'message' => 'New mailbox password required'];
            }

            $mail_server = '';
            $contents = @file_get_contents('/docker/whp/settings.json');
            if ($contents !== false) {
                $settings = json_decode($contents, true);
                if (is_array($settings) && !empty($settings['mail_server'])) {
                    $mail_server = (string)$settings['mail_server'];
                }
            }
            if ($mail_server === '') {
                return ['status' => 'error', 'message' => 'Mail server not configured'];
            }

            $bc = new BichonClient();
            $r = $bc->updateAccount((string)$row['bichon_account_id'], [
                'enabled' => true,
                'imap' => [
                    'host'       => $mail_server,
                    'port'       => 993,
                    'encryption' => 'Ssl',
                    'auth' => [
                        'auth_type' => 'Password',
                        'password'  => $newImapPassword,
                    ],
                ],
            ]);
            if (empty($r['ok'])) {
                return ['status' => 'error', 'message' => 'Bichon: ' . (string)$r['msg']];
            }

            $u = $this->db->prepare("
                UPDATE whp.email_accounts
                   SET archival_paused_at = NULL,
                       archival_last_error = NULL
                 WHERE id = ?
            ");
            $u->execute([$email_id]);

            $a = $this->db->prepare("INSERT INTO whp.email_archival_events
                (email_account_id, username, event_type, bichon_account_id, actor, details)
                VALUES (?, ?, 'resumed', ?, ?, ?)");
            $a->execute([$email_id, $row['username'], $row['bichon_account_id'], $username, 'IMAP credentials refreshed via edit modal']);

            // Re-assert sync_folders coverage (best-effort).
            $this->syncBichonFolders($row['bichon_account_id']);

            return ['status' => 'success', 'message' => 'Archival resumed'];
        } catch (Throwable $e) {
            error_log("emailmgmt::resumeArchival failed for email_id=" . (int)$email_id . ": " . $e->getMessage());
            return ['status' => 'error', 'message' => 'Resume failed: ' . $e->getMessage()];
        }
    }
}
?> 