<?php
// MJ Marketplace v2.9.16 - Multi SMTP email helper with failover/random support.
// No Composer dependency required. Uses configured SMTP accounts, then optional PHP mail fallback.

if (!function_exists('mj_email_ensure_schema')) {
function mj_email_ensure_schema(): void {
    if (!function_exists('q') || !mj_is_installed()) { return; }
    try {
        q("CREATE TABLE IF NOT EXISTS mj_smtp_accounts (
            id INT AUTO_INCREMENT PRIMARY KEY,
            account_name VARCHAR(160) NOT NULL,
            host VARCHAR(190) NOT NULL,
            port INT NOT NULL DEFAULT 587,
            encryption VARCHAR(20) NOT NULL DEFAULT 'tls',
            username VARCHAR(190) NULL,
            password TEXT NULL,
            from_email VARCHAR(190) NOT NULL,
            from_name VARCHAR(190) NULL,
            reply_to_email VARCHAR(190) NULL,
            is_active TINYINT(1) NOT NULL DEFAULT 1,
            priority_order INT NOT NULL DEFAULT 100,
            daily_limit INT NOT NULL DEFAULT 0,
            sent_today INT NOT NULL DEFAULT 0,
            last_sent_at DATETIME NULL,
            last_error TEXT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            INDEX idx_active_priority(is_active, priority_order),
            INDEX idx_from_email(from_email)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    } catch (Throwable $e) {}

    try {
        q("CREATE TABLE IF NOT EXISTS mj_email_send_log (
            id BIGINT AUTO_INCREMENT PRIMARY KEY,
            smtp_account_id INT NULL,
            to_email VARCHAR(190) NOT NULL,
            subject VARCHAR(255) NOT NULL,
            template_key VARCHAR(120) NULL,
            status VARCHAR(40) NOT NULL DEFAULT 'pending',
            error_message TEXT NULL,
            attempts INT NOT NULL DEFAULT 0,
            sent_at DATETIME NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_status_created(status, created_at),
            INDEX idx_to_email(to_email),
            INDEX idx_smtp_account(smtp_account_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    } catch (Throwable $e) {}

    try {
        q("CREATE TABLE IF NOT EXISTS mj_email_queue (
            id BIGINT AUTO_INCREMENT PRIMARY KEY,
            user_id BIGINT NULL,
            admin_id INT NULL,
            to_email VARCHAR(191) NOT NULL,
            to_name VARCHAR(191) NULL,
            subject VARCHAR(255) NOT NULL,
            body_html LONGTEXT NULL,
            body_text LONGTEXT NULL,
            template_key VARCHAR(100) NULL,
            status VARCHAR(40) NOT NULL DEFAULT 'pending',
            priority VARCHAR(40) NOT NULL DEFAULT 'normal',
            attempts INT NOT NULL DEFAULT 0,
            scheduled_at DATETIME NULL,
            sent_at DATETIME NULL,
            failed_at DATETIME NULL,
            error_message TEXT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            INDEX(user_id), INDEX(status), INDEX(template_key), INDEX(scheduled_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    } catch (Throwable $e) {}
}
}

if (!function_exists('mj_email_setting')) {
function mj_email_setting(string $key, string $default=''): string {
    try { return setting($key, $default); } catch (Throwable $e) { return $default; }
}
}

if (!function_exists('mj_smtp_active_accounts')) {
function mj_smtp_active_accounts(): array {
    mj_email_ensure_schema();
    try {
        if (!table_exists('mj_smtp_accounts')) { return []; }
        $accounts = all_rows("SELECT * FROM mj_smtp_accounts WHERE is_active=1 ORDER BY priority_order ASC, id ASC");
        $mode = strtolower(mj_email_setting('smtp_delivery_mode', 'failover'));
        if ($mode === 'random' && count($accounts) > 1) { shuffle($accounts); }
        return $accounts;
    } catch (Throwable $e) { return []; }
}
}

if (!function_exists('mj_smtp_read_reply')) {
function mj_smtp_read_reply($fp): string {
    $reply = '';
    while (!feof($fp)) {
        $line = fgets($fp, 515);
        if ($line === false) { break; }
        $reply .= $line;
        if (strlen($line) >= 4 && substr($line, 3, 1) === ' ') { break; }
    }
    return trim($reply);
}
}

if (!function_exists('mj_smtp_cmd')) {
function mj_smtp_cmd($fp, string $cmd, array $okCodes): string {
    fwrite($fp, $cmd . "\r\n");
    $reply = mj_smtp_read_reply($fp);
    $code = (int)substr($reply, 0, 3);
    if (!in_array($code, $okCodes, true)) {
        throw new RuntimeException('SMTP command failed ['.$cmd.']: '.$reply);
    }
    return $reply;
}
}

if (!function_exists('mj_email_addr')) {
function mj_email_addr(string $email, string $name=''): string {
    $email = trim($email);
    $name = trim(preg_replace('/[\r\n]+/', ' ', $name));
    if ($name === '') { return '<'.$email.'>'; }
    return '"'.addcslashes($name, '"\\').'" <'.$email.'>';
}
}

if (!function_exists('mj_email_subject')) {
function mj_email_subject(string $subject): string {
    $subject = trim(preg_replace('/[\r\n]+/', ' ', $subject));
    return '=?UTF-8?B?'.base64_encode($subject).'?=';
}
}

if (!function_exists('mj_email_build_message')) {
function mj_email_build_message(array $account, string $toEmail, string $subject, string $html, string $text='', array $opts=[]): string {
    $fromEmail = trim((string)($opts['from_email'] ?? $account['from_email'] ?? mj_email_setting('smtp_default_from_email', '')));
    $fromName  = trim((string)($opts['from_name'] ?? $account['from_name'] ?? mj_email_setting('smtp_default_from_name', setting('site_name','MJ Marketplace'))));
    $replyTo   = trim((string)($opts['reply_to_email'] ?? $account['reply_to_email'] ?? $fromEmail));
    $boundary = 'mj_' . bin2hex(random_bytes(12));
    if ($text === '') { $text = trim(strip_tags(str_replace(['<br>','<br/>','<br />'], "\n", $html))); }
    $headers = [];
    $headers[] = 'Date: ' . date('r');
    $headers[] = 'From: ' . mj_email_addr($fromEmail, $fromName);
    $headers[] = 'Reply-To: ' . mj_email_addr($replyTo ?: $fromEmail, $fromName);
    $headers[] = 'To: ' . mj_email_addr($toEmail, (string)($opts['to_name'] ?? ''));
    $headers[] = 'Subject: ' . mj_email_subject($subject);
    $headers[] = 'MIME-Version: 1.0';
    $headers[] = 'Content-Type: multipart/alternative; boundary="'.$boundary.'"';
    $headers[] = 'Message-ID: <'.bin2hex(random_bytes(10)).'@'.preg_replace('/[^a-z0-9.-]/i','', $_SERVER['HTTP_HOST'] ?? 'mjmarketplace.local').'>';
    $body = [];
    $body[] = '--'.$boundary;
    $body[] = 'Content-Type: text/plain; charset=UTF-8';
    $body[] = 'Content-Transfer-Encoding: quoted-printable';
    $body[] = '';
    $body[] = quoted_printable_encode($text);
    $body[] = '--'.$boundary;
    $body[] = 'Content-Type: text/html; charset=UTF-8';
    $body[] = 'Content-Transfer-Encoding: quoted-printable';
    $body[] = '';
    $body[] = quoted_printable_encode($html);
    $body[] = '--'.$boundary.'--';
    $body[] = '';
    return implode("\r\n", $headers) . "\r\n\r\n" . implode("\r\n", $body);
}
}

if (!function_exists('mj_smtp_send_with_account')) {
function mj_smtp_send_with_account(array $account, string $toEmail, string $subject, string $html, string $text='', array $opts=[]): void {
    $host = trim((string)($account['host'] ?? ''));
    $port = (int)($account['port'] ?? 587);
    $enc = strtolower(trim((string)($account['encryption'] ?? 'tls')));
    if ($host === '' || !filter_var($toEmail, FILTER_VALIDATE_EMAIL)) {
        throw new RuntimeException('Invalid SMTP host or recipient email.');
    }
    $remote = ($enc === 'ssl' ? 'ssl://' : '') . $host . ':' . $port;
    $errno = 0; $errstr = '';
    $timeout = (int)mj_email_setting('smtp_timeout_seconds', '15');
    $fp = @stream_socket_client($remote, $errno, $errstr, max(5, min(60, $timeout)), STREAM_CLIENT_CONNECT);
    if (!$fp) { throw new RuntimeException('SMTP connect failed: '.$errstr.' (#'.$errno.')'); }
    stream_set_timeout($fp, max(5, min(60, $timeout)));
    try {
        $greeting = mj_smtp_read_reply($fp);
        if ((int)substr($greeting,0,3) !== 220) { throw new RuntimeException('SMTP greeting failed: '.$greeting); }
        $helo = preg_replace('/[^a-zA-Z0-9.-]/','', $_SERVER['HTTP_HOST'] ?? 'localhost') ?: 'localhost';
        mj_smtp_cmd($fp, 'EHLO '.$helo, [250]);
        if ($enc === 'tls' || $enc === 'starttls') {
            mj_smtp_cmd($fp, 'STARTTLS', [220]);
            $cryptoOk = @stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
            if (!$cryptoOk) { throw new RuntimeException('STARTTLS crypto negotiation failed.'); }
            mj_smtp_cmd($fp, 'EHLO '.$helo, [250]);
        }
        $username = (string)($account['username'] ?? '');
        $password = (string)($account['password'] ?? '');
        if ($username !== '') {
            mj_smtp_cmd($fp, 'AUTH LOGIN', [334]);
            mj_smtp_cmd($fp, base64_encode($username), [334]);
            mj_smtp_cmd($fp, base64_encode($password), [235]);
        }
        $fromEmail = trim((string)($opts['from_email'] ?? $account['from_email'] ?? ''));
        if (!filter_var($fromEmail, FILTER_VALIDATE_EMAIL)) { throw new RuntimeException('SMTP from email is invalid.'); }
        mj_smtp_cmd($fp, 'MAIL FROM:<'.$fromEmail.'>', [250]);
        mj_smtp_cmd($fp, 'RCPT TO:<'.$toEmail.'>', [250,251]);
        mj_smtp_cmd($fp, 'DATA', [354]);
        $message = mj_email_build_message($account, $toEmail, $subject, $html, $text, $opts);
        $message = preg_replace('/^\./m', '..', $message);
        fwrite($fp, $message . "\r\n.\r\n");
        $reply = mj_smtp_read_reply($fp);
        if (!in_array((int)substr($reply,0,3), [250], true)) { throw new RuntimeException('SMTP DATA failed: '.$reply); }
        @mj_smtp_cmd($fp, 'QUIT', [221]);
    } finally { @fclose($fp); }
}
}

if (!function_exists('mj_email_log')) {
function mj_email_log(?int $smtpId, string $toEmail, string $subject, string $status, string $error='', string $templateKey=''): void {
    try {
        mj_email_ensure_schema();
        if (table_exists('mj_email_send_log')) {
            q('INSERT INTO mj_email_send_log(smtp_account_id,to_email,subject,template_key,status,error_message,attempts,sent_at,created_at) VALUES(?,?,?,?,?,?,1,IF(?="sent",NOW(),NULL),NOW())', [$smtpId,$toEmail,$subject,$templateKey,$status,$error,$status]);
        }
    } catch (Throwable $e) {}
}
}

if (!function_exists('mj_send_email')) {
function mj_send_email(string $toEmail, string $subject, string $html, string $text='', array $opts=[]): array {
    mj_email_ensure_schema();
    $toEmail = trim($toEmail);
    if (!filter_var($toEmail, FILTER_VALIDATE_EMAIL)) {
        return ['success'=>false,'error'=>'Invalid recipient email.'];
    }
    if (mj_email_setting('email_queue_enabled','1') === '0' && empty($opts['force_send'])) {
        // Queue disabled means still allow direct send for alerts/test. This setting only controls automatic queuing.
    }
    $accounts = mj_smtp_active_accounts();
    $lastError = '';
    foreach ($accounts as $acc) {
        $smtpId = (int)($acc['id'] ?? 0);
        try {
            if ((int)($acc['daily_limit'] ?? 0) > 0 && (int)($acc['sent_today'] ?? 0) >= (int)$acc['daily_limit']) { continue; }
            mj_smtp_send_with_account($acc, $toEmail, $subject, $html, $text, $opts);
            try { q('UPDATE mj_smtp_accounts SET sent_today=sent_today+1,last_sent_at=NOW(),last_error=NULL WHERE id=?', [$smtpId]); } catch(Throwable $e) {}
            mj_email_log($smtpId,$toEmail,$subject,'sent','',(string)($opts['template_key'] ?? ''));
            return ['success'=>true,'smtp_account_id'=>$smtpId,'method'=>'smtp'];
        } catch (Throwable $e) {
            $lastError = $e->getMessage();
            try { q('UPDATE mj_smtp_accounts SET last_error=? WHERE id=?', [$lastError, $smtpId]); } catch(Throwable $ignore) {}
            mj_email_log($smtpId,$toEmail,$subject,'failed',$lastError,(string)($opts['template_key'] ?? ''));
            continue;
        }
    }
    if (mj_email_setting('smtp_allow_mail_fallback','1') === '1' && function_exists('mail')) {
        $from = trim((string)($opts['from_email'] ?? mj_email_setting('smtp_default_from_email','')));
        if ($from === '') { $from = 'no-reply@'.preg_replace('/[^a-z0-9.-]/i','', $_SERVER['HTTP_HOST'] ?? 'localhost'); }
        $headers = "MIME-Version: 1.0\r\nContent-type: text/html; charset=UTF-8\r\nFrom: ".mj_email_addr($from, (string)($opts['from_name'] ?? setting('site_name','MJ Marketplace')))."\r\n";
        $ok = @mail($toEmail, $subject, $html, $headers);
        if ($ok) { mj_email_log(null,$toEmail,$subject,'sent','','mail_fallback'); return ['success'=>true,'method'=>'mail']; }
        $lastError = $lastError ?: 'PHP mail fallback failed.';
    }
    return ['success'=>false,'error'=>$lastError ?: 'No active SMTP account available.'];
}
}

if (!function_exists('mj_queue_email')) {
function mj_queue_email(string $toEmail, string $subject, string $html, string $text='', string $templateKey='', ?int $userId=null, ?int $adminId=null): bool {
    mj_email_ensure_schema();
    try {
        q('INSERT INTO mj_email_queue(user_id,admin_id,to_email,subject,body_html,body_text,template_key,status,scheduled_at,created_at) VALUES(?,?,?,?,?,?,?,"pending",NOW(),NOW())', [$userId,$adminId,$toEmail,$subject,$html,$text,$templateKey]);
        return true;
    } catch (Throwable $e) { return false; }
}
}

if (!function_exists('mj_process_email_queue')) {
function mj_process_email_queue(int $limit=25): array {
    mj_email_ensure_schema();
    $sent=0; $failed=0; $errors=[];
    try {
        $rows = all_rows('SELECT * FROM mj_email_queue WHERE status IN ("pending","retry") AND (scheduled_at IS NULL OR scheduled_at<=NOW()) ORDER BY FIELD(priority,"urgent","high","normal","low"), id ASC LIMIT '.max(1,min(200,$limit)));
        foreach ($rows as $r) {
            q('UPDATE mj_email_queue SET status="sending", attempts=attempts+1 WHERE id=?', [(int)$r['id']]);
            $res = mj_send_email((string)$r['to_email'], (string)$r['subject'], (string)($r['body_html'] ?? ''), (string)($r['body_text'] ?? ''), ['template_key'=>$r['template_key'] ?? '', 'force_send'=>true]);
            if (!empty($res['success'])) { $sent++; q('UPDATE mj_email_queue SET status="sent", sent_at=NOW(), error_message=NULL WHERE id=?', [(int)$r['id']]); }
            else { $failed++; $err=(string)($res['error'] ?? 'Unknown email error'); $errors[]=$err; q('UPDATE mj_email_queue SET status=IF(attempts>=3,"failed","retry"), failed_at=NOW(), error_message=? WHERE id=?', [$err,(int)$r['id']]); }
        }
    } catch (Throwable $e) { $errors[] = $e->getMessage(); }
    return ['sent'=>$sent,'failed'=>$failed,'errors'=>$errors];
}
}
