<?php
/**
 * Resend Transport Mock for PHPMailer.
 *
 * @since 1.0.0
 *
 * @package NimblyMail\SMTP
 */

namespace NimblyMail\SMTP\Mailers;

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\OAuthTokenProvider;
use Exception;

defined( 'ABSPATH' ) || exit;

/**
 * Class ResendTransport
 *
 * Mocks the SMTP class to intercept PHPMailer's send pipeline and
 * dispatch the email via the Resend API instead.
 *
 * @since 1.0.0 Replaced bundled resend-php SDK with native wp_remote_post().
 */
class ResendTransport extends SMTP {

    /**
     * Resend API endpoint.
     *
     * @since 1.0.0
     *
     * @var string
     */
    const API_URL = 'https://api.resend.com/emails';

    /**
     * The PHPMailer instance.
     *
     * @since 1.0.0
     *
     * @var PHPMailer
     */
    private PHPMailer $phpmailer;

    /**
     * Resend API Key.
     *
     * @since 1.0.0
     *
     * @var string
     */
    private string $api_key;

    /**
     * Constructor.
     *
     * @since 1.0.0
     *
     * @param PHPMailer $phpmailer The current PHPMailer instance.
     * @param string    $api_key   Resend API key.
     */
    public function __construct( PHPMailer $phpmailer, string $api_key ) {
        $this->phpmailer = $phpmailer;
        $this->api_key   = $api_key;
    }

    /**
     * Mock connect.
     *
     * @since 1.0.0
     *
     * @param string $host    Host.
     * @param int    $port    Port.
     * @param int    $timeout Timeout.
     * @param array  $options Options.
     *
     * @return bool
     */
    public function connect( $host, $port = null, $timeout = 30, $options = array() ) {
        $this->edebug( 'ResendTransport: connect (API mode, no TCP connection)', self::DEBUG_CONNECTION );
        return true;
    }

    /**
     * Mock startTLS.
     *
     * @since 1.0.0
     *
     * @return bool
     */
    public function startTLS() {
        return true;
    }

    /**
     * Mock authenticate.
     *
     * @since 1.0.0
     *
     * @param string                  $username Username.
     * @param string                  $password Password.
     * @param string                  $authtype Auth type.
     * @param OAuthTokenProvider|null $OAuth    OAuth token provider.
     *
     * @return bool
     */
    public function authenticate( $username, $password, $authtype = null, $OAuth = null ) {
        return true;
    }

    /**
     * Mock hello.
     *
     * @since 1.0.0
     *
     * @param string $host Host.
     *
     * @return bool
     */
    public function hello( $host = '' ) {
        return true;
    }

    /**
     * Mock mail from.
     *
     * @since 1.0.0
     *
     * @param string $from From email.
     *
     * @return bool
     */
    public function mail( $from ) {
        $this->edebug( "ResendTransport: MAIL FROM: $from", self::DEBUG_LOWLEVEL );
        return true;
    }

    /**
     * Mock recipient.
     *
     * @since 1.0.0
     *
     * @param string $address Recipient email.
     * @param string $dsn     DSN string.
     *
     * @return bool
     */
    public function recipient( $address, $dsn = '' ) {
        $this->edebug( "ResendTransport: RCPT TO: $address", self::DEBUG_LOWLEVEL );
        return true;
    }

    /**
     * Intercept and send via Resend API.
     *
     * Builds a JSON payload from PHPMailer properties and POSTs it to the
     * Resend REST API using wp_remote_post().
     *
     * @since 1.0.0 Replaced bundled SDK call with wp_remote_post().
     *
     * @param string $msg_data Raw MIME data (ignored, we extract from $phpmailer).
     *
     * @return bool
     * @throws Exception When the HTTP transport itself fails or Resend returns an error code; caught internally and converted to a soft failure.
     */
    public function data( $msg_data ) {
        try {
            $this->edebug( 'ResendTransport: Sending via Resend API', self::DEBUG_SERVER );

            $payload = $this->build_payload();

            if ( $this->do_debug >= self::DEBUG_LOWLEVEL ) {
                $this->edebug(
                    'ResendTransport: Payload: ' . wp_json_encode(
                        array_diff_key(
                            $payload,
                            array(
                                'html' => 1,
                                'text' => 1,
                            )
                        )
                    ),
                    self::DEBUG_LOWLEVEL
                );
            }

            $response = wp_remote_post(
                self::API_URL,
                array(
                    'timeout' => 30,
                    'headers' => array(
                        'Authorization' => 'Bearer ' . $this->api_key,
                        'Content-Type'  => 'application/json',
                    ),
                    'body'    => wp_json_encode( $payload ),
                )
            );

            if ( is_wp_error( $response ) ) {
                throw new Exception(
                    sprintf(
                        /* translators: %s: underlying HTTP error message. */
                        esc_html__( 'Resend HTTP error: %s', 'nimblymail-smtp' ),
                        esc_html( $response->get_error_message() )
                    )
                );
            }

            $code = wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            $data = json_decode( $body, true );

            $this->edebug( 'ResendTransport: HTTP ' . $code . ' Response: ' . $body, self::DEBUG_SERVER );

            if ( $code >= 400 ) {
                throw new Exception( $this->parse_api_error( $data, $code, $response ) );
            }

            return true;

        } catch ( Exception $e ) {
            $this->edebug( 'ResendTransport: ERROR: ' . $e->getMessage(), self::DEBUG_SERVER );
            $this->error = array(
                'error'  => $e->getMessage(),
                'detail' => $e->getCode(),
            );
            return false;
        }
    }

    /**
     * Build the Resend API payload from PHPMailer properties.
     *
     * @since 1.0.0
     *
     * @return array The API request payload.
     */
    private function build_payload(): array {
        $payload = array(
            'from'    => $this->phpmailer->FromName
                ? sprintf( '%s <%s>', $this->phpmailer->FromName, $this->phpmailer->From )
                : $this->phpmailer->From,
            'subject' => $this->phpmailer->Subject,
            'to'      => $this->format_addresses( $this->phpmailer->getToAddresses() ),
        );

        $cc = $this->format_addresses( $this->phpmailer->getCcAddresses() );
        if ( ! empty( $cc ) ) {
            $payload['cc'] = $cc;
        }

        $bcc = $this->format_addresses( $this->phpmailer->getBccAddresses() );
        if ( ! empty( $bcc ) ) {
            $payload['bcc'] = $bcc;
        }

        $reply_to = $this->format_addresses( $this->phpmailer->getReplyToAddresses() );
        if ( ! empty( $reply_to ) ) {
            $payload['reply_to'] = $reply_to;
        }

        if ( 'text/html' === $this->phpmailer->ContentType ) {
            $payload['html'] = $this->phpmailer->Body;
            if ( ! empty( $this->phpmailer->AltBody ) ) {
                $payload['text'] = $this->phpmailer->AltBody;
            }
        } else {
            $payload['text'] = $this->phpmailer->Body;
        }

        $attachments = $this->phpmailer->getAttachments();
        if ( ! empty( $attachments ) ) {
            $payload['attachments'] = array();
            foreach ( $attachments as $attachment ) {
                // PHPMailer addAttachment() tuple positions, in order: path or
                // string content, filename, friendly name, encoding, MIME type,
                // is_string flag, disposition, content id.
                list( $path_or_string, $filename, $name, , $type, $is_string, $disposition, $cid ) = $attachment;

                if ( $is_string ) {
                    // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Resend requires base64-encoded attachment content.
                    $content = base64_encode( $path_or_string );
                } else {
                    // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading local attachment file from PHPMailer.
                    $raw = file_get_contents( $path_or_string );
                    // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Resend requires base64-encoded attachment content.
                    $content = false !== $raw ? base64_encode( $raw ) : '';
                }

                if ( ! $content ) {
                    continue;
                }

                $entry = array(
                    'filename' => ! empty( $name ) ? $name : $filename,
                    'content'  => $content,
                );

                if ( ! empty( $type ) ) {
                    $entry['content_type'] = $type;
                }

                if ( 'inline' === $disposition && ! empty( $cid ) ) {
                    $entry['content_id'] = $cid;
                }

                $payload['attachments'][] = $entry;
            }
        }

        $custom_headers = $this->phpmailer->getCustomHeaders();
        if ( ! empty( $custom_headers ) ) {
            $payload['headers'] = array();
            foreach ( $custom_headers as $header ) {
                list( $header_name, $header_value ) = $header;
                $payload['headers'][ $header_name ] = $header_value;
            }
        }

        return $payload;
    }

    /**
     * Format an array of PHPMailer address tuples to RFC 5321 strings.
     *
     * @since 1.0.0
     * @since 1.0.2 Skip tuples with an empty email so the Resend API is never sent a blank recipient.
     *
     * @param array $addresses Addresses in [email, name] form.
     *
     * @return array Formatted address strings.
     */
    private function format_addresses( array $addresses ): array {
        $formatted = array();
        foreach ( $addresses as $addr ) {
            if ( empty( $addr[0] ) ) {
                continue;
            }
            $formatted[] = ! empty( $addr[1] )
                ? sprintf( '%s <%s>', $addr[1], $addr[0] )
                : $addr[0];
        }
        return $formatted;
    }

    /**
     * Translate a Resend API error response into a human-readable message.
     *
     * Resend errors include: missing_required_field, invalid_api_key,
     * not_authorized, validation_error, daily_quota_exceeded, application_error.
     *
     * @since 1.0.0
     *
     * @param array|null $data     Parsed JSON body from Resend (may be null).
     * @param int        $code     HTTP status code.
     * @param array|null $response Raw wp_remote_post response (for headers).
     *
     * @return string Human-readable error message.
     */
    private function parse_api_error( $data, int $code, $response = null ): string {
        $message = is_array( $data ) && isset( $data['message'] )
            ? (string) $data['message']
            : __( 'Unknown error', 'nimblymail-smtp' );
        $name    = is_array( $data ) && isset( $data['name'] )
            ? (string) $data['name']
            : '';

        switch ( $code ) {
            case 400:
                return sprintf(
                    /* translators: %s: validation message returned by Resend. */
                    esc_html__( 'Resend rejected the request: %s', 'nimblymail-smtp' ),
                    $message
                );
            case 401:
            case 403:
                return __( 'Invalid Resend API key. Check the value entered in NimblyMail settings.', 'nimblymail-smtp' );
            case 422:
                return sprintf(
                    /* translators: %s: validation message returned by Resend. */
                    esc_html__( 'Resend rejected the request: %s', 'nimblymail-smtp' ),
                    $message
                );
            case 429:
                $retry_after = is_array( $response )
                    ? (int) wp_remote_retrieve_header( $response, 'retry-after' )
                    : 0;
                if ( $retry_after > 0 ) {
                    return sprintf(
                        /* translators: %d: number of seconds to wait before retrying. */
                        esc_html__( 'Resend rate limit reached. Try again in %d seconds.', 'nimblymail-smtp' ),
                        $retry_after
                    );
                }
                return __( 'Resend rate limit reached. Try again shortly.', 'nimblymail-smtp' );
            case 500:
            case 502:
            case 503:
            case 504:
                return __( 'Resend service is temporarily unavailable. The message will be retried.', 'nimblymail-smtp' );
        }

        if ( '' !== $name ) {
            return sprintf(
                /* translators: 1: Resend error name. 2: HTTP status code. */
                esc_html__( 'Resend API error: %1$s (HTTP %2$d)', 'nimblymail-smtp' ),
                $name,
                $code
            );
        }

        return sprintf(
            /* translators: %d: HTTP status code. */
            esc_html__( 'Resend API error: HTTP %d', 'nimblymail-smtp' ),
            $code
        );
    }

    /**
     * Mock quit.
     *
     * @since 1.0.0
     *
     * @param bool $close_on_error Close on error.
     *
     * @return bool
     */
    public function quit( $close_on_error = true ) {
        return true;
    }

    /**
     * Mock close.
     *
     * @since 1.0.0
     *
     * @return void
     */
    public function close() {
        // No-op.
    }
}
