<?php

namespace WarrantyCart\Notifications;

use Dompdf\Dompdf;
use WarrantyCart\Core\ClaimMessages;
use WarrantyCart\Core\Claims;
use WarrantyCart\Frontend\MyAccount;
use WarrantyCart\Notifications\Emails\OrderCompletion;
use WarrantyCart\Notifications\Emails\WarrantyExpired;
use WarrantyCart\Notifications\Emails\WarrantyReminder;
use WC_Email;
use WC_Emails;
use WC_Order;

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

/**
 * Warranty email.
 *
 * Registers this plugin's three emails with WooCommerce, and decides whether
 * each one goes out — the plugin-wide switch, WooCommerce's own per-email
 * toggle, and whether the order in question carries a warranty at all.
 */
class Mailer {

	/**
	 * Constructor
	 */
	public function __construct() {
		add_filter( 'woocommerce_email_classes', array( $this, 'register_emails' ) );
		add_action( 'woocommerce_order_status_completed', array( $this, 'send_order_completion_notification' ), 10, 2 );
		add_action( 'init', array( $this, 'load_pdf_library' ) );
		add_action( 'warranty_cart_claim_status_changed', array( $this, 'send_claim_status_notification' ), 10, 2 );
		add_action( 'warranty_cart_claim_message_added', array( $this, 'send_claim_message_notification' ), 10, 3 );
	}

	/**
	 * Register warranty emails with WooCommerce
	 *
	 * @param array<string, WC_Email> $emails Existing emails.
	 *
	 * @return array<string, WC_Email>
	 */
	public function register_emails( array $emails ): array {
		$email_classes = array(
			OrderCompletion::class,
			WarrantyReminder::class,
			WarrantyExpired::class,
		);

		$email_classes = apply_filters( 'warranty_cart_email_classes', $email_classes );

		foreach ( $email_classes as $email_class ) {
			$email = new $email_class();

			if ( ! $email instanceof WC_Email ) {
				continue;
			}

			// Register to wc email lists
			$emails[ $email->id ] = $email;
		}

		/**
		 * Filter registered warranty emails.
		 *
		 * @param array<string, WC_Email> $emails        Registered emails.
		 * @param array                   $email_classes Email classes that were registered.
		 */
		return apply_filters( 'warranty_cart_registered_emails', $emails, $email_classes );
	}

	/**
	 * Send order completion notification
	 *
	 * @param int      $order_id Order ID.
	 * @param WC_Order $order    The Order instance.
	 */
	public function send_order_completion_notification( int $order_id, WC_Order $order ): void {
		// Check if order contains warranty products
		if ( ! $this->order_contains_warranties( $order ) ) {
			return;
		}

		if ( ! self::notifications_enabled() ) {
			return;
		}

		/**
		 * Action before sending order completion notification
		 *
		 * @param int      $order_id Order ID.
		 * @param WC_Order $order    Order object.
		 */
		do_action( 'warranty_cart_before_send_order_completion_notification', $order_id, $order );

		$wc_emails = new WC_Emails();
		$emails    = $wc_emails->get_emails();

		// Send warranty order completion email
		if ( isset( $emails['warranty_order_completed'] ) ) {
			$email_class = $emails['warranty_order_completed'];
			if ( ! $email_class instanceof OrderCompletion ) {
				return;
			}

			$email_class->trigger( $order_id );
		}

		// Log notification
		$this->log_notification( 'order_completed', $order_id );

		/**
		 * Action after sending order completion notification
		 *
		 * @param int      $order_id Order ID.
		 * @param WC_Order $order    Order object.
		 */
		do_action( 'warranty_cart_after_send_order_completion_notification', $order_id, $order );
	}

	/**
	 * Send warranty reminder email
	 *
	 * Sends a reminder email to customers about upcoming warranty expiration.
	 *
	 * @param int    $order_id        Order ID.
	 * @param string $expiration_date Expiration date.
	 * @param int    $days_remaining  Days until expiration.
	 */
	/**
	 * Whether the store wants warranty emails at all.
	 *
	 * The Settings screen has offered an "Email Notifications" switch since the
	 * first release and nothing read it: `warranty_enable_email_notifications`
	 * was written on save and consumed nowhere, so turning notifications off
	 * persisted, reloaded in the off position, and sent the emails anyway.
	 *
	 * Defaults to true, matching what Settings reports when the option
	 * has never been set — a store that never opened Settings keeps the
	 * behaviour it already had.
	 *
	 * @return bool
	 */
	private static function notifications_enabled(): bool {
		return (bool) \get_option( 'warranty_enable_email_notifications', true );
	}

	/**
	 * Tell a customer their claim has moved.
	 *
	 * A claim filed into silence is worse than no claim form at all: the
	 * customer has described a fault, been told nothing, and has no way to
	 * tell whether anyone read it. This fires on every status change, and
	 * carries the shop's own note when one has been written — that note is the
	 * answer they are actually waiting for.
	 *
	 * Nothing is sent for a change into `pending`, which is where a claim
	 * starts; the customer already saw that on screen when they filed it.
	 *
	 * @param int    $claim_id The claim.
	 * @param string $status   The new status.
	 *
	 * @return void
	 */
	public function send_claim_status_notification( int $claim_id, string $status ): void {
		if ( ! self::notifications_enabled() || 'pending' === $status ) {
			return;
		}

		$claim = Claims::get( $claim_id );

		if ( ! $claim ) {
			return;
		}

		/*
		 * Whoever filed it, account or not.
		 *
		 * A guest claim has no `customer_id` to look up, and returning here
		 * because of that would be the same silence this method exists to
		 * prevent — worse, in fact, since a guest has no account page to go and
		 * check instead.
		 */
		$user      = $claim->customer_id ? \get_userdata( (int) $claim->customer_id ) : false;
		$recipient = $user ? (string) $user->user_email : (string) ( $claim->guest_email ?? '' );
		$greeting  = $user
			? ( $user->first_name ? $user->first_name : $user->display_name )
			: \__( 'there', 'warranty-cart' );

		if ( ! \is_email( $recipient ) ) {
			return;
		}

		$statuses = Claims::statuses();
		$label    = $statuses[ $status ] ?? $status;
		$product  = \wc_get_product( (int) $claim->product_id );

		$subject = \sprintf(
			/* translators: 1: claim number, 2: new status. */
			\__( 'Your warranty claim #%1$d is now %2$s', 'warranty-cart' ),
			(int) $claim->id,
			$label
		);

		$lines = array(
			\sprintf(
				/* translators: %s: customer first name. */
				\__( 'Hello %s,', 'warranty-cart' ),
				$greeting
			),
			'',
			\sprintf(
				/* translators: 1: claim summary, 2: warranty name. */
				\__( 'Your claim "%1$s" against %2$s has been updated.', 'warranty-cart' ),
				(string) $claim->subject,
				$product ? $product->get_name() : \__( 'your warranty', 'warranty-cart' )
			),
			'',
			\sprintf(
				/* translators: %s: the new status. */
				\__( 'Status: %s', 'warranty-cart' ),
				$label
			),
		);

		if ( ! empty( $claim->resolution ) ) {
			$lines[] = '';
			$lines[] = (string) $claim->resolution;
		}

		/*
		 * An account link is no use to somebody who has no account. Theirs says
		 * how to reach the claim again — the order number and this address, the
		 * same pair they filed it with.
		 */
		$lines[] = '';

		if ( $user ) {
			$lines[] = \__( 'You can see every claim you have filed in your account:', 'warranty-cart' );
			$lines[] = \wc_get_account_endpoint_url( MyAccount::ENDPOINT );
		} else {
			$lines[] = self::guest_lookup_line( (int) $claim->order_id );
		}

		$body = implode( "\n", $lines );

		/**
		 * Filter the claim status email before it is sent.
		 *
		 * @param array  $mail  Subject, body and recipient.
		 * @param object $claim The claim.
		 */
		$mail = \apply_filters(
			'warranty_cart_claim_status_email',
			array(
				'to'      => $recipient,
				'subject' => $subject,
				'body'    => $body,
			),
			$claim
		);

		\wp_mail( $mail['to'], $mail['subject'], $mail['body'] );
	}

	/**
	 * Send a message the shop has written on a claim.
	 *
	 * A message the customer cannot see is worth nothing, and the account page
	 * is somewhere they have to think to go — so the message goes to them, and
	 * the page is where it stays.
	 *
	 * Internal notes are not sent anywhere. That is the entire difference
	 * between the two kinds, so getting it wrong would mail a customer the
	 * shop's private opinion of their claim.
	 *
	 * @param int    $message_id The message.
	 * @param object $claim      The claim it is on.
	 * @param string $visibility Who it is for.
	 *
	 * @return void
	 */
	public function send_claim_message_notification( int $message_id, object $claim, string $visibility ): void {
		if ( ! self::notifications_enabled() || ClaimMessages::TO_CUSTOMER !== $visibility ) {
			return;
		}

		global $wpdb;

		$table = ClaimMessages::table();

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery
		$message = $wpdb->get_row(
			$wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $message_id )
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery

		if ( ! $message ) {
			return;
		}

		$user      = $claim->customer_id ? \get_userdata( (int) $claim->customer_id ) : false;
		$recipient = $user ? (string) $user->user_email : (string) ( $claim->guest_email ?? '' );

		if ( ! \is_email( $recipient ) ) {
			return;
		}

		$subject = \sprintf(
			/* translators: %d: claim number. */
			\__( 'An update on your warranty claim #%d', 'warranty-cart' ),
			(int) $claim->id
		);

		$lines = array(
			\sprintf(
				/* translators: %s: customer first name. */
				\__( 'Hello %s,', 'warranty-cart' ),
				$user && $user->first_name ? $user->first_name : \__( 'there', 'warranty-cart' )
			),
			'',
			\sprintf(
				/* translators: %s: claim summary. */
				\__( 'About your claim "%s":', 'warranty-cart' ),
				(string) $claim->subject
			),
			'',
			(string) $message->body,
		);

		$lines[] = '';

		if ( $user ) {
			$lines[] = \__( 'Your claims are in your account:', 'warranty-cart' );
			$lines[] = \wc_get_account_endpoint_url( MyAccount::ENDPOINT );
		} else {
			$lines[] = self::guest_lookup_line( (int) $claim->order_id );
		}

		/**
		 * Filter the claim message email before it is sent.
		 *
		 * @param array  $mail    Subject, body and recipient.
		 * @param object $claim   The claim.
		 * @param object $message The message being sent.
		 */
		$mail = \apply_filters(
			'warranty_cart_claim_message_email',
			array(
				'to'      => $recipient,
				'subject' => $subject,
				'body'    => implode( "\n", $lines ),
			),
			$claim,
			$message
		);

		\wp_mail( $mail['to'], $mail['subject'], $mail['body'] );
	}

	/**
	 * Tell a guest how to reach their claim again.
	 *
	 * With the address of the page carrying `[warranty_claim]` when the shop has
	 * named one. Without it, this said "look it up on our warranty page" and
	 * left the customer to find a page the plugin itself could not name — which
	 * is the sort of instruction that reads as a brush-off.
	 *
	 * @param int $order_id The order the claim belongs to.
	 *
	 * @return string
	 */
	private static function guest_lookup_line( int $order_id ): string {
		$page = \WarrantyCart\Frontend\GuestClaim::page_url();

		if ( '' !== $page ) {
			return \sprintf(
				/* translators: 1: order number, 2: URL of the warranty lookup page. */
				\__( 'To see it again, look up order #%1$d with this email address at %2$s', 'warranty-cart' ),
				$order_id,
				$page
			);
		}

		return \sprintf(
			/* translators: %d: order number. */
			\__( 'To see it again, quote order #%d and this email address when you contact us.', 'warranty-cart' ),
			$order_id
		);
	}

	public function send_warranty_reminder( int $order_id, string $expiration_date = '', int $days_remaining = 30 ): void {
		if ( ! self::notifications_enabled() ) {
			return;
		}

		/**
		 * Action before sending warranty reminder
		 *
		 * @param int    $order_id        Order ID.
		 * @param string $expiration_date Expiration date.
		 * @param int    $days_remaining  Days until expiration.
		 */
		do_action( 'warranty_cart_before_send_warranty_reminder', $order_id, $expiration_date, $days_remaining );

		$wc_emails = new WC_Emails();
		$emails    = $wc_emails->get_emails();

		if ( isset( $emails['warranty_reminder'] ) ) {
			$email_class = $emails['warranty_reminder'];
			if ( ! $email_class instanceof WarrantyReminder ) {
				return;
			}

			$email_class->trigger( $order_id, $expiration_date, $days_remaining );
			$this->log_notification( 'warranty_reminder', $order_id );
		}

		/**
		 * Action after sending warranty reminder
		 *
		 * @param int    $order_id        Order ID.
		 * @param string $expiration_date Expiration date.
		 * @param int    $days_remaining  Days until expiration.
		 */
		do_action( 'warranty_cart_after_send_warranty_reminder', $order_id, $expiration_date, $days_remaining );
	}

	/**
	 * Send warranty expired email
	 *
	 * Sends a notification email to customers when their warranty has expired.
	 *
	 * @param int    $order_id        Order ID.
	 * @param string $expiration_date Expiration date.
	 */
	public function send_warranty_expired( int $order_id, string $expiration_date = '' ): void {
		// The other two senders check this; this one did not, so a store that
		// switched email notifications off still had expiry mail go out.
		if ( ! self::notifications_enabled() ) {
			return;
		}

		/**
		 * Action before sending warranty expired notification
		 *
		 * @param int    $order_id        Order ID.
		 * @param string $expiration_date Expiration date.
		 */
		do_action( 'warranty_cart_before_send_warranty_expired', $order_id, $expiration_date );

		$wc_emails = new WC_Emails();
		$emails    = $wc_emails->get_emails();

		if ( isset( $emails['warranty_expired'] ) ) {
			$email_class = $emails['warranty_expired'];
			if ( ! $email_class instanceof WarrantyExpired ) {
				return;
			}

			$email_class->trigger( $order_id, $expiration_date );
			$this->log_notification( 'warranty_expired', $order_id );
		}

		/**
		 * Action after sending warranty expired notification
		 *
		 * @param int    $order_id        Order ID.
		 * @param string $expiration_date Expiration date.
		 */
		do_action( 'warranty_cart_after_send_warranty_expired', $order_id, $expiration_date );
	}

	/**
	 * Check if order contains warranty products
	 *
	 * Determines whether an order includes any warranty products.
	 *
	 * @param WC_Order $order Order object.
	 *
	 * @return bool True if order contains warranties, false otherwise.
	 */
	private function order_contains_warranties( WC_Order $order ): bool {
		foreach ( $order->get_items() as $item ) {
			$product = $item->get_product();
			if ( $product && has_term( 'warranties', 'product_cat', $product->get_id() ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Log notification
	 *
	 * Records notification events for debugging and monitoring.
	 *
	 * @param string     $type         Event type (e.g., 'order_completed').
	 * @param int|string $reference_id Reference ID (usually order ID).
	 */
	private function log_notification( string $type, $reference_id ): void {
		warranty_cart()->log(
			"Email sent: {$type}",
			'info',
			array( 'reference_id' => $reference_id )
		);
	}

	/**
	 * Load PDF library
	 *
	 * Initializes the DomPDF library for PDF certificate generation.
	 */
	public function load_pdf_library(): void {
		if ( ! class_exists( Dompdf::class ) ) {
			require_once WARRANTY_CART_PATH . 'vendor/dompdf/dompdf/src/Dompdf.php';
		}
	}
}
