<?php
/**
 * Routes outbound mail through the active provider.
 *
 * @package EmailSmtp
 * @since 1.1.0
 */

namespace EmailSmtp\Includes;

use WP_PowerSuite\Core\OptionStoreModuleSettingsRepository;

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

final class EmailRouter {

	/** @var array<string, mixed>|null */
	private static ?array $settings_overlay = null;

	/** @var DeliveryResult|null */
	private static ?DeliveryResult $last_result = null;

	/** Guard against recursive wp_mail while orchestrating. */
	private static bool $routing = false;

	/**
	 * @param array<string, mixed>|null $settings
	 */
	public static function set_settings_overlay( ?array $settings ): void {
		self::$settings_overlay = $settings;
	}

	public static function get_last_result(): ?DeliveryResult {
		return self::$last_result;
	}

	/**
	 * @return array<string, mixed>
	 */
	public static function get_settings(): array {
		if ( self::$settings_overlay !== null ) {
			return SettingsSchema::normalize( self::$settings_overlay );
		}

		$raw = OptionStoreModuleSettingsRepository::forModule( 'email-smtp' )->all( [] );
		$raw = is_array( $raw ) ? $raw : [];
		$normalized = SettingsSchema::normalize( $raw );

		if ( ! empty( $normalized['_migration_dirty'] ) ) {
			$normalized = SettingsMigrator::maybe_persist(
				$normalized,
				static function ( array $settings ): void {
					unset( $settings['_migration_dirty'] );
					OptionStoreModuleSettingsRepository::forModule( 'email-smtp' )->save( $settings );
				}
			);
		}

		return $normalized;
	}

	public static function get_active_provider(): ?ProviderInterface {
		$settings = self::get_settings();
		return ProviderRegistry::get( (string) ( $settings['provider'] ?? 'wordpress' ) );
	}

	/**
	 * Handle pre_wp_mail for API and SMTP connections (retries + fallback orchestration).
	 *
	 * WordPress default mail is left to core only when the resolved connection is
	 * explicitly the PHP Mail (wordpress) provider. SMTP/API connections never
	 * silently fall through to local Postfix / PHP mail().
	 *
	 * @param mixed                $null
	 * @param array<string, mixed> $atts
	 * @return bool|null
	 */
	public static function maybe_send_via_api( $null, array $atts ) {
		unset( $null );

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

		if ( ! OptionStoreModuleSettingsRepository::isModuleEnabled( 'email-smtp' ) ) {
			return null;
		}

		$base        = self::get_settings();
		$draft       = EmailMessage::from_wp_mail_args( $atts );
		$from_email  = $draft->get_from_email();
		$connection_id = ConnectionStore::resolve_connection_id( $base, $from_email );
		$routing     = ConnectionStore::routing_reason( $base, $from_email, $connection_id );
		$settings    = $connection_id !== ''
			? ConnectionStore::settings_for_connection( $base, $connection_id )
			: ConnectionStore::settings_for_from( $base, $from_email );
		$provider_id = sanitize_key( (string) ( $settings['provider'] ?? 'wordpress' ) );
		$provider    = ProviderRegistry::get( $provider_id );

		$uses_api  = SmtpRelayProfiles::connection_uses_api( $settings, $provider );
		$uses_smtp = SmtpRelayProfiles::connection_uses_smtp( $settings, $provider );

		DeliveryAttemptContext::begin(
			[
				'provider'        => $provider_id,
				'connection_id'   => $connection_id,
				'connection_name' => sanitize_text_field( (string) ( $settings['name'] ?? '' ) ),
				'from_email'      => $from_email,
				'routing_reason'  => $routing,
				'orchestrated'    => ( $uses_api || $uses_smtp ) ? 1 : 0,
				'transport'       => $uses_smtp ? 'smtp' : ( $uses_api ? 'api' : 'php_mail' ),
			]
		);

		// Email Simulation: record the attempt, never deliver (including PHP Mail connections).
		if ( self::is_simulation_enabled( $base ) ) {
			return self::complete_simulated_send( $atts, $draft, $settings, $connection_id, $provider_id );
		}

		// Explicit PHP Mail connection — WordPress core / PHPMailer mail transport.
		// Logging enrichment is finalized in MailInterceptor::on_mail_finished after PHPMailer runs.
		if ( ! $provider || $provider->get_id() === 'wordpress' ) {
			DeliveryAttemptContext::merge(
				[
					'provider'     => 'wordpress',
					'transport'    => 'php_mail',
					'orchestrated' => 0,
				]
			);
			return null;
		}

		// Misconfigured / unknown provider that looks like SMTP/API in settings: fail closed.
		if ( ! $uses_api && ! $uses_smtp ) {
			if ( $provider_id !== '' && $provider_id !== 'wordpress' ) {
				$result = DeliveryResult::failure(
					__( 'Email Delivery could not determine a valid transport for this connection.', 'wp-powersuite' ),
					'invalid_transport:' . $provider_id,
					'configuration',
					'invalid_transport',
					false,
					__( 'Open Email Delivery settings and choose SMTP or API for this connection.', 'wp-powersuite' ),
					0,
					[ 'transport' => 'none', 'connection_id' => $connection_id ]
				);
				self::$last_result = $result;
				self::publish_log_enrichment( $result, $provider );
				do_action( 'wp_mail_failed', $result->to_wp_error() );
				return false;
			}
			return null;
		}

		$message = $draft->apply_sender_settings( SettingsSchema::sender_bag( $settings ) );

		/**
		 * Filter the normalized email message before provider send.
		 *
		 * @param EmailMessage         $message
		 * @param ProviderInterface    $provider
		 * @param array<string, mixed> $settings
		 */
		$message = apply_filters( 'wp_powersuite_email_delivery_message', $message, $provider, $settings );
		if ( ! $message instanceof EmailMessage ) {
			$result = DeliveryResult::failure(
				__( 'Email Delivery message filter returned an invalid value.', 'wp-powersuite' ),
				'invalid_message_filter',
				'configuration',
				'invalid_message',
				false,
				'',
				0,
				[ 'transport' => $uses_smtp ? 'smtp' : 'api' ]
			);
			self::$last_result = $result;
			self::publish_log_enrichment( $result, $provider );
			do_action( 'wp_mail_failed', $result->to_wp_error() );
			return false;
		}

		DeliveryAttemptContext::merge(
			[
				'from_email'      => $message->get_from_email(),
				'envelope_sender' => $message->get_return_path() !== '' ? $message->get_return_path() : $message->get_from_email(),
			]
		);

		/**
		 * Fires before provider send.
		 *
		 * @param EmailMessage         $message
		 * @param ProviderInterface    $provider
		 * @param array<string, mixed> $settings
		 */
		do_action( 'wp_powersuite_email_delivery_before_send', $message, $provider, $settings );

		self::$routing = true;
		try {
			$outcome = DeliveryOrchestrator::send( $message, $provider, $settings );
		} finally {
			self::$routing = false;
		}

		$result = $outcome['result'];
		$final  = $outcome['provider'];
		self::$last_result = $result;

		$meta            = $result->get_meta();
		$final_transport = self::resolve_transport_label( $final, $settings, $meta );

		DeliveryAttemptContext::merge(
			[
				'provider'            => $final->get_id(),
				'transport'           => $final_transport,
				'envelope_sender'     => sanitize_email( (string) ( $meta['envelope_sender'] ?? DeliveryAttemptContext::get( 'envelope_sender' ) ) ),
				'retry_count'         => (int) $outcome['retry_count'],
				'fallback_used'       => (int) $outcome['fallback_used'],
				'orchestrated'        => 1,
				'provider_message_id' => $result->get_provider_message_id(),
				'duration_ms'         => $result->get_duration_ms(),
				'delivery_status'     => $result->is_success() ? 'accepted' : '',
				'connection_id'       => sanitize_key( (string) ( $meta['connection_id'] ?? $connection_id ) ),
			]
		);

		self::publish_log_enrichment( $result, $final );

		if ( $result->is_success() ) {
			do_action( 'wp_powersuite_email_delivery_after_send', $message, $result, $final );
			do_action( 'wp_mail_succeeded', $atts );
			return true;
		}

		// Fail closed: never return null after an orchestrated SMTP/API attempt.
		do_action( 'wp_powersuite_email_delivery_failed', $message, $result, $final );
		if ( ! apply_filters( 'wp_powersuite_email_delivery_skip_failure_alert', false ) ) {
			FailureNotifier::maybe_notify( $result, $final, $base );
		}
		do_action( 'wp_mail_failed', $result->to_wp_error() );
		return false;
	}

	/**
	 * @param array<string, mixed> $settings
	 */
	public static function is_simulation_enabled( ?array $settings = null ): bool {
		$settings = $settings ?? self::get_settings();
		return ! empty( $settings['simulation_mode'] ) && (string) $settings['simulation_mode'] !== '0';
	}

	/**
	 * Pretend the send succeeded without contacting SMTP/API/local mail.
	 * Email Logs (wp_mail filter) already inserted a pending row; we enrich + mark succeeded.
	 *
	 * @param array<string, mixed> $atts
	 * @param array<string, mixed> $settings Connection slice.
	 */
	private static function complete_simulated_send( array $atts, EmailMessage $draft, array $settings, string $connection_id, string $provider_id ): bool {
		$message = $draft->apply_sender_settings( SettingsSchema::sender_bag( $settings ) );
		$from    = $message->get_from_email() !== '' ? $message->get_from_email() : $draft->get_from_email();
		$envelope = $message->get_return_path() !== '' ? $message->get_return_path() : $from;

		$sim_id = 'sim_' . wp_generate_uuid4();
		$result = DeliveryResult::success(
			__( 'Email simulation is on — message was logged but not sent.', 'wp-powersuite' ),
			$sim_id,
			0,
			[
				'transport'       => 'simulation',
				'connection_id'   => $connection_id,
				'envelope_sender' => $envelope,
				'simulated'       => 1,
			]
		);
		self::$last_result = $result;

		DeliveryAttemptContext::merge(
			[
				'provider'            => $provider_id !== '' ? $provider_id : 'simulation',
				'transport'           => 'simulation',
				'from_email'          => $from,
				'envelope_sender'     => $envelope,
				'orchestrated'        => 1,
				'provider_message_id' => $sim_id,
				'duration_ms'         => 0,
				'delivery_status'     => 'accepted',
				'connection_id'       => $connection_id,
				'trigger_source'      => 'simulation',
			]
		);

		self::publish_log_enrichment( $result, ProviderRegistry::get( $provider_id ) );

		/**
		 * Fires when a message is accepted in Email Simulation mode (not delivered).
		 *
		 * @param EmailMessage         $message
		 * @param DeliveryResult       $result
		 * @param array<string, mixed> $atts
		 */
		// Do not fire wp_mail_succeeded — other plugins must not treat simulation as a real send.
		do_action( 'wp_powersuite_email_delivery_simulated', $message, $result, $atts );

		return true;
	}

	/**
	 * @param array<string, mixed> $settings
	 * @param array<string, mixed> $meta
	 */
	private static function resolve_transport_label( ProviderInterface $provider, array $settings, array $meta ): string {
		if ( isset( $meta['transport'] ) && is_string( $meta['transport'] ) && $meta['transport'] !== '' ) {
			return sanitize_key( $meta['transport'] );
		}
		if ( $provider->uses_phpmailer_smtp() || SmtpRelayProfiles::connection_uses_smtp( $settings, $provider ) ) {
			return 'smtp';
		}
		if ( in_array( $provider->get_id(), [ 'google', 'microsoft' ], true ) ) {
			return 'oauth';
		}
		if ( $provider->uses_api_transport() ) {
			return 'api';
		}
		return 'php_mail';
	}

	/**
	 * @param DeliveryResult|null    $result
	 * @param ProviderInterface|null $provider
	 */
	private static function publish_log_enrichment( ?DeliveryResult $result, ?ProviderInterface $provider ): void {
		$log_data = DeliveryAttemptContext::for_log();
		// Ensure Email Logs stores a From header even when the provider used a dedicated PHPMailer instance.
		$from_for_log = sanitize_email( (string) ( $log_data['from_email'] ?? '' ) );
		if ( is_email( $from_for_log ) ) {
			$log_data['headers'] = 'From: ' . $from_for_log;
		}
		if ( $result instanceof DeliveryResult ) {
			$log_data['provider_message_id'] = $result->get_provider_message_id();
			$log_data['duration_ms']         = $result->get_duration_ms();
			$log_data['delivery_status']     = $result->is_success() ? 'accepted' : '';
		}

		/**
		 * Enrich email log rows when Email Logs is active.
		 *
		 * @param array<string, mixed>      $data
		 * @param DeliveryResult|null       $result
		 * @param ProviderInterface|null    $provider
		 */
		$log_data = apply_filters( 'wp_powersuite_email_delivery_log_data', $log_data, $result, $provider );
		do_action( 'wp_powersuite_email_delivery_log_enrich', $log_data, $result, $provider );
	}

	/**
	 * Send a test message through the real routing path.
	 */
	public static function send_test_email( string $to, string $subject, string $body, bool $is_html = true ): DeliveryResult {
		$headers = [
			'Content-Type: ' . ( $is_html ? 'text/html' : 'text/plain' ) . '; charset=UTF-8',
		];

		// Use the default connection From so routing hits the connection under test
		// (and provider-authorized senders), not wordpress@$host.
		$settings  = ConnectionStore::settings_for_from( self::get_settings(), '' );
		$from      = sanitize_email( (string) ( $settings['from_email'] ?? '' ) );
		$from_name = sanitize_text_field( (string) ( $settings['from_name'] ?? '' ) );
		if ( is_email( $from ) ) {
			$headers[] = $from_name !== ''
				? sprintf( 'From: %s <%s>', $from_name, $from )
				: 'From: ' . $from;
		}

		$started = (int) round( microtime( true ) * 1000 );
		self::$last_result = null;
		$sent = wp_mail( $to, $subject, $body, $headers );
		$duration = (int) round( microtime( true ) * 1000 ) - $started;

		if ( self::$last_result instanceof DeliveryResult ) {
			return self::$last_result;
		}

		if ( $sent ) {
			return DeliveryResult::success(
				__( 'Test email sent successfully.', 'wp-powersuite' ),
				'',
				$duration,
				[ 'provider' => (string) ( self::get_settings()['provider'] ?? 'wordpress' ), 'transport' => 'php_mail' ]
			);
		}

		return DeliveryResult::failure(
			__( 'Failed to send test email.', 'wp-powersuite' ),
			'',
			'unknown',
			'',
			false,
			'',
			$duration
		);
	}
}
