<?php
/**
 * SendLayer 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 SendLayerTransport
 *
 * Mocks the SMTP class to intercept PHPMailer's send pipeline and
 * dispatch the email via the SendLayer API instead.
 *
 * @since 1.0.0
 */
class SendLayerTransport extends SMTP {

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

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

    /**
     * SendLayer 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   SendLayer 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( 'SendLayerTransport: 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( "SendLayerTransport: 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( "SendLayerTransport: RCPT TO: $address", self::DEBUG_LOWLEVEL );
        return true;
    }

    /**
     * Intercept and send via SendLayer API.
     *
     * Builds a JSON payload from PHPMailer properties and POSTs it to the
     * SendLayer REST API using wp_remote_post().
     *
     * @since 1.0.0
     *
     * @param string $msg_data Raw MIME data (ignored).
     *
     * @return bool
     * @throws Exception When HTTP transport fails or SendLayer returns an error.
     */
    public function data( $msg_data ) {
        try {
            $this->edebug( 'SendLayerTransport: Sending via SendLayer API', self::DEBUG_SERVER );

            $payload = $this->build_payload();

            if ( $this->do_debug >= self::DEBUG_LOWLEVEL ) {
                $this->edebug(
                    'SendLayerTransport: 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__( 'SendLayer 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( 'SendLayerTransport: HTTP ' . $code . ' Response: ' . $body, self::DEBUG_SERVER );

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

            return true;

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

    /**
     * Build the SendLayer API payload from PHPMailer properties.
     *
     * @since 1.0.0
     *
     * @return array The API request payload.
     */
    private function build_payload(): array {
        $payload = array(
            'From'    => array(
                'Email' => $this->phpmailer->From,
                'Name'  => $this->phpmailer->FromName,
            ),
            '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['ReplyTo'] = $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 ) {
                list( $path_or_string, $filename, $name, , $type, $is_string, $disposition, $cid ) = $attachment;

                if ( $is_string ) {
                    // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- SendLayer 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 -- SendLayer requires base64-encoded attachment content.
                    $content = false !== $raw ? base64_encode( $raw ) : '';
                }

                if ( ! $content ) {
                    continue;
                }

                $payload['Attachments'][] = array(
                    'Filename' => ! empty( $name ) ? $name : $filename,
                    'Content'  => $content,
                );
            }
        }

        return $payload;
    }

    /**
     * Format an array of PHPMailer address tuples to SendLayer objects.
     *
     * @since 1.0.0
     * @since 1.0.2 Skip tuples with an empty email so the SendLayer API is never sent a blank recipient.
     *
     * @param array $addresses Addresses in [email, name] form.
     *
     * @return array SendLayer address objects.
     */
    private function format_addresses( array $addresses ): array {
        $formatted = array();
        foreach ( $addresses as $addr ) {
            if ( empty( $addr[0] ) ) {
                continue;
            }
            $formatted[] = array(
                'Email' => $addr[0],
                'Name'  => $addr[1],
            );
        }
        return $formatted;
    }

    /**
     * Translate a SendLayer API error response into a human-readable message.
     *
     * @since 1.0.0
     *
     * @param array|null $data Parsed JSON body.
     * @param int        $code HTTP status code.
     *
     * @return string Human-readable error message.
     */
    private function parse_api_error( $data, int $code ): string {
        $message = is_array( $data ) && isset( $data['Message'] )
            ? (string) $data['Message']
            : __( 'Unknown error', 'nimblymail-smtp' );

        switch ( $code ) {
            case 401:
                return __( 'Invalid SendLayer API key. Check the value entered in NimblyMail settings.', 'nimblymail-smtp' );
            case 403:
                return __( 'SendLayer account access denied. Verify your API key and account status.', 'nimblymail-smtp' );
        }

        return sprintf(
            /* translators: 1: Error message. 2: HTTP status code. */
            esc_html__( 'SendLayer API error: %1$s (HTTP %2$d)', 'nimblymail-smtp' ),
            $message,
            $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.
    }
}
