<?php
/**
 * WooCommerce integrations for Mail Doctor.
 *
 * @package MailDoctor
 */

namespace MailDoctor;

use MailDoctor\Helpers\Settings;
use WC;
use WC_Email;
use WP_Error;
use function esc_html;
use function wp_strip_all_tags;

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

/**
 * Woo-specific helpers.
 */
class Woo {


	/**
	 * Option key for email toggles.
	 */
	const TOGGLE_OPTION = 'mail_doctor_wc_email_toggles';

	/**
	 * Track registered filters to avoid duplicates.
	 *
	 * @var array<string, bool>
	 */
	protected static $registered_filters = array();

	/**
	 * Stack of pending WooCommerce mail callbacks.
	 *
	 * @var array<int, array{callback: callable|string, email: (WC_Email|null)}>
	 */
	protected static $mail_callback_stack = array();

	/**
	 * Write debug output when debug logging is enabled.
	 *
	 * @param string              $message Log message.
	 * @param array<string,mixed> $context Extra context data.
	 * @return void
	 */
	protected static function debug_log( string $message, array $context = array() ): void {
		if ( ! Settings::debug_logging_enabled() ) {
			return;
		}

		$line = '[MailDoctor Woo] ' . $message;

		if ( ! empty( $context ) ) {
			$encoded = wp_json_encode( $context );
			if ( false !== $encoded ) {
				$line .= ' :: ' . $encoded;
			}
		}

		error_log( $line ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
	}

	/**
	 * Initialize Woo hooks.
	 *
	 * @return void
	 */
	public static function init(): void {
		if ( ! class_exists( 'WooCommerce' ) ) {
			return;
		}

		add_filter( 'woocommerce_mail_callback', array( __CLASS__, 'filter_mail_callback' ), 1, 2 );
		add_filter( 'woocommerce_mail_callback_params', array( __CLASS__, 'filter_mail_callback_params' ), 1, 2 );
		add_action( 'woocommerce_init', array( __CLASS__, 'register_email_filters' ), 20 );
		add_action( 'woocommerce_email', array( __CLASS__, 'capture_email_context' ), 5, 1 );
		add_action( 'woocommerce_email_sent', array( __CLASS__, 'release_email_context' ), 10 );
		add_action( 'woocommerce_email_after_send', array( __CLASS__, 'release_email_context' ), 10 );
	}

	/**
	 * Persist email toggle preferences.
	 *
	 * @param array<string, bool> $map Map of email id => enabled.
	 * @return void
	 */
	public static function save_toggles( array $map ): void {
		update_option( self::TOGGLE_OPTION, $map );
		self::$registered_filters = array();
		self::register_email_filters();
	}

	/**
	 * Get stored toggles.
	 *
	 * @return array<string, bool>
	 */
	public static function get_toggles(): array {
		$stored = get_option( self::TOGGLE_OPTION, array() );

		return is_array( $stored ) ? $stored : array();
	}

	/**
	 * Register enable/disable filters for Woo emails.
	 *
	 * @return void
	 */
	public static function register_email_filters(): void {
		if ( ! function_exists( 'WC' ) ) {
			return;
		}

		$mailer = WC()->mailer();
		if ( ! $mailer ) {
			return;
		}

		$toggles = self::get_toggles();

		foreach ( $mailer->get_emails() as $email ) {
			if ( ! $email instanceof WC_Email ) {
				continue;
			}

			$filter = sprintf( 'woocommerce_email_enabled_%s', $email->id );

			if ( isset( self::$registered_filters[ $filter ] ) ) {
				continue;
			}

			add_filter(
				$filter,
				static function ( $enabled ) use ( $email, $toggles ) {
					if ( isset( $toggles[ $email->id ] ) ) {
						return (bool) $toggles[ $email->id ];
					}

					return $enabled;
				},
				20
			);

			self::$registered_filters[ $filter ] = true;
		}
	}

	/**
	 * Capture WooCommerce email context using mail callback filter.
	 *
	 * @param array    $params Callback parameters.
	 * @param WC_Email $email  Email instance.
	 * @return array
	 */
	public static function filter_mail_callback_params( array $params, $email ): array {
		if ( $email instanceof WC_Email ) {
			self::debug_log(
				'woocommerce_mail_callback_params',
				array(
					'email_id'  => $email->id,
					'transport' => Settings::get( 'transport', 'smtp' ),
				)
			);
		} else {
			self::debug_log( 'woocommerce_mail_callback_params', array( 'email_detected' => false ) );
		}

		return $params;
	}

	/**
	 * Capture context when Woo triggers email.
	 *
	 * @param WC_Email $email Email instance.
	 * @return void
	 */
	public static function capture_email_context( $email ): void {
		if ( ! $email instanceof WC_Email ) {
			return;
		}

		self::debug_log(
			'capture_email_context',
			array(
				'email_id'    => $email->id,
				'email_class' => get_class( $email ),
			)
		);
		Mailer::trace_maildbg(
			'wc.email_context',
			array(
				'email_id'    => $email->id,
				'email_class' => get_class( $email ),
			)
		);

		Mailer::set_event_context( 'woocommerce_' . $email->id );
		Mailer::set_email_class( get_class( $email ) );
		Mailer::set_email_id( $email->id );

		$object = $email->object ?? null;

		if ( $object && method_exists( $object, 'get_id' ) ) {
			Mailer::set_order_context( (int) $object->get_id() );
		}
	}

	/**
	 * Clear context once Woo email sending completes.
	 *
	 * WooCommerce passes different argument sets to the
	 * `woocommerce_email_sent` and `woocommerce_email_after_send` hooks. Accept a
	 * variadic signature so we remain compatible regardless of version or hook
	 * usage.
	 *
	 * @param mixed ...$unused Forwarded parameters (not used).
	 * @return void
	 */
	public static function release_email_context( ...$unused ): void {
		self::debug_log( 'release_email_context' );
		Mailer::trace_maildbg( 'wc.email_context_released' );
		Mailer::clear_event_context();
		Mailer::clear_email_class();
		Mailer::clear_email_id();
	}

	/**
	 * Return map of Woo email classes for filtering.
	 *
	 * @return array<string, string> Class => label.
	 */
	public static function list_email_classes(): array {
		if ( ! function_exists( 'WC' ) ) {
			return array();
		}

		$mailer = WC()->mailer();

		if ( ! $mailer ) {
			return array();
		}

		$emails = array();
		foreach ( $mailer->get_emails() as $email ) {
			if ( ! $email instanceof WC_Email ) {
				continue;
			}
			$emails[ get_class( $email ) ] = $email->get_title();
		}

		asort( $emails );

		return $emails;
	}

	/**
	 * Provide metadata for Woo email toggle UI.
	 *
	 * @return array<int,array<string,mixed>>
	 */
	public static function get_email_toggle_data(): array {
		if ( ! function_exists( 'WC' ) ) {
			return array();
		}

		$mailer = WC()->mailer();

		if ( ! $mailer ) {
			return array();
		}

		$toggles = self::get_toggles();
		$rows    = array();

		foreach ( $mailer->get_emails() as $email ) {
			if ( ! $email instanceof WC_Email ) {
				continue;
			}

			$default_enabled = 'yes' === $email->get_option( 'enabled', 'yes' );
			$current_enabled = method_exists( $email, 'is_enabled' ) ? (bool) $email->is_enabled() : $default_enabled;
			$description     = $email->get_description();

			$rows[] = array(
				'id'              => $email->id,
				'title'           => $email->get_title(),
				'description'     => is_string( $description ) ? $description : '',
				'default_enabled' => (bool) $default_enabled,
				'current_enabled' => (bool) $current_enabled,
				'overridden'      => array_key_exists( $email->id, $toggles ),
			);
		}

		usort(
			$rows,
			static function ( array $a, array $b ): int {
				return strcasecmp( $a['title'], $b['title'] );
			}
		);

		return $rows;
	}

	/**
	 * Provide previewable WooCommerce emails.
	 *
	 * @return array<int, array<string,string>>
	 */
	public static function get_previewable_emails(): array {
		if ( ! function_exists( 'WC' ) ) {
			return array();
		}

		$mailer = WC()->mailer();

		if ( ! $mailer ) {
			return array();
		}

		$list = array();

		foreach ( $mailer->get_emails() as $email ) {
			if ( ! $email instanceof WC_Email ) {
				continue;
			}

			$list[] = array(
				'id'    => $email->id,
				'title' => $email->get_title(),
			);
		}

		usort(
			$list,
			static function ( array $a, array $b ): int {
				return strcasecmp( $a['title'], $b['title'] );
			}
		);

		return $list;
	}

	/**
	 * Locate a WooCommerce email instance by its identifier.
	 *
	 * WooCommerce stores email instances keyed by class name, while the human-readable ID
	 * (e.g. "customer_completed_order") lives on the object itself. Searching ensures
	 * previews function even if array keys differ.
	 *
	 * @param string     $email_id Email identifier.
	 * @param \WC_Emails $mailer   WooCommerce mailer service.
	 * @return WC_Email|null
	 */
	protected static function locate_email_by_id( string $email_id, $mailer ): ?WC_Email {
		if ( ! $mailer || ! method_exists( $mailer, 'get_emails' ) ) {
			return null;
		}

		foreach ( $mailer->get_emails() as $email ) {
			if ( ! $email instanceof WC_Email ) {
				continue;
			}

			if ( $email_id === $email->id ) {
				return $email;
			}
		}

		return null;
	}

	/**
	 * Search WooCommerce orders for preview selection.
	 *
	 * @param string $term Search term or order identifier.
	 * @return array<int, array<string, string>>
	 */
	public static function search_orders( string $term ): array {
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return array();
		}

		$term = trim( $term );

		$args = array(
			'limit'   => 10,
			'orderby' => 'date',
			'order'   => 'DESC',
			'return'  => 'objects',
		);

		if ( is_numeric( $term ) && $term > 0 ) {
			$args['include'] = array( (int) $term );
		} elseif ( '' !== $term ) {
			$args['search']         = '*' . $term . '*';
			$args['search_columns'] = array( 'billing_email', 'billing_last_name', 'billing_first_name' );
		}

		$orders  = wc_get_orders( $args );
		$results = array();

		foreach ( $orders as $order ) {
			if ( ! $order instanceof \WC_Order ) {
				continue;
			}

			$customer = $order->get_formatted_billing_full_name();
			if ( empty( $customer ) ) {
				$customer = $order->get_billing_email();
			}
			if ( empty( $customer ) ) {
				$customer = __( 'Guest customer', 'mail-doctor' );
			}

			$results[ $order->get_id() ] = array(
				'id'     => (string) $order->get_id(),
				'label'  => sprintf( '#%1$d — %2$s', $order->get_id(), $customer ),
				'status' => wc_get_order_status_name( $order->get_status() ),
				'total'  => wp_strip_all_tags( $order->get_formatted_order_total() ),
			);
		}

		return array_values( $results );
	}

	/**
	 * Return preview payload for Woo email without sending.
	 *
	 * @param string   $email_id Email identifier.
	 * @param int|null $order_id Optional order context.
	 * @return array<string,mixed>|WP_Error
	 */
	public static function get_preview( string $email_id, ?int $order_id = null ) {
		if ( ! function_exists( 'WC' ) ) {
			return new WP_Error( 'mail_doctor_woo_missing', __( 'WooCommerce is not available for previews.', 'mail-doctor' ) );
		}

		$mailer = WC()->mailer();
		if ( ! $mailer ) {
			return new WP_Error( 'mail_doctor_mailer_missing', __( 'WooCommerce mailer is not available.', 'mail-doctor' ) );
		}

		$email = self::locate_email_by_id( $email_id, $mailer );

		if ( ! $email ) {
			return new WP_Error( 'mail_doctor_email_missing', __( 'Email template not found.', 'mail-doctor' ) );
		}

		$preview = self::prepare_email_preview( $email, $order_id );

		if ( $preview instanceof WP_Error ) {
			return $preview;
		}

		$subject = $preview['subject'] ?? '';
		if ( '' === $subject ) {
			$subject = $email->get_subject();
		}

		return array(
			'html'     => $preview['html'] ?? '',
			'order_id' => isset( $preview['order_id'] ) ? (int) $preview['order_id'] : null,
			'email_id' => $email_id,
			'title'    => $email->get_title(),
			'subject'  => $subject,
			'heading'  => method_exists( $email, 'get_heading' ) ? $email->get_heading() : '',
		);
	}

	/**
	 * Send preview of Woo email to current user.
	 *
	 * @param string   $email_id Email identifier.
	 * @param int|null $order_id Specific order to use for rendering.
	 * @return array<string, mixed>|WP_Error
	 */
	public static function send_preview( string $email_id, ?int $order_id = null ) {
		if ( ! function_exists( 'WC' ) ) {
			return new WP_Error( 'mail_doctor_woo_missing', __( 'WooCommerce is not available for previews.', 'mail-doctor' ) );
		}

		$mailer = WC()->mailer();
		if ( ! $mailer ) {
			return new WP_Error( 'mail_doctor_mailer_missing', __( 'WooCommerce mailer is not available.', 'mail-doctor' ) );
		}

		$email = self::locate_email_by_id( $email_id, $mailer );

		if ( ! $email ) {
			return new WP_Error( 'mail_doctor_email_missing', __( 'Email template not found.', 'mail-doctor' ) );
		}

		$user    = wp_get_current_user();
		$preview = self::get_preview( $email_id, $order_id );

		if ( $preview instanceof WP_Error ) {
			return $preview;
		}

		$html       = $preview['html'] ?? '';
		$order_used = isset( $preview['order_id'] ) ? (int) $preview['order_id'] : null;

		Mailer::force_next_override();
		Mailer::set_event_context( 'woocommerce_preview_' . $email_id );
		Mailer::set_email_id( $email_id );

		$subject_line = $preview['subject'] ?? '';
		if ( '' === $subject_line ) {
			$subject_line = $email->get_subject();
		}

		$subject = sprintf(
			/* translators: %s Email template name. */
			__( '[Preview] %s', 'mail-doctor' ),
			$subject_line ?: ( $preview['title'] ?? $email->get_title() )
		);

		$headers = array( 'Content-Type: text/html; charset=UTF-8' );

		if ( empty( $user->user_email ) ) {
			Mailer::clear_event_context();
			Mailer::clear_email_id();
			return new WP_Error( 'mail_doctor_missing_email', __( 'The current user does not have an email address.', 'mail-doctor' ) );
		}

		wp_mail( $user->user_email, $subject, $html, $headers );

		Mailer::clear_event_context();
		Mailer::clear_email_id();

		return array(
			'html'     => $html,
			'sent_to'  => $user->user_email,
			'order_id' => $order_used,
		);
	}

	/**
	 * Prepare preview HTML content.
	 *
	 * @param WC_Email $email    Email object.
	 * @param int|null $order_id Specific order identifier.
	 * @return array<string, mixed>|WP_Error
	 */
	protected static function prepare_email_preview( WC_Email $email, ?int $order_id = null ) {
		$original_object = $email->object ?? null;

		$preview_object = self::resolve_preview_object( $email, $order_id );

		if ( $preview_object instanceof WP_Error ) {
			return $preview_object;
		}

		$resolved_order_id = null;

		if ( $preview_object ) {
			$email->object = $preview_object;
			if ( method_exists( $preview_object, 'get_id' ) ) {
				Mailer::set_order_context( (int) $preview_object->get_id() );
				$resolved_order_id = (int) $preview_object->get_id();
			}
		}

		$content          = '';
		$rendered_html    = '';
		$rendered_subject = method_exists( $email, 'get_subject' ) ? wp_strip_all_tags( (string) $email->get_subject() ) : '';
		$content_type     = method_exists( $email, 'get_content_type' ) ? (string) $email->get_content_type() : 'text/html';

		if ( method_exists( $email, 'setup_locale' ) ) {
			$email->setup_locale();
		}

		if ( '' === $rendered_html ) {
			$content = $email->get_content();
		}

		if ( property_exists( $email, 'sending' ) ) {
			$email->sending = false;
		}

		if ( method_exists( $email, 'restore_locale' ) ) {
			$email->restore_locale();
		}

		$email->object = $original_object;

		if ( '' === $rendered_html && '' !== trim( (string) $content ) ) {
			$is_plain_email = ( 'text/plain' === $content_type || ( method_exists( $email, 'get_email_type' ) && 'plain' === $email->get_email_type() ) );

			if ( $is_plain_email ) {
				$rendered_html = '<pre class="mail-doctor-woo-preview__plain">' . esc_html( $content ) . '</pre>';
			} else {
				// Use a custom filter for preview to avoid "woocommerce_" prefix violation in apply_filters().
				$rendered_html = apply_filters( 'mail_doctor_woocommerce_mail_content_preview', $email->style_inline( $content ) );
			}
		}

		if ( '' === $rendered_html ) {
			$rendered_html = __( 'Preview content unavailable for this email template.', 'mail-doctor' );
		}

		return array(
			'html'     => $rendered_html,
			'subject'  => $rendered_subject,
			'order_id' => $resolved_order_id,
		);
	}

	/**
	 * Locate a preview object for the email template.
	 *
	 * @param WC_Email $email    Email instance.
	 * @param int|null $order_id Specific order identifier.
	 * @return object|WP_Error|null
	 */
	protected static function resolve_preview_object( WC_Email $email, ?int $order_id = null ) {
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return null;
		}

		if ( $order_id && function_exists( 'wc_get_order' ) ) {
			$order = wc_get_order( $order_id );
			if ( $order ) {
				return $order;
			}

			return new WP_Error( 'mail_doctor_order_missing', __( 'The selected order could not be found.', 'mail-doctor' ) );
		}

		$orders = wc_get_orders(
			array(
				'limit'   => 1,
				'orderby' => 'date',
				'order'   => 'DESC',
				'return'  => 'objects',
				'status'  => array_keys( wc_get_order_statuses() ),
			)
		);

		if ( ! empty( $orders ) ) {
			return $orders[0];
		}

		return new WP_Error(
			'mail_doctor_order_missing',
			__( 'No WooCommerce orders found to build a preview. Create a sample order first.', 'mail-doctor' )
		);
	}

	/**
	 * Capture WooCommerce mail callback and ensure we run our wrapper.
	 *
	 * @param callable|string $callback Original callback.
	 * @param mixed           $email    Email instance.
	 * @return callable
	 */
	public static function filter_mail_callback( $callback, $email ) {
		self::debug_log(
			'filter_mail_callback',
			array(
				'callback' => is_string( $callback ) ? $callback : ( is_array( $callback ) ? implode(
					'::',
					array_map(
						static function ( $part ) {
							return is_object( $part ) ? get_class( $part ) : (string) $part;
						},
						(array) $callback
					)
				) : gettype( $callback ) ),
				'email_id' => $email instanceof WC_Email ? $email->id : null,
			)
		);
		if ( $email instanceof WC_Email ) {
			Mailer::trace_maildbg(
				'wc.mail_callback_wrapped',
				array(
					'email_id' => $email->id,
				)
			);
		}

		if ( ! is_callable( $callback ) ) {
			$callback = 'wp_mail';
		}

		self::$mail_callback_stack[] = array(
			'callback' => $callback,
			'email'    => $email instanceof WC_Email ? $email : null,
		);

		return array( __CLASS__, 'send_mail_callback' );
	}

	/**
	 * Wrapper around the WooCommerce mail callback so we can enforce SMTP override.
	 *
	 * @param mixed ...$args Arguments passed to the original callback.
	 * @return mixed
	 */
	public static function send_mail_callback( ...$args ) {
		$frame = array_pop( self::$mail_callback_stack );
		if ( ! is_array( $frame ) ) {
			$frame = array(
				'callback' => 'wp_mail',
				'email'    => null,
			);
		}
		$callback = isset( $frame['callback'] ) && is_callable( $frame['callback'] ) ? $frame['callback'] : 'wp_mail';
		$email    = $frame['email'] ?? null;

		self::debug_log(
			'send_mail_callback start',
			array(
				'callback'   => is_string( $callback ) ? $callback : ( is_array( $callback ) ? implode(
					'::',
					array_map(
						static function ( $part ) {
							return is_object( $part ) ? get_class( $part ) : (string) $part;
						},
						(array) $callback
					)
				) : gettype( $callback ) ),
				'args_count' => count( $args ),
				'email_id'   => $email instanceof WC_Email ? $email->id : null,
			)
		);
		Mailer::trace_maildbg(
			'wc.send_mail_callback_start',
			array(
				'email_id'   => $email instanceof WC_Email ? $email->id : null,
				'args_count' => count( $args ),
			)
		);

		if ( $email instanceof WC_Email ) {
			self::capture_email_context( $email );
			Mailer::force_next_override();

			$profile = Settings::resolve_profile_for_email(
				$email->id ?? null,
				array(
					'roles'   => Settings::current_user_roles(),
					'site_id' => is_multisite() ? get_current_blog_id() : 0,
				)
			);

			Mailer::set_retry_context(
				array(
					'smtp_profile' => $profile,
				)
			);
			self::debug_log( 'Mailer retry context seeded', array( 'profile' => $profile ) );
		}

		$result = call_user_func_array( $callback, $args );

		self::debug_log(
			'send_mail_callback finished',
			array(
				'result' => is_bool( $result ) ? ( $result ? 'true' : 'false' ) : gettype( $result ),
			)
		);
		Mailer::trace_maildbg(
			'wc.send_mail_callback_finish',
			array(
				'result' => is_bool( $result ) ? ( $result ? 'true' : 'false' ) : gettype( $result ),
			)
		);

		if ( $result instanceof WP_Error ) {
			return false;
		}

		return $result;
	}
}
