<?php
namespace App\Core;

use App\Core\Database;

class SmtpMailer
{
    private array $settings = [];
    private string $lastError = '';
    private string $debugLog = '';

    public function __construct()
    {
        $this->settings = self::settings();
    }

    public static function settings(): array
    {
        try {
            $db = Database::getInstance();
            $rows = $db->query("
                SELECT setting_key, setting_value
                FROM system_mail_settings
            ")->fetchAll(\PDO::FETCH_KEY_PAIR);

            return is_array($rows) ? $rows : [];
        } catch (\Throwable $e) {
            return [];
        }
    }

    public function lastError(): string
    {
        return $this->lastError;
    }

    public function debugLog(): string
    {
        return $this->debugLog;
    }

    private function log(string $message): void
    {
        $this->debugLog .= '[' . date('Y-m-d H:i:s') . '] ' . $message . "\n";
    }

    private function read($socket): string
    {
        $data = '';
        while (($line = fgets($socket, 515)) !== false) {
            $data .= $line;
            if (strlen($line) >= 4 && $line[3] === ' ') {
                break;
            }
        }

        $this->log('S: ' . trim($data));
        return $data;
    }

    private function write($socket, string $command, bool $hide = false): string
    {
        $this->log('C: ' . ($hide ? '[hidden]' : trim($command)));
        fwrite($socket, $command . "\r\n");
        return $this->read($socket);
    }

    private function code(string $response): int
    {
        return (int)substr($response, 0, 3);
    }

    private function expect(string $response, array $allowed, string $step): bool
    {
        $code = $this->code($response);
        if (!in_array($code, $allowed, true)) {
            $this->lastError = $step . ' failed. SMTP response: ' . trim($response);
            return false;
        }
        return true;
    }

    private function encodeHeader(string $value): string
    {
        if (preg_match('/[^\x20-\x7E]/', $value)) {
            return '=?UTF-8?B?' . base64_encode($value) . '?=';
        }
        return $value;
    }

    private function normalizeBody(string $body): string
    {
        $body = str_replace(["\r\n", "\r"], "\n", $body);
        $body = str_replace("\n", "\r\n", $body);
        return $body;
    }

    public function send(string $to, string $subject, string $body, ?string $toName = null): array
    {
        $enabled = (string)($this->settings['smtp_enabled'] ?? '0') === '1';

        if (!$enabled) {
            return [
                'ok' => false,
                'error' => 'SMTP is disabled in system_mail_settings.',
                'debug' => '',
            ];
        }

        $host = trim((string)($this->settings['smtp_host'] ?? ''));
        $port = (int)($this->settings['smtp_port'] ?? 587);
        $security = strtolower(trim((string)($this->settings['smtp_security'] ?? 'tls')));
        $username = trim((string)($this->settings['smtp_username'] ?? ''));
        $password = (string)($this->settings['smtp_password'] ?? '');
        $fromEmail = trim((string)($this->settings['smtp_from_email'] ?? $username));
        $fromName = trim((string)($this->settings['smtp_from_name'] ?? 'EasyChair'));

        if ($host === '' || $port <= 0 || $username === '' || $password === '' || $fromEmail === '') {
            return [
                'ok' => false,
                'error' => 'SMTP settings are incomplete.',
                'debug' => '',
            ];
        }

        $this->lastError = '';
        $this->debugLog = '';

        $remote = ($security === 'ssl' ? 'ssl://' : '') . $host . ':' . $port;

        $errno = 0;
        $errstr = '';
        $socket = @stream_socket_client($remote, $errno, $errstr, 30, STREAM_CLIENT_CONNECT);

        if (!$socket) {
            return [
                'ok' => false,
                'error' => "SMTP connection failed: {$errstr} ({$errno})",
                'debug' => $this->debugLog,
            ];
        }

        stream_set_timeout($socket, 30);

        $response = $this->read($socket);
        if (!$this->expect($response, [220], 'Connect')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        $serverName = $_SERVER['SERVER_NAME'] ?? 'localhost';

        $response = $this->write($socket, "EHLO {$serverName}");
        if (!$this->expect($response, [250], 'EHLO')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        if ($security === 'tls' || $security === 'starttls') {
            $response = $this->write($socket, "STARTTLS");
            if (!$this->expect($response, [220], 'STARTTLS')) {
                fclose($socket);
                return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
            }

            $cryptoOk = @stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);

            if ($cryptoOk !== true) {
                fclose($socket);
                return [
                    'ok' => false,
                    'error' => 'Could not enable STARTTLS encryption.',
                    'debug' => $this->debugLog,
                ];
            }

            $response = $this->write($socket, "EHLO {$serverName}");
            if (!$this->expect($response, [250], 'EHLO after STARTTLS')) {
                fclose($socket);
                return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
            }
        }

        $response = $this->write($socket, "AUTH LOGIN");
        if (!$this->expect($response, [334], 'AUTH LOGIN')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        $response = $this->write($socket, base64_encode($username), true);
        if (!$this->expect($response, [334], 'SMTP username')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        $response = $this->write($socket, base64_encode($password), true);
        if (!$this->expect($response, [235], 'SMTP password')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        $response = $this->write($socket, "MAIL FROM:<{$fromEmail}>");
        if (!$this->expect($response, [250], 'MAIL FROM')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        $response = $this->write($socket, "RCPT TO:<{$to}>");
        if (!$this->expect($response, [250, 251], 'RCPT TO')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        $response = $this->write($socket, "DATA");
        if (!$this->expect($response, [354], 'DATA')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        $encodedSubject = $this->encodeHeader($subject);
        $encodedFromName = $this->encodeHeader($fromName);
        $encodedToName = $toName ? $this->encodeHeader($toName) : $to;

        $messageId = sprintf('<%s.%s@%s>', time(), bin2hex(random_bytes(8)), preg_replace('/^www\./', '', $serverName));

        $headers = [];
        $headers[] = "From: {$encodedFromName} <{$fromEmail}>";
        $headers[] = "To: {$encodedToName} <{$to}>";
        $headers[] = "Subject: {$encodedSubject}";
        $headers[] = "Date: " . date('r');
        $headers[] = "Message-ID: {$messageId}";
        $headers[]="MIME-Version: 1.0";
        $headers[]="Content-Type: text/html; charset=UTF-8";
        $headers[]="Content-Transfer-Encoding: quoted-printable";

        $html=
quoted_printable_encode(
$this->normalizeBody(
(string)$body
)
);

        $message=
implode(
"\r\n",
$headers
)
.
"\r\n\r\n"
.
$html;

        // Dot-stuffing per SMTP DATA rules.
        $message = preg_replace('/^\./m', '..', $message);

        fwrite($socket, $message . "\r\n.\r\n");
        $response = $this->read($socket);

        if (!$this->expect($response, [250], 'Send DATA')) {
            fclose($socket);
            return ['ok' => false, 'error' => $this->lastError, 'debug' => $this->debugLog];
        }

        $this->write($socket, "QUIT");
        fclose($socket);

        return [
            'ok' => true,
            'error' => null,
            'debug' => $this->debugLog,
        ];
    }
}
