<?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\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

/**
 * SparkPost Transmissions API transport (no official Symfony bridge exists -
 * Symfony dropped it after SparkPost's transactional business was sold).
 * Modeled on Symfony's own SendgridApiTransport for consistency.
 */
class SparkPostApiTransport extends AbstractApiTransport
{
    private const HOST = 'api.sparkpost.com';

    public function __construct(
        private readonly string $key,
        private readonly string $region = 'us',
        ?\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('sparkpost+api://%s', $this->getEndpoint());
    }

    protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
    {
        $response = $this->client->request('POST', 'https://' . $this->getEndpoint() . '/api/v1/transmissions', [
            'headers' => [
                'Authorization' => $this->key,
            ],
            'json' => $this->getPayload($email, $envelope),
        ]);

        try {
            $statusCode = $response->getStatusCode();
            $result     = $response->toArray(false);
        } catch (TransportExceptionInterface $e) {
            throw new HttpTransportException('Could not reach the remote SparkPost server.', $response, 0, $e);
        }

        if (200 !== $statusCode) {
            $errors  = $result['errors'] ?? [];
            $message = implode('; ', array_column($errors, 'message')) ?: $response->getContent(false);

            throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $message, $statusCode), $response);
        }

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

        return $response;
    }

    private function getPayload(Email $email, Envelope $envelope): array
    {
        $address = fn (Address $a) => array_filter([
            'email' => $a->getAddress(),
            'name'  => $a->getName() ?: null,
        ]);

        $headerTo = implode(', ', array_map(
            fn (Address $a) => $a->getName() ? sprintf('%s <%s>', $a->getName(), $a->getAddress()) : $a->getAddress(),
            $email->getTo()
        ));

        $recipients = [];
        foreach ($email->getTo() as $to) {
            $recipients[] = ['address' => $address($to) + ['header_to' => $headerTo]];
        }
        foreach ($email->getCc() as $cc) {
            $recipients[] = ['address' => $address($cc) + ['header_to' => $headerTo]];
        }
        foreach ($email->getBcc() as $bcc) {
            $recipients[] = ['address' => $address($bcc)];
        }

        $content = [
            'from'    => $address($envelope->getSender()),
            'subject' => $email->getSubject() ?? '',
        ];

        if (null !== $text = $email->getTextBody()) {
            $content['text'] = $text;
        }
        if (null !== $html = $email->getHtmlBody()) {
            $content['html'] = $html;
        }

        if ($replyTo = $email->getReplyTo()) {
            $content['reply_to'] = implode(', ', array_map(
                fn (Address $a) => $a->getName() ? sprintf('%s <%s>', $a->getName(), $a->getAddress()) : $a->getAddress(),
                $replyTo
            ));
        }

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

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

        return [
            'recipients' => $recipients,
            'content'    => $content,
        ];
    }

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

            $attachments[] = [
                'name' => $filename,
                'type' => $headers->get('Content-Type')->getBody(),
                'data' => base64_encode($attachment->bodyToString()),
            ];
        }

        return $attachments;
    }

    private function getEndpoint(): string
    {
        return $this->region === 'eu' ? 'api.eu.sparkpost.com' : self::HOST;
    }
}
