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

/**
 * Gmail / Google Workspace transport via the Gmail API's `messages.send`,
 * authenticated with an OAuth2 refresh token (no official Symfony bridge
 * exists). Google issues a fresh access token from the refresh token on
 * every send - refresh calls are cheap and this avoids the complexity of
 * caching a short-lived access token across requests.
 */
class GoogleApiTransport extends AbstractApiTransport
{
    private const TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token';
    private const SEND_ENDPOINT  = 'https://gmail.googleapis.com/gmail/v1/users/me/messages/send';

    public function __construct(
        private readonly string $clientId,
        private readonly string $clientSecret,
        private readonly ?string $refreshToken,
        ?\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 'gmail+api://gmail.googleapis.com';
    }

    protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
    {
        if (!$this->refreshToken) {
            throw new HttpTransportException('This Gmail connection has not been authenticated with Google yet - click "Authenticate with Google" first.');
        }

        $accessToken = $this->fetchAccessToken();

        $raw = rtrim(strtr(base64_encode($this->buildRawMessage($email)), '+/', '-_'), '=');

        $response = $this->client->request('POST', self::SEND_ENDPOINT, [
            'auth_bearer' => $accessToken,
            'json'        => ['raw' => $raw],
        ]);

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

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

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

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

        return $response;
    }

    /**
     * Symfony's Email::getPreparedHeaders() strips Bcc (correct for real SMTP,
     * where bcc recipients are only carried in the protocol envelope). The
     * Gmail API has no separate envelope - recipients are read straight out
     * of the raw message - so Bcc must be re-added or those recipients would
     * silently never receive the mail.
     */
    private function buildRawMessage(Email $email): string
    {
        $headers = $email->getPreparedHeaders();

        if (($bcc = $email->getBcc()) && !$headers->has('Bcc')) {
            $headers->addMailboxListHeader('Bcc', $bcc);
        }

        return $headers->toString() . $email->getBody()->toString();
    }

    private function fetchAccessToken(): string
    {
        $response = $this->client->request('POST', self::TOKEN_ENDPOINT, [
            'body' => [
                'client_id'     => $this->clientId,
                'client_secret' => $this->clientSecret,
                'refresh_token' => $this->refreshToken,
                'grant_type'    => 'refresh_token',
            ],
        ]);

        try {
            $result = $response->toArray(false);
        } catch (TransportExceptionInterface $e) {
            throw new HttpTransportException('Could not reach Google\'s OAuth token endpoint.', $response, 0, $e);
        }

        if (empty($result['access_token'])) {
            $message = $result['error_description'] ?? $result['error'] ?? 'unknown error';

            throw new HttpTransportException("Unable to refresh the Google access token: {$message}. Try re-authenticating with Google.", $response);
        }

        return $result['access_token'];
    }
}
