<?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\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

/**
 * Elastic Email v2 Web API transport - no official Symfony bridge exists.
 * Unlike the other providers here, this is a legacy form-urlencoded API
 * (not JSON) that always answers HTTP 200 and reports failure via a
 * "success" field in the body.
 */
class ElasticEmailApiTransport extends AbstractApiTransport
{
    private const ENDPOINT = 'https://api.elasticemail.com/v2/email/send';

    public function __construct(
        private readonly string $key,
        private readonly bool $isTransactional = true,
        ?\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 'elasticemail+api://api.elasticemail.com';
    }

    protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
    {
        $fields = $this->getPayload($email, $envelope);

        if (isset($fields['attachmentFiles'])) {
            $formData = new FormDataPart($fields);
            $response = $this->client->request('POST', self::ENDPOINT, [
                'headers' => $formData->getPreparedHeaders()->toArray(),
                'body'    => $formData->bodyToIterable(),
            ]);
        } else {
            $response = $this->client->request('POST', self::ENDPOINT, [
                'body' => $fields,
            ]);
        }

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

        if ($statusCode !== 200 || empty($result['success'])) {
            $message = $result['error'] ?? $response->getContent(false);

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

        $sentMessage->setMessageId(is_string($result['data'] ?? null) ? $result['data'] : '');

        return $response;
    }

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

        $fields = [
            'apikey'          => $this->key,
            'from'            => $envelope->getSender()->getAddress(),
            'fromName'        => $envelope->getSender()->getName() ?: '',
            'to'              => $stringify($this->getRecipients($email, $envelope)),
            'subject'         => $email->getSubject() ?? '',
            'isTransactional' => $this->isTransactional ? 'true' : 'false',
        ];

        if ($cc = $email->getCc()) {
            $fields['cc'] = $stringify($cc);
        }
        if ($bcc = $email->getBcc()) {
            $fields['bcc'] = $stringify($bcc);
        }
        if ($replyTo = $email->getReplyTo()) {
            $fields['replyTo'] = $stringify($replyTo);
        }
        if (null !== $text = $email->getTextBody()) {
            $fields['bodyText'] = $text;
        }
        if (null !== $html = $email->getHtmlBody()) {
            $fields['bodyHtml'] = $html;
        }

        if ($attachments = $email->getAttachments()) {
            $fields['attachmentFiles'] = array_map(
                fn (DataPart $a) => new DataPart($a->getBody(), $a->getFilename() ?? 'attachment', $a->getContentType()),
                $attachments
            );
        }

        return $fields;
    }
}
