<?php
namespace EasyChat\Library\Email;

 
final class SmtpTransport {
    public function test(array $config): bool {
        $host=(string)$config['smtp_host'];$port=(int)$config['smtp_port'];$ip=EmailEndpoint::resolveAddress($host,!empty($config['allow_private_server']));if($ip==='')throw new \RuntimeException('smtp_dns_failed');
        $context=stream_context_create(['ssl'=>['verify_peer'=>true,'verify_peer_name'=>true,'allow_self_signed'=>false,'peer_name'=>$host,'SNI_enabled'=>true]]);$socket=$this->connect($ip,$port,$context);
        try{$this->expect($socket,220);$this->write($socket,'EHLO easychat.local');$this->expect($socket,250);if($port===587){$this->write($socket,'STARTTLS');$this->expect($socket,220);if(!stream_socket_enable_crypto($socket,true,STREAM_CRYPTO_METHOD_TLS_CLIENT))throw new \RuntimeException('smtp_tls_failed');$this->write($socket,'EHLO easychat.local');$this->expect($socket,250);}if(!array_key_exists('smtp_auth',$config)||!empty($config['smtp_auth'])){$this->write($socket,'AUTH PLAIN '.base64_encode("\0".$config['username']."\0".$config['password']));$this->expect($socket,235);}$this->write($socket,'QUIT');return true;}finally{fclose($socket);}
    }
    public function send(array $config, string $to, string $text, string $messageId, array $options = []): array {
        try {
            $mail = new MailEnvelope(array(
                'to'=>$to,
                'from'=>(string)($config['sender_email'] ?? $config['email_address'] ?? ''),
                'from_name'=>(string)($config['display_name'] ?? ''),
                'reply_to'=>(string)($config['email_address'] ?? ''),
                'subject'=>(string)($options['subject'] ?? 'Ответ EasyChat'),
                'text'=>$text,
                'message_id'=>$messageId,
                'in_reply_to'=>(string)($options['in_reply_to'] ?? ''),
                'references'=>(array)($options['references'] ?? array()),
                'idempotency_key'=>(string)($options['idempotency_key'] ?? ''),
            ));
        } catch (\Throwable $e) {
            return array('success'=>false,'error'=>'email_recipient_invalid');
        }
        return $this->sendEnvelope($config, $mail);
    }

    public function sendEnvelope(array $config, MailEnvelope $mail): array {
        $host = (string)$config['smtp_host']; $port = (int)$config['smtp_port'];$ip=EmailEndpoint::resolveAddress($host,!empty($config['allow_private_server']));if($ip==='')return ['success'=>false,'error'=>'smtp_dns_failed','retryable'=>true];
        $context = stream_context_create(['ssl'=>['verify_peer'=>true,'verify_peer_name'=>true,'allow_self_signed'=>false,'peer_name'=>$host,'SNI_enabled'=>true]]);
        $socket = $this->connect($ip, $port, $context);
        $dataSubmitted=false;
        try {
            $this->expect($socket, 220);
            $this->write($socket, 'EHLO easychat.local'); $this->expect($socket, 250);
            if ($port === 587) {
                $this->write($socket, 'STARTTLS'); $this->expect($socket, 220);
                if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) throw new \RuntimeException('smtp_tls_failed');
                $this->write($socket, 'EHLO easychat.local'); $this->expect($socket, 250);
            }
            if (!array_key_exists('smtp_auth', $config) || !empty($config['smtp_auth'])) {
                $this->write($socket, 'AUTH PLAIN ' . base64_encode("\0".$config['username']."\0".$config['password'])); $this->expect($socket, 235);
            }
            $this->write($socket, 'MAIL FROM:<' . $mail->from() . '>'); $this->expect($socket, 250);
            $this->write($socket, 'RCPT TO:<' . $mail->to() . '>'); $this->expect($socket, [250,251]);
            $this->write($socket, 'DATA'); $this->expect($socket, 354);
            $body = str_replace("\n.", "\n..", str_replace(["\r\n","\r"], "\n", $mail->text()));
             
             
            fwrite($socket, implode("\r\n", $mail->headers())."\r\n\r\n".str_replace("\n","\r\n",$body)."\r\n.\r\n");
            $dataSubmitted=true;
            $this->expect($socket, 250); $this->write($socket, 'QUIT');
            return ['success'=>true,'external_message_id'=>$mail->messageId(),'delivery_state'=>'accepted_by_server','transport'=>'direct_smtp'];
        } catch (\Throwable $e) { return ['success'=>false,'error'=>$dataSubmitted?'smtp_delivery_outcome_unknown':'smtp_send_failed','outcome_unknown'=>$dataSubmitted,'retryable'=>!$dataSubmitted]; }
        finally { fclose($socket); }
    }
    private function connect(string $ip,int $port,$context) {
        $errno=0;$error='';$warning='';$target=($port===465?'tls://':'tcp://').EmailEndpoint::socketHost($ip).':'.$port;
        set_error_handler(function ($severity, $message) use (&$warning) { $warning = (string)$message; return true; });
        try { $s=stream_socket_client($target,$errno,$error,10,STREAM_CLIENT_CONNECT,$context); }
        finally { restore_error_handler(); }
        if(!$s){$details=strtolower($warning.' '.$error);$code=(strpos($details,'certificate')!==false||strpos($details,'peer')!==false)?'smtp_tls_identity_mismatch':'smtp_connect_failed';throw new \RuntimeException($code);}
        stream_set_timeout($s,10);return $s;
    }
    private function write($socket,string $line): void { fwrite($socket,$line."\r\n"); }
    private function expect($socket,$codes): void { $codes=(array)$codes;$last='';do{$line=fgets($socket,4096);if($line===false)throw new \RuntimeException('smtp_read_failed');$last=$line;}while(isset($line[3])&&$line[3]==='-');if(!in_array((int)substr($last,0,3),$codes,true))throw new \RuntimeException('smtp_rejected'); }
}
