<?php
require_once __DIR__ . '/../config/config.php';

$phpmailerPath = __DIR__ . '/../vendor/phpmailer/phpmailer/src/PHPMailer.php';
$smtpPath = __DIR__ . '/../vendor/phpmailer/phpmailer/src/SMTP.php';
$exceptionPath = __DIR__ . '/../vendor/phpmailer/phpmailer/src/Exception.php';

if (!file_exists($phpmailerPath)) {
    throw new Exception("PHPMailer not found at: $phpmailerPath - Please extract PHPMailer to vendor/phpmailer/phpmailer/src/");
}

require_once $phpmailerPath;
require_once $smtpPath;
require_once $exceptionPath;

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception as PHPMailerException;

require_once 'Logger.php';

/**
 * Manages the admin-owned mailbox (send via SMTP, receive via IMAP).
 * Works against any standard SMTP/IMAP provider, including cPanel-hosted
 * mailboxes (get the exact host/ports from cPanel > Email Accounts > Connect Devices).
 */
class MailboxService {
    /** Used whenever no display name is configured, so the From header never falls back to the raw
     *  mailbox address (mail clients render an address-only From as just the local part, e.g. "mailbox"). */
    const DEFAULT_DISPLAY_NAME = 'IMS Admin Mailbox';

    private $conn;
    private $logger;
    private $settings;
    private $accountId;

    public function __construct($conn, $accountId, $logger = null) {
        $this->conn = $conn;
        $this->accountId = (int)$accountId;
        $this->logger = $logger;
        self::ensureTable($conn);
        $this->settings = $this->loadSettings();
    }

    public function getAccountId() {
        return $this->accountId;
    }

    /**
     * Creates the mailbox_accounts table on first use (the 2026_07_08 migration that introduced
     * it is not guaranteed to have been run manually) and carries over the old single-account
     * mailbox_settings row, if any, so upgrading doesn't silently drop an existing configuration.
     */
    public static function ensureTable(PDO $conn) {
        $conn->exec(
            'CREATE TABLE IF NOT EXISTS mailbox_accounts (' .
            'account_id INT AUTO_INCREMENT PRIMARY KEY,' .
            'branch_id INT DEFAULT NULL,' .
            'account_label VARCHAR(150) NOT NULL,' .
            'display_name VARCHAR(150) NOT NULL DEFAULT "IMS Admin Mailbox",' .
            'email VARCHAR(190) NOT NULL,' .
            'password_encrypted TEXT DEFAULT NULL,' .
            'smtp_host VARCHAR(190) DEFAULT NULL,' .
            'smtp_port INT NOT NULL DEFAULT 587,' .
            'smtp_encryption ENUM("tls","ssl") NOT NULL DEFAULT "tls",' .
            'imap_host VARCHAR(190) DEFAULT NULL,' .
            'imap_port INT NOT NULL DEFAULT 993,' .
            'imap_encryption ENUM("tls","ssl") NOT NULL DEFAULT "ssl",' .
            'verify_cert TINYINT(1) NOT NULL DEFAULT 1,' .
            'is_enabled TINYINT(1) NOT NULL DEFAULT 0,' .
            'is_default TINYINT(1) NOT NULL DEFAULT 0,' .
            'created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,' .
            'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,' .
            'updated_by INT DEFAULT NULL,' .
            'UNIQUE KEY email (email),' .
            'KEY branch_id (branch_id),' .
            'KEY updated_by (updated_by)' .
            ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci'
        );

        $legacyTableExists = (bool)$conn->query("SHOW TABLES LIKE 'mailbox_settings'")->fetchColumn();
        if ($legacyTableExists) {
            $conn->exec(
                "INSERT INTO mailbox_accounts
                    (branch_id, account_label, display_name, email, password_encrypted,
                     smtp_host, smtp_port, smtp_encryption, imap_host, imap_port, imap_encryption,
                     verify_cert, is_enabled, is_default)
                 SELECT
                    NULL,
                    'Head Office (migrated)',
                    COALESCE(MAX(CASE WHEN setting_key='mailbox_display_name' THEN setting_value END), 'IMS Admin Mailbox'),
                    MAX(CASE WHEN setting_key='mailbox_email' THEN setting_value END),
                    MAX(CASE WHEN setting_key='mailbox_password' THEN setting_value END),
                    MAX(CASE WHEN setting_key='smtp_host' THEN setting_value END),
                    COALESCE(MAX(CASE WHEN setting_key='smtp_port' THEN setting_value END), 587),
                    COALESCE(MAX(CASE WHEN setting_key='smtp_encryption' THEN setting_value END), 'tls'),
                    MAX(CASE WHEN setting_key='imap_host' THEN setting_value END),
                    COALESCE(MAX(CASE WHEN setting_key='imap_port' THEN setting_value END), 993),
                    COALESCE(MAX(CASE WHEN setting_key='imap_encryption' THEN setting_value END), 'ssl'),
                    COALESCE(MAX(CASE WHEN setting_key='verify_cert' THEN setting_value END), 'true') != 'false',
                    COALESCE(MAX(CASE WHEN setting_key='mailbox_enabled' THEN setting_value END), 'false') = 'true',
                    1
                 FROM mailbox_settings
                 HAVING MAX(CASE WHEN setting_key='mailbox_email' THEN setting_value END) IS NOT NULL
                    AND MAX(CASE WHEN setting_key='mailbox_email' THEN setting_value END) != ''
                    AND NOT EXISTS (SELECT 1 FROM mailbox_accounts)"
            );
        }
    }

    // ---------------------------------------------------------------
    // Settings
    // ---------------------------------------------------------------

    private function loadSettings() {
        $defaults = [
            'mailbox_enabled' => 'false',
            'mailbox_display_name' => 'IMS Admin Mailbox',
            'mailbox_email' => '',
            'mailbox_password' => '',
            'smtp_host' => '',
            'smtp_port' => '465',
            'smtp_encryption' => 'ssl',
            'imap_host' => '',
            'imap_port' => '993',
            'imap_encryption' => 'ssl',
            'verify_cert' => 'true',
        ];

        try {
            $stmt = $this->conn->prepare("SELECT * FROM mailbox_accounts WHERE account_id = :account_id");
            $stmt->bindValue(':account_id', $this->accountId, PDO::PARAM_INT);
            $stmt->execute();
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                $password = $row['password_encrypted'];
                $defaults['mailbox_enabled'] = $row['is_enabled'] ? 'true' : 'false';
                $defaults['mailbox_display_name'] = $row['display_name'];
                $defaults['mailbox_email'] = $row['email'];
                $defaults['mailbox_password'] = ($password !== '' && $password !== null) ? self::decrypt($password) : '';
                $defaults['smtp_host'] = $row['smtp_host'];
                $defaults['smtp_port'] = $row['smtp_port'];
                $defaults['smtp_encryption'] = $row['smtp_encryption'];
                $defaults['imap_host'] = $row['imap_host'];
                $defaults['imap_port'] = $row['imap_port'];
                $defaults['imap_encryption'] = $row['imap_encryption'];
                $defaults['verify_cert'] = $row['verify_cert'] ? 'true' : 'false';
            }
        } catch (PDOException $e) {
            error_log('MailboxService: failed to load mailbox_accounts - ' . $e->getMessage());
        }

        return $defaults;
    }

    /** All configured mailbox accounts (for the admin account-switcher and Settings list). */
    public static function listAccounts($conn) {
        self::ensureTable($conn);
        $stmt = $conn->prepare(
            "SELECT ma.account_id, ma.branch_id, ma.account_label, ma.display_name, ma.email,
                    ma.is_enabled, ma.is_default, b.branch_name
             FROM mailbox_accounts ma
             LEFT JOIN branches b ON b.branch_id = ma.branch_id
             ORDER BY ma.is_default DESC, ma.account_label ASC"
        );
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /** Creates a new mailbox account and returns its account_id. */
    public static function createAccount($conn, array $data, $userId) {
        self::ensureTable($conn);
        $password = $data['mailbox_password'] ?? '';
        $encryptedPassword = ($password !== '' && !preg_match('/^\*+$/', $password)) ? self::encrypt($password) : null;

        $stmt = $conn->prepare(
            "INSERT INTO mailbox_accounts
                (branch_id, account_label, display_name, email, password_encrypted,
                 smtp_host, smtp_port, smtp_encryption, imap_host, imap_port, imap_encryption,
                 verify_cert, is_enabled, is_default, updated_by)
             VALUES
                (:branch_id, :account_label, :display_name, :email, :password_encrypted,
                 :smtp_host, :smtp_port, :smtp_encryption, :imap_host, :imap_port, :imap_encryption,
                 :verify_cert, :is_enabled, :is_default, :updated_by)"
        );
        $stmt->bindValue(':branch_id', $data['branch_id'] ?: null, empty($data['branch_id']) ? PDO::PARAM_NULL : PDO::PARAM_INT);
        $stmt->bindValue(':account_label', $data['account_label'] ?? '');
        $stmt->bindValue(':display_name', trim((string)($data['mailbox_display_name'] ?? '')) !== '' ? $data['mailbox_display_name'] : self::DEFAULT_DISPLAY_NAME);
        $stmt->bindValue(':email', $data['mailbox_email'] ?? '');
        $stmt->bindValue(':password_encrypted', $encryptedPassword);
        $stmt->bindValue(':smtp_host', $data['smtp_host'] ?? '');
        $stmt->bindValue(':smtp_port', (int)($data['smtp_port'] ?? 587), PDO::PARAM_INT);
        $stmt->bindValue(':smtp_encryption', $data['smtp_encryption'] ?? 'tls');
        $stmt->bindValue(':imap_host', $data['imap_host'] ?? '');
        $stmt->bindValue(':imap_port', (int)($data['imap_port'] ?? 993), PDO::PARAM_INT);
        $stmt->bindValue(':imap_encryption', $data['imap_encryption'] ?? 'ssl');
        $stmt->bindValue(':verify_cert', !empty($data['verify_cert']) ? 1 : 0, PDO::PARAM_INT);
        $stmt->bindValue(':is_enabled', !empty($data['mailbox_enabled']) ? 1 : 0, PDO::PARAM_INT);
        $stmt->bindValue(':is_default', !empty($data['is_default']) ? 1 : 0, PDO::PARAM_INT);
        $stmt->bindValue(':updated_by', $userId, $userId === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
        $stmt->execute();

        return (int)$conn->lastInsertId();
    }

    /** Deletes a mailbox account (its SMTP/IMAP config only - no local message cache to clean up). */
    public static function deleteAccount($conn, $accountId) {
        self::ensureTable($conn);
        $stmt = $conn->prepare("DELETE FROM mailbox_accounts WHERE account_id = :account_id");
        $stmt->bindValue(':account_id', (int)$accountId, PDO::PARAM_INT);
        $stmt->execute();
    }

    public function isConfigured() {
        return $this->settings['mailbox_enabled'] === 'true'
            && !empty($this->settings['mailbox_email'])
            && !empty($this->settings['mailbox_password']);
    }

    /** Settings safe to render back to the admin UI (password masked). */
    public function getDisplaySettings() {
        $safe = $this->settings;
        $safe['mailbox_password'] = !empty($this->settings['mailbox_password']) ? str_repeat('*', 12) : '';
        return $safe;
    }

    public function saveSettings(array $data, $userId) {
        $displayName = trim((string)($data['mailbox_display_name'] ?? ''));
        if ($displayName === '') {
            // Never save a blank display name: it would make the From header fall back to the raw
            // mailbox address, which mail clients show as just the local part (e.g. "mailbox").
            $displayName = $this->settings['mailbox_display_name'];
        }

        // Keep the previously stored password if the admin left the masked field untouched.
        $password = $data['mailbox_password'] ?? '';
        if ($password === '' || preg_match('/^\*+$/', $password)) {
            $passwordSql = ''; // leave password_encrypted untouched
        } else {
            $passwordSql = 'password_encrypted = :password,';
        }

        $sql = "UPDATE mailbox_accounts SET
                    account_label = :account_label,
                    branch_id = :branch_id,
                    display_name = :display_name,
                    email = :email,
                    $passwordSql
                    smtp_host = :smtp_host,
                    smtp_port = :smtp_port,
                    smtp_encryption = :smtp_encryption,
                    imap_host = :imap_host,
                    imap_port = :imap_port,
                    imap_encryption = :imap_encryption,
                    verify_cert = :verify_cert,
                    is_enabled = :is_enabled,
                    is_default = :is_default,
                    updated_at = NOW(),
                    updated_by = :user_id
                WHERE account_id = :account_id";

        try {
            $stmt = $this->conn->prepare($sql);
            $stmt->bindValue(':account_label', $data['account_label'] ?? $this->settings['mailbox_display_name']);
            $stmt->bindValue(':branch_id', $data['branch_id'] ?: null, empty($data['branch_id']) ? PDO::PARAM_NULL : PDO::PARAM_INT);
            $stmt->bindValue(':display_name', $displayName);
            $stmt->bindValue(':email', $data['mailbox_email'] ?? $this->settings['mailbox_email']);
            if ($passwordSql !== '') {
                $stmt->bindValue(':password', self::encrypt($password));
            }
            $stmt->bindValue(':smtp_host', $data['smtp_host'] ?? $this->settings['smtp_host']);
            $stmt->bindValue(':smtp_port', (int)($data['smtp_port'] ?? $this->settings['smtp_port']), PDO::PARAM_INT);
            $stmt->bindValue(':smtp_encryption', $data['smtp_encryption'] ?? $this->settings['smtp_encryption']);
            $stmt->bindValue(':imap_host', $data['imap_host'] ?? $this->settings['imap_host']);
            $stmt->bindValue(':imap_port', (int)($data['imap_port'] ?? $this->settings['imap_port']), PDO::PARAM_INT);
            $stmt->bindValue(':imap_encryption', $data['imap_encryption'] ?? $this->settings['imap_encryption']);
            $stmt->bindValue(':verify_cert', !empty($data['verify_cert']) ? 1 : 0, PDO::PARAM_INT);
            $stmt->bindValue(':is_enabled', !empty($data['mailbox_enabled']) ? 1 : 0, PDO::PARAM_INT);
            $stmt->bindValue(':is_default', !empty($data['is_default']) ? 1 : 0, PDO::PARAM_INT);
            $stmt->bindValue(':user_id', $userId, $userId === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
            $stmt->bindValue(':account_id', $this->accountId, PDO::PARAM_INT);
            $stmt->execute();
        } catch (PDOException $e) {
            throw new Exception('Failed to save mailbox settings: ' . $e->getMessage());
        }

        $this->settings = $this->loadSettings();
    }

    // ---------------------------------------------------------------
    // Encryption helpers (AES-256-CBC using config/config.php ENCRYPTION_KEY)
    // ---------------------------------------------------------------

    private static function requireOpenssl() {
        if (!extension_loaded('openssl')) {
            throw new Exception('The PHP OpenSSL extension is not enabled on this server. Enable "php_openssl" to store the mailbox password securely.');
        }
    }

    private static function encrypt($plaintext) {
        self::requireOpenssl();
        $key = hash('sha256', ENCRYPTION_KEY, true);
        $iv = openssl_random_pseudo_bytes(16);
        $ciphertext = openssl_encrypt($plaintext, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
        return base64_encode($iv . $ciphertext);
    }

    private static function decrypt($encoded) {
        self::requireOpenssl();
        $key = hash('sha256', ENCRYPTION_KEY, true);
        $raw = base64_decode($encoded);
        if ($raw === false || strlen($raw) < 17) {
            return '';
        }
        $iv = substr($raw, 0, 16);
        $ciphertext = substr($raw, 16);
        $plaintext = openssl_decrypt($ciphertext, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
        return $plaintext === false ? '' : $plaintext;
    }

    // ---------------------------------------------------------------
    // Sending (SMTP)
    // ---------------------------------------------------------------

    public function sendMail($to, $subject, $bodyHtml, $altBody = null, array $attachments = []) {
        if (!$this->isConfigured()) {
            throw new Exception('Mailbox is not configured. Please save mailbox settings first.');
        }

        $mail = new PHPMailer(true);
        try {
            $mail->isSMTP();
            $mail->Host       = $this->settings['smtp_host'];
            $mail->SMTPAuth   = true;
            $mail->Username   = $this->settings['mailbox_email'];
            $mail->Password   = $this->settings['mailbox_password'];
            $mail->SMTPSecure = $this->settings['smtp_encryption'] === 'ssl' ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS;
            $mail->Port       = (int)$this->settings['smtp_port'];

            if ($this->settings['verify_cert'] === 'false') {
                // Shared cPanel hosting often serves a cert for the shared server hostname
                // (e.g. *.web-hosting.com), not the customer's mail.<domain>. Only disable
                // verification when the admin has explicitly opted out in Mailbox Settings.
                $mail->SMTPOptions = [
                    'ssl' => [
                        'verify_peer' => false,
                        'verify_peer_name' => false,
                        'allow_self_signed' => true,
                    ],
                ];
            }

            $displayName = trim($this->settings['mailbox_display_name']) !== '' ? $this->settings['mailbox_display_name'] : self::DEFAULT_DISPLAY_NAME;

            $mail->CharSet = PHPMailer::CHARSET_UTF8;
            // Envelope sender (Return-Path) matching the authenticated From address keeps SPF aligned;
            // leaving it unset can let the server substitute a different envelope address.
            $mail->Sender = $this->settings['mailbox_email'];
            // Suppress PHPMailer's default "X-Mailer: PHPMailer x.x" header - a well-known signature
            // spam filters associate with bulk mail sent from generic PHP scripts on shared hosting.
            $mail->XMailer = ' ';

            // Without this, PHPMailer builds the Message-ID/HELO from $_SERVER['SERVER_NAME'] (or
            // "localhost.localdomain" if that's unset, e.g. under CLI/cron) - a domain that often has
            // nothing to do with the mailbox's own domain. A Message-ID/HELO domain that doesn't match
            // the From domain is itself a spam signal to strict filters, independent of SPF/DKIM.
            $mailDomain = substr((string)strrchr($this->settings['mailbox_email'], '@'), 1);
            if ($mailDomain !== '') {
                $mail->Hostname = $mailDomain;
            }

            $mail->setFrom($this->settings['mailbox_email'], $displayName);
            $mail->addAddress($to);
            $mail->addReplyTo($this->settings['mailbox_email'], $displayName);

            foreach ($attachments as $attachment) {
                if (!empty($attachment['path']) && file_exists($attachment['path'])) {
                    $mail->addAttachment($attachment['path'], $attachment['name'] ?? basename($attachment['path']));
                }
            }

            $mail->isHTML(true);
            $mail->Subject = $subject;
            $mail->Body    = $bodyHtml;
            $mail->AltBody = $altBody ?: 'Please use an HTML-compatible email client to view this message.';

            $result = $mail->send();

            if ($result && extension_loaded('imap')) {
                $this->appendToSentFolder($mail->getSentMIMEMessage());
            }

            if ($this->logger) {
                $this->logger->log('MAILBOX_EMAIL_SENT', 'mailbox', "Email sent to $to: $subject");
            }

            return $result;
        } catch (PHPMailerException $e) {
            if ($this->logger) {
                $this->logger->log('MAILBOX_EMAIL_FAILED', 'mailbox', "Failed to send to $to: " . $mail->ErrorInfo);
            }
            throw new Exception('Message could not be sent. Mailer Error: ' . $mail->ErrorInfo);
        }
    }

    public function sendTestEmail($to = null) {
        $recipient = $to ?: $this->settings['mailbox_email'];
        if (empty($recipient)) {
            throw new Exception('No recipient address available for the test email.');
        }

        $subject = 'IMS Mailbox - Test Email';
        $body = "
        <html><body>
            <h2 style='color:#28a745;'>Mailbox Test Successful</h2>
            <p>This mailbox is correctly configured to send email via SMTP.</p>
            <ul>
                <li><strong>SMTP Host:</strong> {$this->settings['smtp_host']}</li>
                <li><strong>SMTP Port:</strong> {$this->settings['smtp_port']}</li>
                <li><strong>Mailbox:</strong> {$this->settings['mailbox_email']}</li>
            </ul>
            <p><strong>Sent at:</strong> " . date('Y-m-d H:i:s') . "</p>
        </body></html>";

        $sentAt = date('Y-m-d H:i:s');
        $altBody = "Mailbox Test Successful\n\n"
            . "This mailbox is correctly configured to send email via SMTP.\n\n"
            . "SMTP Host: {$this->settings['smtp_host']}\n"
            . "SMTP Port: {$this->settings['smtp_port']}\n"
            . "Mailbox: {$this->settings['mailbox_email']}\n\n"
            . "Sent at: $sentAt";

        // A real plain-text alternative (not PHPMailer's generic fallback string) matters for
        // deliverability: spam filters weigh HTML-only mail with a boilerplate/mismatched AltBody heavily.
        return $this->sendMail($recipient, $subject, $body, $altBody);
    }

    // ---------------------------------------------------------------
    // Receiving (IMAP)
    // ---------------------------------------------------------------

    private function requireImapExtension() {
        if (!extension_loaded('imap')) {
            throw new Exception('The PHP IMAP extension is not enabled on this server. Enable "php_imap" (e.g. via cPanel MultiPHP INI Editor or php.ini) to receive mail.');
        }
    }

    private function buildMailboxRoot() {
        $encryption = $this->settings['imap_encryption'] === 'tls' ? 'tls' : 'ssl';
        $certFlag = $this->settings['verify_cert'] === 'false' ? '/novalidate-cert' : '';
        return '{' . $this->settings['imap_host'] . ':' . (int)$this->settings['imap_port'] . '/imap/' . $encryption . $certFlag . '}';
    }

    private function openImapConnection($folder = 'INBOX') {
        $this->requireImapExtension();

        if (!$this->isConfigured()) {
            throw new Exception('Mailbox is not configured. Please save mailbox settings first.');
        }

        $mailboxString = $this->buildMailboxRoot() . $folder;

        // The c-client library backing ext-imap can print connection errors directly,
        // bypassing normal @-suppression. Silence error display for the call and restore it after.
        $previousDisplayErrors = ini_set('display_errors', '0');
        imap_errors(); // drain any stale error queue so imap_last_error() below reflects this attempt only
        $connection = @imap_open($mailboxString, $this->settings['mailbox_email'], $this->settings['mailbox_password']);
        $lastError = imap_last_error();
        ini_set('display_errors', $previousDisplayErrors);

        if ($connection === false) {
            throw new Exception('IMAP connection failed: ' . ($lastError ?: 'unknown error'));
        }

        return $connection;
    }

    public function testImapConnection() {
        $connection = $this->openImapConnection();
        imap_close($connection);
        return true;
    }

    /** Returns the mailbox's folder tree, e.g. [['name' => 'INBOX.Sent', 'display' => 'Sent'], ...]. */
    public function listFolders() {
        $connection = $this->openImapConnection();

        try {
            $root = $this->buildMailboxRoot();

            $raw = @imap_list($connection, $root, '*');
            if (!$raw) {
                return [['name' => 'INBOX', 'display' => 'Inbox']];
            }

            $folders = [];
            foreach ($raw as $mailbox) {
                $name = str_replace($root, '', $mailbox);
                $display = preg_replace('/^INBOX\.?/i', '', $name);
                $display = $display === '' ? 'Inbox' : str_replace('.', ' / ', $display);
                $folders[] = ['name' => $name, 'display' => $display];
            }

            usort($folders, function ($a, $b) {
                if ($a['name'] === 'INBOX') return -1;
                if ($b['name'] === 'INBOX') return 1;
                return strcasecmp($a['display'], $b['display']);
            });

            return $folders;
        } finally {
            imap_close($connection);
        }
    }

    /** Best-effort guess at the provider's "Sent" folder name from the folder list. */
    private function detectSentFolder(array $folders) {
        $candidates = ['INBOX.Sent', 'Sent', 'INBOX.Sent Items', 'Sent Items', 'INBOX.Sent Messages'];
        $names = array_column($folders, 'name');
        foreach ($candidates as $candidate) {
            if (in_array($candidate, $names, true)) {
                return $candidate;
            }
        }
        foreach ($names as $name) {
            if (stripos($name, 'sent') !== false) {
                return $name;
            }
        }
        return null;
    }

    /** Appends a raw MIME message to the Sent folder, if one can be detected. Non-fatal on failure. */
    private function appendToSentFolder($rawMessage) {
        try {
            $sentFolder = $this->detectSentFolder($this->listFolders());
            if (!$sentFolder) {
                return;
            }
            $connection = $this->openImapConnection();
            imap_append($connection, $this->buildMailboxRoot() . $sentFolder, $rawMessage, "\\Seen");
            imap_close($connection);
        } catch (Exception $e) {
            error_log('MailboxService: failed to save sent copy - ' . $e->getMessage());
        }
    }

    /** Returns the most recent messages in the mailbox, newest first. */
    public function fetchInbox($limit = 20, $folder = 'INBOX') {
        $connection = $this->openImapConnection($folder);

        try {
            $totalMessages = imap_num_msg($connection);
            $messages = [];

            for ($i = $totalMessages; $i > 0 && count($messages) < $limit; $i--) {
                $overview = imap_fetch_overview($connection, $i, 0);
                if (empty($overview)) {
                    continue;
                }
                $info = $overview[0];
                $messages[] = [
                    'msg_no' => $i,
                    'uid' => imap_uid($connection, $i),
                    'from' => $info->from ?? '(unknown)',
                    'subject' => isset($info->subject) ? imap_utf8($info->subject) : '(no subject)',
                    'date' => $info->date ?? '',
                    'seen' => !empty($info->seen),
                ];
            }

            return $messages;
        } finally {
            imap_close($connection);
        }
    }

    /** Returns the full body of one message by IMAP UID. */
    public function fetchMessage($uid, $folder = 'INBOX') {
        $connection = $this->openImapConnection($folder);

        try {
            $msgNo = imap_msgno($connection, (int)$uid);
            if (!$msgNo) {
                throw new Exception('Message not found.');
            }

            $header = imap_headerinfo($connection, $msgNo);
            $structure = imap_fetchstructure($connection, $msgNo);

            $bodyHtml = null;
            $bodyPlain = null;

            if (isset($structure->parts) && count($structure->parts)) {
                foreach ($structure->parts as $partNum => $part) {
                    $partText = imap_fetchbody($connection, $msgNo, (string)($partNum + 1));
                    $partText = $this->decodePart($partText, $part);
                    if ($part->subtype === 'HTML' && $bodyHtml === null) {
                        $bodyHtml = $partText;
                    } elseif ($part->subtype === 'PLAIN' && $bodyPlain === null) {
                        $bodyPlain = $partText;
                    }
                }
            } else {
                $partText = imap_fetchbody($connection, $msgNo, '1');
                $bodyPlain = $this->decodePart($partText, $structure);
            }

            imap_setflag_full($connection, (string)$msgNo, '\\Seen');

            return [
                'subject' => $header->subject ?? '(no subject)',
                'from' => $header->fromaddress ?? '(unknown)',
                'to' => $header->toaddress ?? '',
                'date' => $header->date ?? '',
                'body_html' => $bodyHtml,
                'body_plain' => $bodyPlain,
            ];
        } finally {
            imap_close($connection);
        }
    }

    /** Best-effort guess at the provider's "Trash" folder name from the folder list. */
    private function detectTrashFolder(array $folders) {
        $candidates = ['INBOX.Trash', 'Trash', 'INBOX.Deleted Items', 'Deleted Items', 'INBOX.Deleted Messages'];
        $names = array_column($folders, 'name');
        foreach ($candidates as $candidate) {
            if (in_array($candidate, $names, true)) {
                return $candidate;
            }
        }
        foreach ($names as $name) {
            if (stripos($name, 'trash') !== false || stripos($name, 'deleted') !== false) {
                return $name;
            }
        }
        return null;
    }

    /**
     * Deletes a message by UID. Moves it to the account's Trash folder when one exists
     * and isn't already the source folder; otherwise permanently deletes it.
     */
    public function deleteMessage($uid, $folder = 'INBOX') {
        $connection = $this->openImapConnection($folder);

        try {
            $msgNo = imap_msgno($connection, (int)$uid);
            if (!$msgNo) {
                throw new Exception('Message not found.');
            }

            $trashFolder = $this->detectTrashFolder($this->listFolders());
            if ($trashFolder && $trashFolder !== $folder) {
                if (!@imap_mail_move($connection, (string)$msgNo, $trashFolder)) {
                    throw new Exception('Unable to move message to Trash: ' . (imap_last_error() ?: 'unknown error'));
                }
            } else {
                imap_delete($connection, (string)$msgNo);
            }

            imap_expunge($connection);
            return true;
        } finally {
            imap_close($connection);
        }
    }

    private function decodePart($text, $part) {
        if ($part->encoding === 3) { // BASE64
            $text = base64_decode($text);
        } elseif ($part->encoding === 4) { // QUOTED-PRINTABLE
            $text = quoted_printable_decode($text);
        }
        return $text;
    }
}
