<?php
/**
 * Mailer bootstrap and guard routines.
 *
 * @package MailDoctor
 */

namespace MailDoctor;

use WP_Error;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

trait Mailer_Init_Trait {


	/**
	 * Initialize hooks.
	 *
	 * @return void
	 */
	public static function init(): void {
		self::debug_log( 'init', array( 'transport' => self::current_transport() ) );

		add_filter( 'pre_wp_mail', array( __CLASS__, 'apply_send_guards' ), 5, 2 );
		add_filter( 'wp_mail', array( __CLASS__, 'capture_attempt' ), 5 );
		add_action( 'phpmailer_init', array( __CLASS__, 'configure' ) );
		add_action( 'wp_mail_succeeded', array( __CLASS__, 'cleanup_context' ), 20, 0 );
		add_action( 'wp_mail_failed', array( __CLASS__, 'handle_send_failure' ), 12, 1 );
		add_action( 'wp_mail_failed', array( __CLASS__, 'cleanup_context' ), 20, 0 );
		add_filter( 'wp_mail_from', array( __CLASS__, 'filter_from_email' ), 20 );
		add_filter( 'wp_mail_from_name', array( __CLASS__, 'filter_from_name' ), 20 );
		add_action( 'shutdown', array( __CLASS__, 'process_shutdown_queue' ), 5 );
		add_action( 'mail_doctor_async_send', array( __CLASS__, 'handle_async_action' ), 10, 1 );
	}

	/**
	 * Apply idempotency and rate limiting before wp_mail executes.
	 *
	 * @param mixed               $short_circuit Preempted value.
	 * @param array<string,mixed> $atts          Mail arguments.
	 * @return mixed
	 */
	public static function apply_send_guards( $short_circuit, $atts ) {
		if ( null !== $short_circuit ) {
			return $short_circuit;
		}

		if ( self::$bypass_plugin ) {
			return null;
		}

		$args = self::normalize_mail_args( $atts );
		self::ensure_correlation_id();
		self::trace_event(
			'guards.enter',
			array(
				'to'        => implode( ',', self::parse_recipient_array( $args['to'] ?? array() ) ),
				'subject'   => mb_substr( (string) ( $args['subject'] ?? '' ), 0, 120 ),
				'transport' => self::current_transport(),
			)
		);
		self::debug_log(
			'apply_send_guards',
			array(
				'event'     => self::$current_event,
				'to'        => $args['to'],
				'subject'   => mb_substr( (string) ( $args['subject'] ?? '' ), 0, 120 ),
				'force'     => self::$force_override,
				'override'  => self::$should_override,
				'transport' => self::current_transport(),
			)
		);

		$window = max( 60, (int) Settings::get( 'idempotency_window', 300 ) );

		self::$idempotency_bucket    = (int) floor( time() / max( 1, $window ) );
		self::$idempotency_key       = self::generate_idempotency_key( $args );
		self::$idempotency_duplicate = false;

		if ( self::$idempotency_key ) {
			$existing = Logger::find_idempotent( self::$idempotency_key, (int) self::$idempotency_bucket );

			if ( $existing ) {
				self::$idempotency_duplicate = true;
				self::trace_event(
					'guards.idempotent',
					array(
						'log_id' => $existing['id'] ?? null,
					)
				);
				self::debug_log( 'apply_send_guards short-circuit: duplicate', array( 'log_id' => $existing['id'] ?? null ) );
				self::cleanup_context();
				return true;
			}
		}

		if ( class_exists( __NAMESPACE__ . '\Suppression' ) ) {
			$recipients = self::parse_recipient_array( $args['to'] ?? array() );
			if ( ! empty( $recipients ) ) {
				$suppressed = Suppression::filter_suppressed( $recipients );
				if ( ! empty( $suppressed ) ) {
					self::trace_event(
						'guards.suppressed',
						array(
							'count' => count( $suppressed ),
						)
					);
					self::debug_log( 'apply_send_guards suppressed recipients', array( 'count' => count( $suppressed ) ) );
					self::handle_suppressed_delivery( $args, $suppressed );
					self::cleanup_context();
					return true;
				}
			}
		}

		self::$attachment_meta  = array();
		self::$attachment_total = 0;

		$inspection = self::inspect_attachments( $args['attachments'] ?? array() );

		if ( isset( $inspection['error'] ) && $inspection['error'] instanceof WP_Error ) {
			self::handle_attachment_violation( $args, $inspection['error'], $inspection['meta'] ?? array() );
			self::trace_event(
				'guards.attachments_blocked',
				array(
					'error' => $inspection['error']->get_error_message(),
				)
			);
			return true;
		}

		self::$attachment_meta  = $inspection['meta'] ?? array();
		self::$attachment_total = isset( $inspection['total'] ) ? (int) $inspection['total'] : 0;

		$transport = self::current_transport();

		if ( self::is_api_transport( $transport ) ) {
			self::debug_log( 'apply_send_guards handing to API transport', array( 'transport' => $transport ) );
			$result = self::deliver_via_transport( $transport, $args );

			if ( isset( $result['deferred'] ) && $result['deferred'] ) {
				self::trace_event(
					'transport.deferred',
					array(
						'transport' => $transport,
					)
				);
				self::cleanup_context();
				return true;
			}

			if ( empty( $result['ok'] ) ) {
				$error = ( $result['error'] ?? null );
				if ( ! $error instanceof WP_Error ) {
					$error = new WP_Error(
						'mail_doctor_transport_failed',
						__( 'Delivery via configured transport failed.', 'mail-doctor' )
					);
				}

				self::trace_event(
					'transport.error',
					array(
						'transport' => $transport,
						'error'     => $error->get_error_message(),
						'log_id'    => $result['log_id'] ?? null,
					)
				);

				$error = self::augment_failed_error( $error, $args, $result['log_id'] ?? null );
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress core hook.
				do_action( 'wp_mail_failed', $error );
				self::cleanup_context();
				return $error;
			}

			self::trace_event(
				'transport.success',
				array(
					'transport'     => $transport,
					'log_id'        => $result['log_id'] ?? null,
					'provider_code' => $result['provider_code'] ?? '',
				)
			);
			self::cleanup_context();
			return true;
		}

		self::prepare_profile();
		self::ensure_phpmailer_loaded();

		if ( self::should_defer_async() ) {
			self::debug_log( 'apply_send_guards deferring async', array( 'mode' => Settings::async_mode() ) );
			self::enqueue_async_delivery( $args );
			self::cleanup_context();
			return true;
		}

		if ( class_exists( __NAMESPACE__ . '\RateLimiter' ) ) {
			$slot = RateLimiter::claim_slot();

			if ( ! $slot['allow'] ) {
				self::debug_log(
					'apply_send_guards rate limited',
					array(
						'wait'   => $slot['wait'] ?? null,
						'reason' => $slot['reason'] ?? null,
					)
				);
				self::trace_event(
					'guards.rate_limited',
					array(
						'wait'   => $slot['wait'] ?? null,
						'reason' => $slot['reason'] ?? null,
					)
				);
				self::queue_deferral( $args, max( 1, (int) $slot['wait'] ), $slot['reason'] ?? 'rate_limit' );
				self::cleanup_context();
				return true;
			}
		}

		return null;
	}

	/**
	 * Capture wp_mail payload and register log entry.
	 *
	 * @param array<string, mixed> $mail_args Mail arguments.
	 * @return array<string, mixed>
	 */
	public static function capture_attempt( array $mail_args ): array {
		if ( self::$bypass_plugin ) {
			return $mail_args;
		}

		$settings = Settings::all();

		self::$should_override = self::determine_override_scope( $settings );

		if ( null === self::$idempotency_bucket ) {
			$window_setting           = isset( $settings['idempotency_window'] ) ? (int) $settings['idempotency_window'] : 300;
			$window                   = max( 60, $window_setting );
			self::$idempotency_bucket = (int) floor( time() / max( 1, $window ) );
		}

		if ( null === self::$idempotency_key ) {
			self::$idempotency_key = self::generate_idempotency_key( self::normalize_mail_args( $mail_args ) );
		}

		self::prepare_profile();

		$context = self::build_logging_context();

		$log_id = Logger::log_attempt( $mail_args, $context );
		self::debug_log(
			'capture_attempt',
			array(
				'log_id'    => $log_id,
				'event'     => $context['event'] ?? null,
				'profile'   => self::$active_profile_key,
				'transport' => self::current_transport(),
			)
		);

		if ( $log_id ) {
			if ( ! empty( self::$attachment_meta ) ) {
				Logger::annotate_attachments( $log_id, self::$attachment_meta, self::$attachment_total );
			}
			self::$active_log_id  = $log_id;
			$original_headers     = $mail_args['headers'] ?? array();
			$headers              = self::inject_metadata_headers( $original_headers, $log_id );
			$headers              = self::inject_thread_headers( $headers, $context );
			$headers              = self::maybe_add_unsubscribe_headers( $headers, $settings, $context );
			$mail_args['headers'] = is_string( $original_headers ) ? implode( "\r\n", $headers ) : $headers;
		}

		return $mail_args;
	}
}
