<?php

namespace App\Mail\Transport;

use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\HttpTransportException;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

/**
 * Postmark Email API transport, hand-rolled rather than depending on
 * symfony/postmark-mailer so that TrackOpens / TrackLinks / MessageStream
 * (all exposed in our UI) can be sent - the official bridge does not
 * support the tracking flags.
 */
class PostmarkApiTransport extends AbstractApiTransport
{
    private const HOST = 'api.postmarkapp.com';

    public function __construct(
        private readonly string $key,
        private readonly bool $trackOpens = false,
        private readonly string $trackLinks = 'None',
        private readonly string $messageStream = 'outbound',
        ?\Symfony\Contracts\HttpClient\HttpClientInterface $client = null,
        ?\Psr\EventDispatcher\EventDispatcherInterface $dispatcher = null,
        ?\Psr\Log\LoggerInterface $logger = null,
    ) {
        parent::__construct($client, $dispatcher, $logger);
    }

    public function __toString(): string
    {
        return sprintf('postmark+api://%s', self::HOST);
    }

    protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
    {
        $response = $this->client->request('POST', 'https://' . self::HOST . '/email', [
            'headers' => [
                'Accept'                  => 'application/json',
                'X-Postmark-Server-Token' => $this->key,
            ],
            'json' => $this->getPayload($email, $envelope),
        ]);

        try {
            $statusCode = $response->getStatusCode();
            $result     = $response->toArray(false);
        } catch (DecodingExceptionInterface) {
            throw new HttpTransportException('Unable to send an email: ' . $response->getContent(false) . sprintf(' (code %d).', $response->getStatusCode()), $response);
        } catch (TransportExceptionInterface $e) {
            throw new HttpTransportException('Could not reach the remote Postmark server.', $response, 0, $e);
        }

        if (200 !== $statusCode) {
            throw new HttpTransportException('Unable to send an email: ' . ($result['Message'] ?? '') . sprintf(' (code %d).', $result['ErrorCode'] ?? $statusCode), $response);
        }

        $sentMessage->setMessageId($result['MessageID'] ?? '');

        return $response;
    }

    private function getPayload(Email $email, Envelope $envelope): array
    {
        $stringify = fn (array $addresses) => implode(',', array_map(
            fn (Address $a) => $a->toString(),
            $addresses
        ));

        $payload = [
            'From'        => $envelope->getSender()->toString(),
            'To'          => $stringify($this->getRecipients($email, $envelope)),
            'Subject'     => $email->getSubject() ?? '',
            'TextBody'    => $email->getTextBody(),
            'HtmlBody'    => $email->getHtmlBody(),
            'TrackOpens'  => $this->trackOpens,
            'TrackLinks'  => $this->trackLinks,
        ];

        if ($cc = $email->getCc()) {
            $payload['Cc'] = $stringify($cc);
        }
        if ($bcc = $email->getBcc()) {
            $payload['Bcc'] = $stringify($bcc);
        }
        if ($replyTo = $email->getReplyTo()) {
            $payload['ReplyTo'] = $stringify($replyTo);
        }
        if ($this->messageStream !== '') {
            $payload['MessageStream'] = $this->messageStream;
        }

        $skipHeaders = ['from', 'to', 'cc', 'bcc', 'subject', 'content-type', 'sender', 'reply-to', 'date'];
        $headers     = [];
        foreach ($email->getHeaders()->all() as $name => $header) {
            if (in_array($name, $skipHeaders, true)) {
                continue;
            }
            $headers[] = ['Name' => $header->getName(), 'Value' => $header->getBodyAsString()];
        }
        if ($headers) {
            $payload['Headers'] = $headers;
        }

        if ($email->getAttachments()) {
            $payload['Attachments'] = $this->getAttachments($email);
        }

        return $payload;
    }

    private function getAttachments(Email $email): array
    {
        $attachments = [];
        foreach ($email->getAttachments() as $attachment) {
            $headers     = $attachment->getPreparedHeaders();
            $filename    = $headers->getHeaderParameter('Content-Disposition', 'filename');
            $disposition = $headers->getHeaderBody('Content-Disposition');

            $att = [
                'Name'        => $filename,
                'Content'     => $attachment->bodyToString(),
                'ContentType' => $headers->get('Content-Type')->getBody(),
            ];

            if ('inline' === $disposition) {
                $att['ContentID'] = 'cid:' . ($attachment->hasContentId() ? $attachment->getContentId() : $filename);
            }

            $attachments[] = $att;
        }

        return $attachments;
    }
}
