<?php
/**
 * SMTP Mailer Library for Web Hosting Panel
 * Lightweight SMTP mailer using PHP socket functions
 * Supports TLS (STARTTLS), SSL, and plain connections with AUTH LOGIN
 */

class SmtpMailer {

    private $host;
    private $port;
    private $encryption; // none, tls, ssl
    private $username;
    private $password;
    private $from_email;
    private $from_name;
    private $socket;
    private $last_response;
    private $debug = false;

    private static $settings_file = '/docker/whp/settings.json';
    private static $cipher = 'AES-256-CBC';

    /**
     * Constructor
     *
     * @param array $config SMTP configuration array
     */
    public function __construct($config) {
        $this->host = $config['host'] ?? '';
        $this->port = (int)($config['port'] ?? 587);
        $this->encryption = $config['encryption'] ?? 'tls';
        $this->username = $config['username'] ?? '';
        $this->password = $config['password'] ?? '';
        $this->from_email = $config['from_email'] ?? '';
        $this->from_name = $config['from_name'] ?? '';
    }

    /**
     * Get the encryption key derived from NONCE_SALT
     *
     * @return string 32-byte key for AES-256-CBC
     */
    private static function getEncryptionKey() {
        // Ensure NONCE_SALT is defined
        if (!defined('NONCE_SALT')) {
            require_once('/docker/whp/web/configs/config.php');
        }
        return hash('sha256', NONCE_SALT, true);
    }

    /**
     * Encrypt a password for storage in settings.json
     *
     * @param string $plaintext The password to encrypt
     * @return string Base64-encoded IV + ciphertext
     */
    public static function encryptPassword($plaintext) {
        $key = self::getEncryptionKey();
        $iv_length = openssl_cipher_iv_length(self::$cipher);
        $iv = openssl_random_pseudo_bytes($iv_length);

        $ciphertext = openssl_encrypt($plaintext, self::$cipher, $key, OPENSSL_RAW_DATA, $iv);
        if ($ciphertext === false) {
            error_log('SmtpMailer: Failed to encrypt password');
            return false;
        }

        // Prepend IV to ciphertext and base64 encode
        return base64_encode($iv . $ciphertext);
    }

    /**
     * Decrypt a password read from settings.json
     *
     * @param string $encrypted Base64-encoded IV + ciphertext
     * @return string|false The decrypted password, or false on failure
     */
    public static function decryptPassword($encrypted) {
        $key = self::getEncryptionKey();
        $iv_length = openssl_cipher_iv_length(self::$cipher);

        $data = base64_decode($encrypted);
        if ($data === false || strlen($data) < $iv_length) {
            error_log('SmtpMailer: Failed to decode encrypted password');
            return false;
        }

        $iv = substr($data, 0, $iv_length);
        $ciphertext = substr($data, $iv_length);

        $plaintext = openssl_decrypt($ciphertext, self::$cipher, $key, OPENSSL_RAW_DATA, $iv);
        if ($plaintext === false) {
            error_log('SmtpMailer: Failed to decrypt password');
            return false;
        }

        return $plaintext;
    }

    /**
     * Load SMTP configuration from settings.json
     *
     * @return array|null SMTP config array with decrypted password, or null if not configured/disabled
     */
    public static function loadConfig() {
        if (!file_exists(self::$settings_file)) {
            error_log('SmtpMailer: settings.json not found');
            return null;
        }

        $settings_content = file_get_contents(self::$settings_file);
        $settings = json_decode($settings_content, true);

        if (!$settings || !isset($settings['smtp'])) {
            error_log('SmtpMailer: No SMTP configuration found in settings.json');
            return null;
        }

        $smtp = $settings['smtp'];

        // Check if SMTP is enabled
        if (empty($smtp['enabled'])) {
            return null;
        }

        // Validate required fields - authenticated SMTP required for deliverability
        $required = array('host', 'port', 'username', 'password', 'from_email');
        foreach ($required as $field) {
            if (empty($smtp[$field])) {
                error_log('SmtpMailer: Missing required SMTP config field: ' . $field);
                return null;
            }
        }

        // Decrypt the password
        $decrypted_password = self::decryptPassword($smtp['password']);
        if ($decrypted_password === false) {
            error_log('SmtpMailer: Failed to decrypt SMTP password');
            return null;
        }

        return array(
            'host'       => $smtp['host'],
            'port'       => (int)$smtp['port'],
            'encryption' => $smtp['encryption'] ?? 'tls',
            'username'   => $smtp['username'],
            'password'   => $decrypted_password,
            'from_email' => $smtp['from_email'],
            'from_name'  => $smtp['from_name'] ?? '',
            'enabled'    => true
        );
    }

    /**
     * Send an email via SMTP
     *
     * @param string $to Recipient email address
     * @param string $subject Email subject
     * @param string $bodyHtml HTML body content
     * @param string $bodyText Plain text body content
     * @return array ['success' => bool, 'error' => string|null]
     */
    public function send($to, $subject, $bodyHtml, $bodyText = '') {
        try {
            // Validate inputs
            if (empty($to) || empty($subject)) {
                return array('success' => false, 'error' => 'Recipient and subject are required');
            }

            if (empty($this->host) || empty($this->from_email)) {
                return array('success' => false, 'error' => 'SMTP configuration is incomplete');
            }

            // Connect to SMTP server
            $connected = $this->connect();
            if (!$connected) {
                return array('success' => false, 'error' => 'Failed to connect to SMTP server: ' . $this->last_response);
            }

            // Read server greeting
            $greeting = $this->getResponse();
            if (!$this->isResponseOk($greeting, 220)) {
                $this->disconnect();
                return array('success' => false, 'error' => 'SMTP server rejected connection: ' . $greeting);
            }

            // Send EHLO
            $ehlo_response = $this->sendCommand('EHLO ' . gethostname());
            if (!$this->isResponseOk($ehlo_response, 250)) {
                $this->disconnect();
                return array('success' => false, 'error' => 'EHLO failed: ' . $ehlo_response);
            }

            // Start TLS if needed
            if ($this->encryption === 'tls') {
                $starttls_response = $this->sendCommand('STARTTLS');
                if (!$this->isResponseOk($starttls_response, 220)) {
                    $this->disconnect();
                    return array('success' => false, 'error' => 'STARTTLS failed: ' . $starttls_response);
                }

                // Enable TLS on the socket
                $crypto_result = stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT);
                if (!$crypto_result) {
                    $this->disconnect();
                    return array('success' => false, 'error' => 'TLS negotiation failed');
                }

                // Re-send EHLO after STARTTLS
                $ehlo_response = $this->sendCommand('EHLO ' . gethostname());
                if (!$this->isResponseOk($ehlo_response, 250)) {
                    $this->disconnect();
                    return array('success' => false, 'error' => 'EHLO after STARTTLS failed: ' . $ehlo_response);
                }
            }

            // Authenticate
            if (!empty($this->username) && !empty($this->password)) {
                $auth_result = $this->authenticate();
                if (!$auth_result) {
                    $this->disconnect();
                    return array('success' => false, 'error' => 'SMTP authentication failed: ' . $this->last_response);
                }
            }

            // MAIL FROM
            $from_response = $this->sendCommand('MAIL FROM:<' . $this->from_email . '>');
            if (!$this->isResponseOk($from_response, 250)) {
                $this->disconnect();
                return array('success' => false, 'error' => 'MAIL FROM rejected: ' . $from_response);
            }

            // RCPT TO
            $rcpt_response = $this->sendCommand('RCPT TO:<' . $to . '>');
            if (!$this->isResponseOk($rcpt_response, 250)) {
                $this->disconnect();
                return array('success' => false, 'error' => 'RCPT TO rejected: ' . $rcpt_response);
            }

            // DATA
            $data_response = $this->sendCommand('DATA');
            if (!$this->isResponseOk($data_response, 354)) {
                $this->disconnect();
                return array('success' => false, 'error' => 'DATA command rejected: ' . $data_response);
            }

            // Build and send the message
            $message = $this->buildMessage($to, $subject, $bodyHtml, $bodyText);
            $msg_response = $this->sendCommand($message . "\r\n.");
            if (!$this->isResponseOk($msg_response, 250)) {
                $this->disconnect();
                return array('success' => false, 'error' => 'Message rejected: ' . $msg_response);
            }

            // QUIT
            $this->sendCommand('QUIT');
            $this->disconnect();

            return array('success' => true, 'error' => null);

        } catch (Exception $e) {
            error_log('SmtpMailer: Exception during send: ' . $e->getMessage());
            $this->disconnect();
            return array('success' => false, 'error' => 'Exception: ' . $e->getMessage());
        }
    }

    /**
     * Send a test email
     *
     * @param string $to Recipient email address for the test
     * @return array ['success' => bool, 'error' => string|null]
     */
    public static function sendTestEmail($to) {
        $config = self::loadConfig();
        if (!$config) {
            return array('success' => false, 'error' => 'SMTP is not configured or disabled');
        }

        $mailer = new self($config);

        $subject = 'WHP SMTP Test Email';
        $bodyHtml = '<html><body>'
            . '<h2 style="color: #333;">WHP SMTP Test</h2>'
            . '<p>This is a test email from your Web Hosting Panel.</p>'
            . '<p>If you are reading this, your SMTP configuration is working correctly.</p>'
            . '<hr style="border: 1px solid #eee;">'
            . '<p style="color: #999; font-size: 12px;">Sent from WHP at ' . htmlspecialchars(date('Y-m-d H:i:s T')) . '</p>'
            . '</body></html>';
        $bodyText = "WHP SMTP Test\n\nThis is a test email from your Web Hosting Panel.\nIf you are reading this, your SMTP configuration is working correctly.\n\nSent from WHP at " . date('Y-m-d H:i:s T');

        return $mailer->send($to, $subject, $bodyHtml, $bodyText);
    }

    /**
     * Connect to the SMTP server
     *
     * @return bool True on success
     */
    private function connect() {
        $timeout = 30;
        $errno = 0;
        $errstr = '';

        if ($this->encryption === 'ssl') {
            // Direct SSL connection (typically port 465)
            $context = stream_context_create(array(
                'ssl' => array(
                    'verify_peer' => false,
                    'verify_peer_name' => false,
                    'allow_self_signed' => true
                )
            ));
            $this->socket = stream_socket_client(
                'ssl://' . $this->host . ':' . $this->port,
                $errno,
                $errstr,
                $timeout,
                STREAM_CLIENT_CONNECT,
                $context
            );
        } else {
            // Plain or TLS (STARTTLS) connection
            $context = stream_context_create(array(
                'ssl' => array(
                    'verify_peer' => false,
                    'verify_peer_name' => false,
                    'allow_self_signed' => true
                )
            ));
            $this->socket = stream_socket_client(
                'tcp://' . $this->host . ':' . $this->port,
                $errno,
                $errstr,
                $timeout,
                STREAM_CLIENT_CONNECT,
                $context
            );
        }

        if (!$this->socket) {
            $this->last_response = "Connection failed: $errstr ($errno)";
            error_log('SmtpMailer: ' . $this->last_response);
            return false;
        }

        stream_set_timeout($this->socket, $timeout);
        return true;
    }

    /**
     * Disconnect from the SMTP server
     */
    private function disconnect() {
        if ($this->socket) {
            @fclose($this->socket);
            $this->socket = null;
        }
    }

    /**
     * Send a command to the SMTP server and return the response
     *
     * @param string $command The SMTP command to send
     * @return string The server response
     */
    private function sendCommand($command) {
        if (!$this->socket) {
            return '';
        }

        fwrite($this->socket, $command . "\r\n");
        $response = $this->getResponse();
        $this->last_response = $response;

        return $response;
    }

    /**
     * Read response from SMTP server
     *
     * @return string The full response string
     */
    private function getResponse() {
        if (!$this->socket) {
            return '';
        }

        $response = '';
        $end_time = time() + 30;

        while (time() < $end_time) {
            $line = @fgets($this->socket, 515);
            if ($line === false) {
                break;
            }
            $response .= $line;

            // Check if this is the last line of the response
            // SMTP multiline responses have a dash after the code (e.g., "250-")
            // The final line has a space (e.g., "250 ")
            if (isset($line[3]) && $line[3] === ' ') {
                break;
            }
            if (strlen($line) < 4) {
                break;
            }
        }

        $this->last_response = trim($response);
        return $this->last_response;
    }

    /**
     * Check if the response code matches the expected code
     *
     * @param string $response The server response
     * @param int $expected_code The expected status code
     * @return bool True if the response starts with the expected code
     */
    private function isResponseOk($response, $expected_code) {
        return (int)substr($response, 0, 3) === $expected_code;
    }

    /**
     * Authenticate with the SMTP server using AUTH LOGIN
     *
     * @return bool True on success
     */
    private function authenticate() {
        // Send AUTH LOGIN
        $auth_response = $this->sendCommand('AUTH LOGIN');
        if (!$this->isResponseOk($auth_response, 334)) {
            return false;
        }

        // Send base64-encoded username
        $user_response = $this->sendCommand(base64_encode($this->username));
        if (!$this->isResponseOk($user_response, 334)) {
            return false;
        }

        // Send base64-encoded password
        $pass_response = $this->sendCommand(base64_encode($this->password));
        if (!$this->isResponseOk($pass_response, 235)) {
            return false;
        }

        return true;
    }

    /**
     * Build a MIME multipart email message
     *
     * @param string $to Recipient email
     * @param string $subject Email subject
     * @param string $bodyHtml HTML content
     * @param string $bodyText Plain text content
     * @return string The formatted email message
     */
    private function buildMessage($to, $subject, $bodyHtml, $bodyText) {
        $boundary = 'WHP_' . md5(uniqid(time()));
        $message_id = '<' . uniqid('whp_', true) . '@' . gethostname() . '>';

        // Build from header
        $from_header = '';
        if (!empty($this->from_name)) {
            $from_header = '=?UTF-8?B?' . base64_encode($this->from_name) . '?= <' . $this->from_email . '>';
        } else {
            $from_header = $this->from_email;
        }

        // Encode subject for UTF-8
        $encoded_subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';

        // Headers
        $headers = array();
        $headers[] = 'Date: ' . date('r');
        $headers[] = 'From: ' . $from_header;
        $headers[] = 'To: ' . $to;
        $headers[] = 'Subject: ' . $encoded_subject;
        $headers[] = 'Message-ID: ' . $message_id;
        $headers[] = 'MIME-Version: 1.0';
        $headers[] = 'Content-Type: multipart/alternative; boundary="' . $boundary . '"';
        $headers[] = 'X-Mailer: WHP-SmtpMailer/1.0';

        // Build message body
        $body = '';
        $body .= '--' . $boundary . "\r\n";
        $body .= "Content-Type: text/plain; charset=UTF-8\r\n";
        $body .= "Content-Transfer-Encoding: base64\r\n";
        $body .= "\r\n";
        $body .= chunk_split(base64_encode($bodyText ?: strip_tags($bodyHtml))) . "\r\n";

        $body .= '--' . $boundary . "\r\n";
        $body .= "Content-Type: text/html; charset=UTF-8\r\n";
        $body .= "Content-Transfer-Encoding: base64\r\n";
        $body .= "\r\n";
        $body .= chunk_split(base64_encode($bodyHtml)) . "\r\n";

        $body .= '--' . $boundary . '--' . "\r\n";

        // Combine headers and body
        $message = implode("\r\n", $headers) . "\r\n\r\n" . $body;

        // Escape any lines starting with a dot (SMTP transparency)
        $message = str_replace("\r\n.", "\r\n..", $message);

        return $message;
    }
}
