<?php
/**
 * Mailer fallback handling.
 *
 * @package MailDoctor
 */

namespace MailDoctor;

use MailDoctor\Helpers\Settings;
use WP_Error;
use function sanitize_key;

trait Mailer_Fallback_Trait {


	/**
	 * Handle failures reported by wp_mail_failed.
	 *
	 * @param mixed $error Error object.
	 * @return void
	 */
	public static function handle_send_failure( $error ): void {
		if ( self::$bypass_plugin || ! ( $error instanceof WP_Error ) ) {
			return;
		}

		$data       = (array) $error->get_error_data();
		$maildoctor = isset( $data['maildoctor'] ) && is_array( $data['maildoctor'] ) ? $data['maildoctor'] : array();
		$mail_data  = array();

		if ( isset( $data['mail_doctor_raw'] ) && is_array( $data['mail_doctor_raw'] ) ) {
			$mail_data = $data['mail_doctor_raw'];
		}

		$log_id = isset( $maildoctor['log_id'] ) ? (int) $maildoctor['log_id'] : 0;

		if ( $log_id <= 0 && isset( $mail_data['headers'] ) ) {
			$log_id = self::resolve_log_id_from_headers( $mail_data['headers'] );
		}

		if ( $log_id <= 0 && isset( $data['headers'] ) ) {
			$log_id = self::resolve_log_id_from_headers( $data['headers'] );
		}

		if ( $log_id <= 0 && isset( $maildoctor['mail_args_masked']['headers']['sample'] ) ) {
			$log_id = self::resolve_log_id_from_headers( $maildoctor['mail_args_masked']['headers']['sample'] );
		}

		if ( $log_id <= 0 ) {
			return;
		}

		self::debug_log(
			'handle_send_failure',
			array(
				'log_id' => $log_id,
				'error'  => $error->get_error_message(),
			)
		);

		if ( isset( self::$fallback_attempted[ $log_id ] ) ) {
			return;
		}

		$entry = Logger::get_entry( $log_id );
		$event = isset( $entry['event'] ) ? sanitize_key( (string) $entry['event'] ) : '';

		if ( 'maildoctor-test' === $event ) {
			self::trace_event(
				'fallback.skipped_test_event',
				array(
					'log_id' => $log_id,
					'code'   => $error->get_error_code(),
				)
			);
			return;
		}

		if ( ! self::should_attempt_fallback( $error ) ) {
			return;
		}

		if ( empty( $mail_data ) ) {
			self::trace_event(
				'fallback.skipped_no_payload',
				array(
					'log_id' => $log_id,
				)
			);
			return;
		}

		self::$fallback_attempted[ $log_id ] = true;

		self::attempt_fallback_transport( $log_id, $mail_data, $error );
	}

	/**
	 * Attempt to route the email through the native wp_mail transport.
	 *
	 * @param int                  $log_id    Log identifier.
	 * @param array<string, mixed> $mail_data Mail data from failure hook.
	 * @param WP_Error             $error     Original SMTP error.
	 * @return void
	 */
	protected static function attempt_fallback_transport( int $log_id, array $mail_data, WP_Error $error ): void {
		$to          = $mail_data['to'] ?? '';
		$subject     = $mail_data['subject'] ?? '';
		$message     = $mail_data['message'] ?? '';
		$headers     = $mail_data['headers'] ?? array();
		$attachments = $mail_data['attachments'] ?? array();

		self::debug_log(
			'attempt_fallback_transport',
			array(
				'log_id' => $log_id,
				'to'     => $to,
			)
		);

		self::$bypass_plugin = true;

		$result = wp_mail( $to, $subject, $message, $headers, $attachments );

		self::$bypass_plugin = false;

		$timestamp = gmdate( 'Y-m-d H:i:s' );

		if ( $result ) {
			self::debug_log( 'fallback transport succeeded', array( 'log_id' => $log_id ) );
			self::trace_event(
				'fallback.success',
				array(
					'log_id' => $log_id,
				)
			);
			Logger::annotate_fallback(
				$log_id,
				array(
					'mode'        => 'wp_mail',
					'success'     => true,
					'error'       => $error->get_error_message(),
					'occurred_at' => $timestamp,
				)
			);
			Logger::mark_sent( $log_id, array( 'provider_code' => 'fallback_wp_mail' ) );
			self::record_fallback_notice(
				$log_id,
				true,
				sprintf(
					/* translators: %d: log id */
					__( 'Mail Doctor fell back to wp_mail() for log #%d.', 'mail-doctor' ),
					$log_id
				)
			);
			if ( class_exists( __NAMESPACE__ . '\Alerts' ) ) {
				Alerts::notify(
					__( 'Mail Doctor used the fallback transport for an email.', 'mail-doctor' ),
					array(
						'log_id' => $log_id,
						'mode'   => 'wp_mail',
						'status' => 'success',
						'error'  => $error->get_error_message(),
					)
				);
			}
		} else {
			self::debug_log( 'fallback transport failed', array( 'log_id' => $log_id ) );
			self::trace_event(
				'fallback.failed',
				array(
					'log_id' => $log_id,
				)
			);
			$fallback_error = new WP_Error( 'mail_doctor_fallback_failed', __( 'Fallback transport failed after SMTP error.', 'mail-doctor' ) );
			Logger::annotate_fallback(
				$log_id,
				array(
					'mode'        => 'wp_mail',
					'success'     => false,
					'occurred_at' => $timestamp,
					'error'       => $fallback_error->get_error_message(),
				)
			);
			Logger::mark_failed( $log_id, $fallback_error );
			self::record_fallback_notice(
				$log_id,
				false,
				sprintf(
					/* translators: %d: log id */
					__( 'Mail Doctor fallback transport failed for log #%d.', 'mail-doctor' ),
					$log_id
				)
			);
			if ( class_exists( __NAMESPACE__ . '\Alerts' ) ) {
				Alerts::notify(
					__( 'Mail Doctor fallback transport failed.', 'mail-doctor' ),
					array(
						'log_id' => $log_id,
						'mode'   => 'wp_mail',
						'status' => 'failed',
					)
				);
			}
		}

		self::cleanup_context();
	}

	/**
	 * Determine whether fallback transport should be attempted.
	 *
	 * @param WP_Error $error Error from SMTP failure.
	 * @return bool
	 */
	protected static function should_attempt_fallback( WP_Error $error ): bool {
		if ( ! Settings::fallback_transport_enabled() ) {
			self::trace_event(
				'fallback.disabled',
				array(
					'code' => $error->get_error_code(),
				)
			);
			return false;
		}

		$message = strtolower( $error->get_error_message() );

		if ( false !== strpos( $message, 'authentication' ) || false !== strpos( $message, 'credentials' ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Store fallback notice for admin UI.
	 *
	 * @param int    $log_id  Log identifier.
	 * @param bool   $success Success flag.
	 * @param string $message Notice message.
	 * @return void
	 */
	protected static function record_fallback_notice( int $log_id, bool $success, string $message ): void {
		set_transient(
			'mail_doctor_fallback_notice',
			array(
				'log_id'  => $log_id,
				'success' => $success,
				'message' => $message,
				'time'    => time(),
			),
			5 * MINUTE_IN_SECONDS
		);
	}
}
