<?php
/**
 * REST API Network Test Email Controller.
 *
 * @package NimblyMail\SMTP
 *
 * @since 1.0.0
 */

namespace NimblyMail\SMTP\Rest\Network;

use NimblyMail\SMTP\Options;
use NimblyMail\SMTP\Processor;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * Class NetworkTestEmailController
 *
 * Sends a test email through the main site's effective mail configuration
 * and captures PHPMailer debug output for the network admin.
 *
 * @since 1.0.0
 * @since 1.0.0 Added recipient allowlist enforcement with nimblymail_network_test_email_recipients filter and NIMBLYMAIL_TEST_EMAIL_UNRESTRICTED constant override.
 * @since 1.0.5 send_test() now restores the switched blog context via a
 *              finally block instead of only on the normal-completion path.
 */
class NetworkTestEmailController extends AbstractNetworkController {

    /**
     * Register the REST routes.
     *
     * @since 1.0.0
     *
     * @return void
     */
    protected function do_register_routes(): void {
        register_rest_route(
            self::NAMESPACE,
            '/network/test-email',
            array(
                array(
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => array( $this, 'send_test' ),
                    'permission_callback' => array( $this, 'permissions_check' ),
                    'args'                => array(
                        'to' => array(
                            'type'              => 'string',
                            'sanitize_callback' => 'sanitize_email',
                        ),
                    ),
                ),
            )
        );
    }

    /**
     * Send a network-level test email using the main site's mail configuration.
     *
     * Switches to the main site so Options resolves through the network layer,
     * captures PHPMailer debug output, and restores the original blog context
     * before returning.
     *
     * Enforces a rate limit of 5 requests per minute per user. By default,
     * the recipient is restricted to the network admin email address.
     *
     * To allow additional recipients, use the nimblymail_network_test_email_recipients
     * filter to return an array of permitted addresses, or define the constant
     * NIMBLYMAIL_TEST_EMAIL_UNRESTRICTED as true to disable the check entirely.
     *
     * @since 1.0.0
     * @since 1.0.0 Added recipient allowlist enforcement.
     * @since 1.0.5 Wrapped the switched-blog region in try/finally so
     *              restore_current_blog() always runs, even when a
     *              third-party wp_mail_failed/phpmailer_init callback throws
     *              during the test send — otherwise the request stays pinned
     *              to the main site for its remainder.
     *
     * @param WP_REST_Request $request The REST request.
     *
     * @return WP_REST_Response|WP_Error
     */
    public function send_test( WP_REST_Request $request ) {
        $rate_error = $this->check_rate_limit( 'network_test_rate', 5, 60 );
        if ( is_wp_error( $rate_error ) ) {
            return $rate_error;
        }

        $to = $request->get_param( 'to' );

        if ( empty( $to ) ) {
            $to = (string) get_site_option( 'admin_email' );
        }

        if ( ! is_email( $to ) ) {
            return new WP_Error(
                'invalid_email',
                __( 'Please provide a valid email address.', 'nimblymail-smtp' ),
                array( 'status' => 400 )
            );
        }

        if ( ! defined( 'NIMBLYMAIL_TEST_EMAIL_UNRESTRICTED' ) || ! NIMBLYMAIL_TEST_EMAIL_UNRESTRICTED ) {
            /**
             * Filters the list of permitted network test email recipients.
             *
             * Return an array of email addresses that are allowed as the `to`
             * field for network test email requests. Defaults to the network
             * admin email. Define NIMBLYMAIL_TEST_EMAIL_UNRESTRICTED as true
             * to bypass this check entirely.
             *
             * @since 1.0.0
             *
             * @param string[] $allowed Allowed recipient addresses.
             *
             * @return string[]
             */
            $allowed = (array) apply_filters( 'nimblymail_network_test_email_recipients', array( get_site_option( 'admin_email' ) ) );

            if ( ! in_array( $to, $allowed, true ) ) {
                return new WP_Error(
                    'nimblymail_test_restricted_recipient',
                    __( 'Test emails can only be sent to permitted addresses. Use the nimblymail_network_test_email_recipients filter or NIMBLYMAIL_TEST_EMAIL_UNRESTRICTED constant to allow other recipients.', 'nimblymail-smtp' ),
                    array( 'status' => 403 )
                );
            }
        }

        switch_to_blog( get_main_site_id() );
        Options::reset();

        try {
            $subject = __( '[Network] Test Email - NimblyMail', 'nimblymail-smtp' );
            $body    = $this->get_test_email_body( network_admin_url() );

            $bypass_admin = (bool) Options::init()->get( 'queue', 'bypass_admin_emails' );

            $outcome = $this->send_with_debug_capture(
                function () use ( $to, $subject, $body, $bypass_admin ) {
                    $headers = array( 'Content-Type: text/html' );
                    // Test emails default to synchronous delivery so the network
                    // admin sees an immediate result. Honour the queue.bypass_admin_emails
                    // toggle so operators can route them through the queue when wanted.
                    if ( $bypass_admin ) {
                        Processor::mark_bypass(
                            array(
                                'to'      => $to,
                                'subject' => $subject,
                                'message' => $body,
                            )
                        );
                    }
                    return wp_mail( $to, $subject, $body, $headers );
                }
            );
        } finally {
            // A third-party wp_mail_failed/phpmailer_init callback that throws
            // during the switched-context send must not leave the request
            // pinned to the main site — $wpdb->prefix, get_option(), upload
            // paths, and the object-cache blog prefix would stay wrong for
            // the rest of the request, including any shutdown/cron work that
            // runs afterward. The finally block guarantees this restore runs
            // even when the try block above throws, without swallowing the
            // exception.
            restore_current_blog();
            Options::reset();
        }

        return new WP_REST_Response(
            array(
                'success'   => $outcome['success'],
                'debug_log' => $outcome['debug_log'],
                'message'   => $outcome['success']
                    ? __( 'Network test email sent successfully!', 'nimblymail-smtp' )
                    : __( 'Failed to send network test email.', 'nimblymail-smtp' ),
            )
        );
    }

    /**
     * Generate the HTML body for the network test email.
     *
     * @since 1.0.0
     *
     * @param string $network_admin_url The network admin URL.
     *
     * @return string The HTML email body.
     */
    private function get_test_email_body( string $network_admin_url ): string {
        $timestamp = current_time( 'mysql' );

        return '<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body style="font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
    <h2 style="color: #1d2327;">NimblyMail Network Test Email</h2>
    <p>This is a test email sent from your WordPress network via <strong>NimblyMail</strong>.</p>
    <p>Sent at: ' . esc_html( $timestamp ) . '</p>
    <p>Network Admin: <a href="' . esc_url( $network_admin_url ) . '">' . esc_html( $network_admin_url ) . '</a></p>
    <hr style="border: none; border-top: 1px solid #dcdcde; margin: 20px 0;">
    <p style="color: #787c82; font-size: 13px;">If you received this email, your network mail configuration is working correctly.</p>
</body>
</html>';
    }
}
