<?php

/**
 * SMTP Relay — credentials stored in the DB, sending via PHP's built-in mail
 * wrappers (SMTP transport layer is wired later).
 *
 * Responsibilities:
 *   1. CRUD on the singleton smtp_settings row.
 *   2. Build MIME messages.
 *   3. Send via `mail()` now; swap transport later (e.g. PHPMailer / Symfony Mailer).
 */
class SmtpRelay {

    // -----------------------------------------------------------------------
    // DB operations
    // -----------------------------------------------------------------------

    /**
     * Fetch current SMTP settings from the DB.
     *
     * @return array{host:string,port:int,encryption:string,username:string,password:string,from_email:string,from_name:string}
     */
    public static function getSettings(Database $db): array {
        $stmt = $db->getPDO()->query(
            "SELECT host, port, encryption, username, password, from_email, from_name
             FROM v_smtp_active LIMIT 1"
        );
        $row = $stmt->fetch();

        return $row ?: [
            'host'       => '',
            'port'       => 587,
            'encryption' => 'tls',
            'username'   => '',
            'password'   => '',
            'from_email' => '',
            'from_name'  => 'Post-ERG',
        ];
    }

    /**
     * Upsert SMTP settings.
     *
     * @param array $data  Associative array with keys: host, port, encryption,
     *                     username, password, from_email, from_name.
     *                     Keys not present are left unchanged.
     */
    public static function updateSettings(Database $db, array $data): void {
        // Read existing so we can merge partial updates
        $current = self::getSettings($db);
        $merged  = array_merge($current, $data);

        // Sanitize
        $port = max(1, min(65535, (int)$merged['port']));
        $encryption = in_array($merged['encryption'], ['tls', 'ssl', 'none'], true)
            ? $merged['encryption'] : 'tls';

        $stmt = $db->getPDO()->prepare(
            "UPDATE smtp_settings
             SET host       = :host,
                 port       = :port,
                 encryption = :encryption,
                 username   = :username,
                 password   = :password,
                 from_email = :from_email,
                 from_name  = :from_name,
                 updated_at = CURRENT_TIMESTAMP
             WHERE id = 1"
        );

        $stmt->execute([
            ':host'       => trim($merged['host']),
            ':port'       => $port,
            ':encryption' => $encryption,
            ':username'   => trim($merged['username']),
            ':password'   => $merged['password'],           // keep as-is
            ':from_email' => trim($merged['from_email']),
            ':from_name'  => trim($merged['from_name']),
        ]);
    }

    /**
     * Check whether the SMTP relay is fully configured.
     */
    public static function isConfigured(Database $db): bool {
        $s = self::getSettings($db);
        return $s['host'] !== '' && $s['username'] !== '' && $s['from_email'] !== '';
    }

    // -----------------------------------------------------------------------
    // Send helpers (transport wired later — stub implementation now)
    // -----------------------------------------------------------------------

    /**
     * Send an e-mail using the stored SMTP credentials.
     *
     * Uses a native PHP socket SMTP client — no external dependencies.
     * Supports STARTTLS (port 587 / encryption=tls) and direct SSL (port 465 /
     * encryption=ssl).  Falls back to plain if encryption=none.
     *
     * @param string $to      Recipient e-mail address
     * @param string $subject Subject line
     * @param string $body    HTML body
     * @param string $plain   Plain-text alternative (optional)
     * @return bool True on successful delivery acceptance; false on failure
     */
    public static function send(
        Database $db,
        string   $to,
        string   $subject,
        string   $body,
        string   $plain = ''
    ): bool {
        $s = self::getSettings($db);
        if ($s['from_email'] === '') {
            error_log('[SmtpRelay] send() aborted — no from_email configured');
            return false;
        }

        // Build MIME message
        $boundary = 'posterg_' . bin2hex(random_bytes(8));
        $date     = date('r');
        $fromHdr  = $s['from_name'] !== ''
            ? "=?UTF-8?B?" . base64_encode($s['from_name']) . "?= <{$s['from_email']}>"
            : $s['from_email'];
        $subjectHdr = '=?UTF-8?B?' . base64_encode($subject) . '?=';

        $hasPlain = ($plain !== '');
        if ($hasPlain) {
            $msgBody  = "--{$boundary}\r\n";
            $msgBody .= "Content-Type: text/plain; charset=UTF-8\r\n";
            $msgBody .= "Content-Transfer-Encoding: base64\r\n\r\n";
            $msgBody .= chunk_split(base64_encode($plain)) . "\r\n";
            $msgBody .= "--{$boundary}\r\n";
            $msgBody .= "Content-Type: text/html; charset=UTF-8\r\n";
            $msgBody .= "Content-Transfer-Encoding: base64\r\n\r\n";
            $msgBody .= chunk_split(base64_encode($body)) . "\r\n";
            $msgBody .= "--{$boundary}--";
            $ctHdr = "multipart/alternative; boundary=\"{$boundary}\"";
        } else {
            $msgBody = chunk_split(base64_encode($body));
            $ctHdr   = 'text/html; charset=UTF-8';
        }

        $rawMessage =
            "Date: {$date}\r\n" .
            "From: {$fromHdr}\r\n" .
            "To: {$to}\r\n" .
            "Subject: {$subjectHdr}\r\n" .
            "MIME-Version: 1.0\r\n" .
            "Content-Type: {$ctHdr}\r\n" .
            (!$hasPlain ? "Content-Transfer-Encoding: base64\r\n" : '') .
            "\r\n" .
            $msgBody;

        try {
            return self::smtpSend($s, $to, $rawMessage);
        } catch (\Throwable $e) {
            error_log('[SmtpRelay] ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Low-level native SMTP socket client.
     *
     * @param array  $s          SMTP settings row
     * @param string $to         Envelope recipient
     * @param string $rawMessage Full RFC 2822 message (headers + body)
     */
    private static function smtpSend(array $s, string $to, string $rawMessage): bool
    {
        $host       = $s['host'];
        $port       = (int) $s['port'];
        $encryption = $s['encryption']; // 'tls' | 'ssl' | 'none'
        $timeout    = 15;

        // For direct SSL (port 465) open with ssl:// wrapper
        $connectHost = ($encryption === 'ssl') ? "ssl://{$host}" : $host;

        $errno = 0; $errstr = '';
        $sock = @stream_socket_client(
            "{$connectHost}:{$port}", $errno, $errstr, $timeout
        );
        if ($sock === false) {
            throw new \RuntimeException("SMTP connect failed ({$connectHost}:{$port}): {$errstr} [{$errno}]");
        }
        stream_set_timeout($sock, $timeout);

        $read = function () use ($sock): string {
            $buf = '';
            while ($line = fgets($sock, 512)) {
                $buf .= $line;
                // 4th char is ' ' when it's the last line of a multi-line reply
                if (isset($line[3]) && $line[3] === ' ') break;
            }
            return $buf;
        };
        $send = function (string $cmd) use ($sock, $read): string {
            fwrite($sock, $cmd . "\r\n");
            return $read();
        };
        $expect = function (string $cmd, string $code) use ($send): string {
            $resp = $send($cmd);
            if (strncmp($resp, $code, strlen($code)) !== 0) {
                throw new \RuntimeException("SMTP unexpected response to '{$cmd}': {$resp}");
            }
            return $resp;
        };

        // Greeting
        $greeting = $read();
        if (strncmp($greeting, '220', 3) !== 0) {
            throw new \RuntimeException("SMTP bad greeting: {$greeting}");
        }

        $parseEhlo = function (string $resp): array {
            // Returns list of capability tokens, e.g. ['STARTTLS','AUTH PLAIN LOGIN',...]
            $caps = [];
            foreach (explode("\n", $resp) as $line) {
                $line = rtrim($line);
                if (strlen($line) > 4) {
                    $caps[] = strtoupper(substr($line, 4));
                }
            }
            return $caps;
        };

        // EHLO
        $ehloResp = $send('EHLO ' . gethostname());
        $caps = strncmp($ehloResp, '250', 3) === 0 ? $parseEhlo($ehloResp) : [];
        if (empty($caps)) {
            $send('HELO ' . gethostname());
        }

        // STARTTLS upgrade
        if ($encryption === 'tls') {
            $expect('STARTTLS', '220');
            if (!stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                throw new \RuntimeException('SMTP STARTTLS crypto negotiation failed');
            }
            // Re-EHLO after TLS — refresh capabilities
            $ehloResp = $send('EHLO ' . gethostname());
            $caps = $parseEhlo($ehloResp);
        }

        // AUTH — pick mechanism from server capabilities
        if ($s['username'] !== '') {
            // Find the AUTH line, e.g. "AUTH PLAIN LOGIN" or "AUTH=PLAIN LOGIN"
            $authLine = '';
            foreach ($caps as $cap) {
                if (strncmp($cap, 'AUTH', 4) === 0) { $authLine = $cap; break; }
            }
            $mechanisms = preg_split('/[\s=]+/', $authLine);
            // Prefer PLAIN, fall back to LOGIN
            if (in_array('PLAIN', $mechanisms, true)) {
                // AUTH PLAIN: single base64(\0user\0pass)
                $token = base64_encode("\0{$s['username']}\0{$s['password']}");
                $expect("AUTH PLAIN {$token}", '235');
            } else {
                // AUTH LOGIN: challenge/response
                $expect('AUTH LOGIN', '334');
                $expect(base64_encode($s['username']), '334');
                $expect(base64_encode($s['password']), '235');
            }
        }

        // Envelope
        $expect("MAIL FROM:<{$s['from_email']}>", '250');
        $expect("RCPT TO:<{$to}>", '250');
        $expect('DATA', '354');

        // Message — dot-stuff lines starting with '.'
        $stuffed = preg_replace('/^\./m', '..', $rawMessage);
        $resp = $send($stuffed . "\r\n.");
        if (strncmp($resp, '250', 3) !== 0) {
            throw new \RuntimeException("SMTP DATA rejected: {$resp}");
        }

        $send('QUIT');
        fclose($sock);
        return true;
    }

    /**
     * Queue (persist) an e-mail for deferred sending.
     *
     * Stub — will create a `mail_queue` table in a future migration.
     */
    public static function queue(
        Database $db,
        string   $to,
        string   $subject,
        string   $body,
        string   $plain = ''
    ): void {
        // TODO: INSERT INTO mail_queue …
        // Placeholder so callers exist now and wire up later.
    }

    // -----------------------------------------------------------------------
    // Internal
    // -----------------------------------------------------------------------

    /**
     * Strip HTML tags to produce a rough plain-text fallback.
     */
    private static function htmlToPlain(string $html): string {
        $text = strip_tags($html);
        // Collapse multiple whitespace lines
        $text = preg_replace('/\n{3,}/', "\n\n", $text);
        return trim($text);
    }
}
