<?php
/**
 * REST controller for downloading and saving order invoices.
 *
 * @package PDFDraft_WooCommerce
 */

namespace PDFDraft\Woocommerce\Controllers;

use PDFDraft\Woocommerce\Core\OrderInvoiceService;
use PDFDraft\Woocommerce\Core\VirtualOrderEmailSuppressor;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

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

/**
 * REST controller for downloading order invoices.
 *
 * Route: GET /pdfdraft-pdf-invoices-for-woocommerce/v1/orders/(?P<id>\d+)/invoice
 */
class DownloadInvoiceController {

	/**
	 * REST API namespace.
	 *
	 * @var string
	 */
	private const NAMESPACE = 'pdfdraft-pdf-invoices-for-woocommerce/v1';

	/**
	 * Pixel size for product thumbnail images embedded in invoice line items.
	 *
	 * @var int
	 */
	private const INVOICE_ITEM_IMAGE_SIZE_PX = 48;

	/**
	 * Order meta key for the one-time checkout upload token.
	 *
	 * @var string
	 */
	private const META_UPLOAD_TOKEN = 'pdfdraft_checkout_upload_token';

	/**
	 * Order meta key for the session fingerprint bound to the upload token.
	 *
	 * @var string
	 */
	private const META_UPLOAD_FINGERPRINT      = 'pdfdraft_checkout_upload_fingerprint';
	private const META_INVOICE_NUMBER          = 'pdfdraft_invoice_number';
	private const OPTION_INVOICE_SEQUENCE_LAST = 'pdfdraft_pdf_invoices_for_woocommerce_invoice_sequence_last';

	/**
	 * Invoice service instance.
	 *
	 * @var OrderInvoiceService
	 */
	private $orderInvoiceService;

	/**
	 * Constructor.
	 *
	 * @param OrderInvoiceService $orderInvoiceService Invoice service instance.
	 */
	public function __construct( OrderInvoiceService $orderInvoiceService ) {
		$this->orderInvoiceService = $orderInvoiceService;
	}

	/**
	 * Build a session fingerprint from the current request's IP and User-Agent.
	 *
	 * @return string
	 */
	private function getSessionFingerprint(): string {
		$ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) );
		$ua = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ) );
		return wp_hash( $ip . '|' . $ua );
	}

	/**
	 * Resolve invoice number for merge tags using Woo settings.
	 *
	 * @param \WC_Order $order Order object.
	 * @return string
	 */
	private function resolveInvoiceNumber( \WC_Order $order ): string {
		$option_key = (string) apply_filters( 'pdfdraft_pdf_invoices_for_woocommerce_settings_option_key', 'pdfdraft_pdf_invoices_for_woocommerce_settings' );
		$settings   = get_option( $option_key, array() );
		$mode       = $settings['general']['invoice_settings']['invoice_number_mode'] ?? 'order_id';
		$prefix     = (string) ( $settings['general']['invoice_settings']['invoice_prefix'] ?? '' );
		$suffix     = (string) ( $settings['general']['invoice_settings']['invoice_suffix'] ?? '' );

		if ( 'sequential' !== $mode ) {
			$number = (string) $order->get_id();
		} else {
			$existing = (string) $order->get_meta( self::META_INVOICE_NUMBER, true );
			if ( '' !== $existing ) {
				// Already has a stored number — re-apply prefix/suffix so changes reflect immediately.
				return $prefix . $existing . $suffix;
			}

			$start = absint( $settings['general']['invoice_settings']['invoice_number_start'] ?? 1 );
			if ( $start < 1 ) {
				$start = 1;
			}
			$last   = absint( get_option( self::OPTION_INVOICE_SEQUENCE_LAST, $start - 1 ) );
			$next   = max( $start, $last + 1 );
			$number = (string) $next;

			$order->update_meta_data( self::META_INVOICE_NUMBER, $number );
			$order->save();
			update_option( self::OPTION_INVOICE_SEQUENCE_LAST, $next );
		}

		return $prefix . $number . $suffix;
	}

	/**
	 * Register REST API routes for invoice operations.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_item' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'id' => array(
						'description'       => __( 'Order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
						'type'              => 'integer',
						'required'          => true,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
						'validate_callback' => function ( $param ) {
							return absint( $param ) > 0;
						},
					),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'save_invoice' ),
				'permission_callback' => array( $this, 'check_save_permission' ),
				'args'                => array(
					'id'  => array(
						'description'       => __( 'Order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
						'type'              => 'integer',
						'required'          => true,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
						'validate_callback' => function ( $param ) {
							return absint( $param ) > 0;
						},
					),
					'pdf' => array(
						'description'       => __( 'Base64-encoded PDF data.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
						'type'              => 'string',
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return is_string( $param ) && strlen( $param ) > 0;
						},
					),
				),
			)
		);

		$order_id_arg = array(
			'description'       => __( 'Order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
			'type'              => 'integer',
			'required'          => true,
			'minimum'           => 1,
			'sanitize_callback' => 'absint',
			'validate_callback' => function ( $param ) {
				return absint( $param ) > 0;
			},
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice',
			array(
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'delete_invoice' ),
				'permission_callback' => array( $this, 'check_delete_permission' ),
				'args'                => array(
					'id' => $order_id_arg,
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice-context',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_invoice_context' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'id' => array(
						'description'       => __( 'Order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
						'type'              => 'integer',
						'required'          => true,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
						'validate_callback' => function ( $param ) {
							return absint( $param ) > 0;
						},
					),
				),
			)
		);

		$order_key_arg = array(
			'description'       => __( 'WooCommerce order key.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
			'type'              => 'string',
			'required'          => true,
			'sanitize_callback' => 'sanitize_text_field',
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice-context/checkout',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_checkout_invoice_context' ),
				'permission_callback' => array( $this, 'check_order_key_permission' ),
				'args'                => array(
					'id'        => array(
						'type'              => 'integer',
						'required'          => true,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
						'validate_callback' => fn( $p ) => absint( $p ) > 0,
					),
					'order_key' => $order_key_arg,
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice/checkout',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'save_checkout_invoice' ),
				'permission_callback' => array( $this, 'check_order_key_permission' ),
				'args'                => array(
					'id'           => array(
						'type'              => 'integer',
						'required'          => true,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
						'validate_callback' => fn( $p ) => absint( $p ) > 0,
					),
					'order_key'    => $order_key_arg,
					'pdf'          => array(
						'type'              => 'string',
						'required'          => true,
						'validate_callback' => fn( $p ) => is_string( $p ) && '' !== $p,
					),
					'upload_token' => array(
						'description'       => __( 'One-time upload token from invoice context.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice-design',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_checkout_invoice_design' ),
				'permission_callback' => array( $this, 'check_order_key_permission' ),
				'args'                => array(
					'id'        => array(
						'type'              => 'integer',
						'required'          => true,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
						'validate_callback' => fn( $p ) => absint( $p ) > 0,
					),
					'order_key' => array(
						'description'       => __( 'WooCommerce order key. Optional for admin users.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
						'type'              => 'string',
						'required'          => false,
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/send-invoice-email',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'send_invoice_email' ),
				'permission_callback' => array( $this, 'check_order_key_permission' ),
				'args'                => array(
					'id'        => array(
						'type'              => 'integer',
						'required'          => true,
						'minimum'           => 1,
						'sanitize_callback' => 'absint',
						'validate_callback' => fn( $p ) => absint( $p ) > 0,
					),
					'order_key' => $order_key_arg,
				),
			)
		);

		// Customer-facing endpoints (authenticated via logged-in user ownership).
		$customer_id_arg = array(
			'description'       => __( 'Order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
			'type'              => 'integer',
			'required'          => true,
			'minimum'           => 1,
			'sanitize_callback' => 'absint',
			'validate_callback' => fn( $p ) => absint( $p ) > 0,
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice-context/customer',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_invoice_context' ),
				'permission_callback' => array( $this, 'check_customer_permission' ),
				'args'                => array( 'id' => $customer_id_arg ),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice/customer',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_item' ),
				'permission_callback' => array( $this, 'check_customer_permission' ),
				'args'                => array( 'id' => $customer_id_arg ),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice/customer',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'save_invoice' ),
				'permission_callback' => array( $this, 'check_customer_permission' ),
				'args'                => array(
					'id'  => $customer_id_arg,
					'pdf' => array(
						'description'       => __( 'Base64-encoded PDF data.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
						'type'              => 'string',
						'required'          => true,
						'validate_callback' => fn( $p ) => is_string( $p ) && '' !== $p,
					),
				),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/orders/(?P<id>\d+)/invoice-design/customer',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_checkout_invoice_design' ),
				'permission_callback' => array( $this, 'check_customer_permission' ),
				'args'                => array( 'id' => $customer_id_arg ),
			)
		);
	}

	/**
	 * Check permission using the WooCommerce order key.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return bool
	 */
	public function check_order_key_permission( WP_REST_Request $request ): bool {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return false;
		}
		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return false;
		}
		$order_key = sanitize_text_field( $request->get_param( 'order_key' ) ?? '' );
		if ( $order_key && hash_equals( $order->get_order_key(), $order_key ) ) {
			return true;
		}
		return current_user_can( 'manage_woocommerce' ) || current_user_can( 'manage_options' ); // phpcs:ignore WordPress.WP.Capabilities.Unknown
	}

	/**
	 * Retrieve invoice context for checkout with an upload token.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_checkout_invoice_context( WP_REST_Request $request ) {
		$order_id = absint( $request['id'] );
		$order    = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return new WP_Error( 'rest_order_not_found', __( 'Order not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 404 ) );
		}

		$context = $this->get_invoice_context( $request );
		if ( is_wp_error( $context ) ) {
			return $context;
		}

		$data                 = $context->get_data();
		$data['emailPending'] = VirtualOrderEmailSuppressor::isEmailPending( $order_id );

		// Generate a one-time upload token bound to this session's fingerprint
		// so only the same browser/IP can use it.
		$upload_token = wp_generate_password( 32, false );
		$order->update_meta_data( self::META_UPLOAD_TOKEN, wp_hash( $upload_token ) );
		$order->update_meta_data( self::META_UPLOAD_FINGERPRINT, $this->getSessionFingerprint() );
		$order->save();
		$data['uploadToken'] = $upload_token;

		return new WP_REST_Response( $data, 200 );
	}

	/**
	 * Save an invoice PDF uploaded from the checkout page.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function save_checkout_invoice( WP_REST_Request $request ) {
		// Rate-limit checkout uploads: max 5 attempts per IP per 10-minute window.
		$ip            = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? 'unknown' ) );
		$transient_key = 'pdfdraft_upload_rate_' . md5( $ip );
		$attempts      = (int) get_transient( $transient_key );
		if ( $attempts >= 5 ) {
			return new WP_Error( 'rest_rate_limited', __( 'Too many upload attempts. Please try again later.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 429 ) );
		}
		set_transient( $transient_key, $attempts + 1, 10 * MINUTE_IN_SECONDS );

		$order_id = absint( $request['id'] );
		$order    = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return new WP_Error( 'rest_order_not_found', __( 'Order not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 404 ) );
		}

		if ( ! VirtualOrderEmailSuppressor::isEmailPending( $order_id ) ) {
			return new WP_Error( 'rest_invoice_not_pending', __( 'Invoice upload is no longer accepted for this order.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 403 ) );
		}

		// Skip auto-generation for free orders if the setting is enabled.
		$option_key   = (string) apply_filters( 'pdfdraft_pdf_invoices_for_woocommerce_settings_option_key', 'pdfdraft_pdf_invoices_for_woocommerce_settings' );
		$settings     = get_option( $option_key, array() );
		$disable_free = (bool) ( $settings['general']['invoice_settings']['disable_free_order_invoices'] ?? false );
		if ( $disable_free && (float) $order->get_total() <= 0 ) {
			VirtualOrderEmailSuppressor::clearEmailPending( $order_id );
			return new WP_REST_Response(
				array(
					'success' => true,
					'skipped' => true,
					'orderId' => $order_id,
				),
				200
			);
		}

		// Validate and consume the one-time upload token.
		$upload_token = sanitize_text_field( $request->get_param( 'upload_token' ) ?? '' );
		$stored_hash  = $order->get_meta( self::META_UPLOAD_TOKEN );
		if ( '' === $stored_hash || ! hash_equals( $stored_hash, wp_hash( $upload_token ) ) ) {
			return new WP_Error( 'rest_invalid_upload_token', __( 'Invalid or expired upload token.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 403 ) );
		}
		// Verify the upload is coming from the same session (IP + User-Agent) that fetched the context.
		$stored_fingerprint = $order->get_meta( self::META_UPLOAD_FINGERPRINT );
		if ( '' === $stored_fingerprint || ! hash_equals( $stored_fingerprint, $this->getSessionFingerprint() ) ) {
			return new WP_Error( 'rest_session_mismatch', __( 'Upload session does not match the original request.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 403 ) );
		}
		// Consume: delete the token and fingerprint so they cannot be reused.
		$order->delete_meta_data( self::META_UPLOAD_TOKEN );
		$order->delete_meta_data( self::META_UPLOAD_FINGERPRINT );
		$order->save();

		$base64 = $request->get_param( 'pdf' );
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		$pdfData = base64_decode( $base64, true );
		if ( false === $pdfData ) {
			return new WP_Error( 'rest_invalid_base64', __( 'PDF data is not valid base64.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 400 ) );
		}

		$result = $this->orderInvoiceService->saveClientPdf( $order, $pdfData );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		return new WP_REST_Response(
			array(
				'success' => true,
				'orderId' => $order_id,
			),
			200
		);
	}

	/**
	 * Send the pending invoice email for an order.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function send_invoice_email( WP_REST_Request $request ) {
		$order_id = absint( $request['id'] );
		$order    = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return new WP_Error( 'rest_order_not_found', __( 'Order not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 404 ) );
		}

		if ( ! VirtualOrderEmailSuppressor::isEmailPending( $order_id ) ) {
			return new WP_REST_Response(
				array(
					'success' => true,
					'skipped' => true,
				),
				200
			);
		}

		VirtualOrderEmailSuppressor::clearEmailPending( $order_id );
		$this->orderInvoiceService->sendCompletionEmail( $order_id );

		return new WP_REST_Response( array( 'success' => true ), 200 );
	}

	/**
	 * Retrieve the active invoice design content.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_checkout_invoice_design( WP_REST_Request $request ) {
		$order_id = absint( $request['id'] );
		$order    = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return new WP_Error( 'rest_order_not_found', __( 'Order not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 404 ) );
		}

		$slug = get_option( 'pdfdraft_pdf_invoices_for_woocommerce_active_design_slug', '' );
		if ( empty( $slug ) ) {
			return new WP_Error( 'rest_no_design', __( 'No active invoice design configured.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 404 ) );
		}

		$query = new \WP_Query(
			array(
				'name'           => sanitize_title( $slug ),
				'post_type'      => 'pdfdraft-design',
				'posts_per_page' => 1,
				'post_status'    => array( 'publish', 'draft' ),
			)
		);

		$post = $query->posts[0] ?? null;
		if ( ! $post instanceof \WP_Post ) {
			return new WP_Error( 'rest_design_not_found', __( 'Invoice design not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 404 ) );
		}

		$content = json_decode( $post->post_content );
		if ( ! $content ) {
			return new WP_Error( 'rest_design_invalid', __( 'Invoice design content is invalid.', 'pdfdraft-pdf-invoices-for-woocommerce' ), array( 'status' => 500 ) );
		}

		return new WP_REST_Response( array( 'content' => $content ), 200 );
	}

	/**
	 * Check whether the logged-in user is the order's customer or an admin.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return bool
	 */
	public function check_customer_permission( WP_REST_Request $request ): bool {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return false;
		}
		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return false;
		}
		if ( current_user_can( 'manage_woocommerce' ) || current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown
			return true;
		}
		$current_user_id = get_current_user_id();
		return $current_user_id > 0 && $current_user_id === $order->get_customer_id();
	}

	/**
	 * Check whether the current user can access the invoice.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return bool
	 */
	public function check_permission( WP_REST_Request $request ): bool {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return false;
		}
		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return false;
		}
		$capability = apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_invoice_download_capability',
			'manage_woocommerce',
			$order,
			$request
		);
		return current_user_can( $capability ) || current_user_can( 'manage_options' );
	}

	/**
	 * Download the invoice PDF for an order.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_item( WP_REST_Request $request ) {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return new WP_Error(
				'rest_invalid_order_id',
				__( 'Invalid order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 400 )
			);
		}

		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return new WP_Error(
				'rest_order_not_found',
				__( 'Order not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 404 )
			);
		}

		do_action( 'pdfdraft_pdf_invoices_for_woocommerce_before_serve_invoice', $order, $request );

		$result = $this->orderInvoiceService->downloadForOrder( $order_id );

		do_action( 'pdfdraft_pdf_invoices_for_woocommerce_after_serve_invoice', $order, $request );

		return $result;
	}

	/**
	 * Check whether the current user can save invoices.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return bool
	 */
	public function check_save_permission( WP_REST_Request $request ): bool {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return false;
		}
		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return false;
		}
		$capability = apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_invoice_save_capability',
			'manage_woocommerce',
			$order,
			$request
		);
		return current_user_can( $capability ) || current_user_can( 'manage_options' );
	}

	/**
	 * Check whether the current user can delete stored invoices.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return bool
	 */
	public function check_delete_permission( WP_REST_Request $request ): bool {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return false;
		}
		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return false;
		}
		$capability = apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_invoice_delete_capability',
			'manage_woocommerce',
			$order,
			$request
		);
		return current_user_can( $capability ) || current_user_can( 'manage_options' );
	}

	/**
	 * Save an invoice PDF for an order via the admin REST endpoint.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function save_invoice( WP_REST_Request $request ) {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return new WP_Error(
				'rest_invalid_order_id',
				__( 'Invalid order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 400 )
			);
		}

		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return new WP_Error(
				'rest_order_not_found',
				__( 'Order not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 404 )
			);
		}

		$base64 = $request->get_param( 'pdf' );
		if ( ! is_string( $base64 ) || '' === $base64 ) {
			return new WP_Error(
				'rest_missing_pdf',
				__( 'PDF data is required.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 400 )
			);
		}

		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		$pdfData = base64_decode( $base64, true );
		if ( false === $pdfData ) {
			return new WP_Error(
				'rest_invalid_base64',
				__( 'PDF data is not valid base64.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 400 )
			);
		}

		do_action( 'pdfdraft_pdf_invoices_for_woocommerce_before_save_invoice', $order, $request );

		$result = $this->orderInvoiceService->saveClientPdf( $order, $pdfData );

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		do_action( 'pdfdraft_pdf_invoices_for_woocommerce_after_save_invoice', $order, $result, $request );

		return new WP_REST_Response(
			array(
				'success' => true,
				'orderId' => $order->get_id(),
				'saved'   => true,
			),
			200
		);
	}

	/**
	 * Remove the stored invoice file and order meta for an order.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function delete_invoice( WP_REST_Request $request ) {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return new WP_Error(
				'rest_invalid_order_id',
				__( 'Invalid order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 400 )
			);
		}

		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return new WP_Error(
				'rest_order_not_found',
				__( 'Order not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 404 )
			);
		}

		$is_gen           = $order->get_meta( 'pdfdraft_is_invoice_generated' );
		$had_invoice_meta = ( 'true' === $is_gen || true === $is_gen );
		$had_path         = '' !== (string) $order->get_meta( 'pdfdraft_invoice_path', true );

		if ( ! $had_invoice_meta && ! $had_path ) {
			return new WP_Error(
				'rest_invoice_nothing_to_delete',
				__( 'There is no stored invoice for this order.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 404 )
			);
		}

		do_action( 'pdfdraft_pdf_invoices_for_woocommerce_before_delete_invoice', $order, $request );

		$this->orderInvoiceService->removeInvoiceForOrder( $order );

		do_action( 'pdfdraft_pdf_invoices_for_woocommerce_after_delete_invoice', $order, $request );

		return new WP_REST_Response(
			array(
				'success' => true,
				'deleted' => true,
			),
			200
		);
	}

	/**
	 * Format an address as plain text lines.
	 *
	 * @param array<string, mixed> $address Address parts.
	 * @param string               $context Optional context label (billing|shipping|store|...).
	 * @return string
	 */
	private function formatAddress( array $address, string $context = '' ): string {
		if ( function_exists( 'WC' ) && WC() && isset( WC()->countries ) && WC()->countries ) {
			$formatted = (string) WC()->countries->get_formatted_address( $address );
			$formatted = str_replace( array( '<br/>', '<br />', '<br>' ), "\n", $formatted );
			$formatted = trim( wp_strip_all_tags( $formatted ) );
			return (string) apply_filters(
				'pdfdraft_pdf_invoices_for_woocommerce_format_address',
				$formatted,
				$address,
				$context
			);
		}

		$formatted = implode(
			"\n",
			array_values(
				array_filter(
					array(
						(string) ( $address['address_1'] ?? '' ),
						(string) ( $address['address_2'] ?? '' ),
						trim(
							implode(
								', ',
								array_values(
									array_filter(
										array(
											(string) ( $address['city'] ?? '' ),
											(string) ( $address['state'] ?? '' ),
											(string) ( $address['postcode'] ?? '' ),
										),
										static function ( $value ) {
											return '' !== trim( (string) $value );
										}
									)
								)
							)
						),
						(string) ( $address['country'] ?? '' ),
					),
					static function ( $value ) {
						return '' !== trim( (string) $value );
					}
				)
			)
		);

		return (string) apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_format_address',
			$formatted,
			$address,
			$context
		);
	}

	/**
	 * Build store address string from WooCommerce options.
	 *
	 * @return string
	 */
	private function getStoreAddressString(): string {
		$store_country_state = (string) get_option( 'woocommerce_default_country', '' );
		$store_country       = '';
		$store_state         = '';
		if ( '' !== $store_country_state ) {
			$store_country_parts = explode( ':', $store_country_state, 2 );
			$store_country       = $store_country_parts[0] ?? '';
			$store_state         = $store_country_parts[1] ?? '';
		}

		$store_address = array(
			'address_1' => (string) get_option( 'woocommerce_store_address', '' ),
			'address_2' => (string) get_option( 'woocommerce_store_address_2', '' ),
			'city'      => (string) get_option( 'woocommerce_store_city', '' ),
			'state'     => $store_state,
			'postcode'  => (string) get_option( 'woocommerce_store_postcode', '' ),
			'country'   => $store_country,
		);

		$formatted = $this->formatAddress( $store_address, 'store' );
		return (string) apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_store_address_string',
			$formatted,
			$store_address
		);
	}

	/**
	 * Build order item merge tags for invoice export.
	 *
	 * @param \WC_Order $order WooCommerce order.
	 * @return array<int, array<string, string>>
	 */
	private function buildOrderItemsMergeTags( \WC_Order $order ): array {
		$order_items = array();
		foreach ( $order->get_items() as $item ) {
			/**
			 * Order line item product.
			 *
			 * @var \WC_Order_Item_Product $item
			 */
			$product    = $item->get_product();
			$unit_price = $product
				? wc_price( (float) $order->get_item_subtotal( $item, false, false ), array( 'currency' => $order->get_currency() ) )
				: '';

			$product_image = '';
			if ( $product && $product->get_image_id() ) {
				$image_url = wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' );
				if ( $image_url ) {
					$size          = self::INVOICE_ITEM_IMAGE_SIZE_PX;
					$product_image = '<img src="' . esc_attr( $image_url ) . '" width="' . $size . '" height="' . $size . '" style="width:' . $size . 'px;height:' . $size . 'px;object-fit:cover;display:block;" />';
				}
			}

			$item_merge_tags = array(
				'product_name'   => $item->get_name(),
				'sku'            => $product ? $product->get_sku() : '',
				'quantity'       => (string) $item->get_quantity(),
				'unit_price'     => $unit_price,
				'line_subtotal'  => wc_price( (float) $item->get_subtotal(), array( 'currency' => $order->get_currency() ) ),
				'line_total'     => wc_price( (float) $item->get_total(), array( 'currency' => $order->get_currency() ) ),
				'product_meta'   => wc_get_formatted_variation( $product ?: array(), true ),
				'variation_data' => $product instanceof \WC_Product_Variation
					? wc_get_formatted_variation( $product, true )
					: '',
				'product_image'  => $product_image,
			);

			$order_items[] = (array) apply_filters(
				'pdfdraft_pdf_invoices_for_woocommerce_invoice_order_item_merge_tags',
				$item_merge_tags,
				$item,
				$order
			);
		}

		return (array) apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_invoice_order_items_merge_tags',
			$order_items,
			$order
		);
	}

	/**
	 * Build invoice merge tags.
	 *
	 * @param \WC_Order $order WooCommerce order.
	 * @return array<string, mixed>
	 */
	private function buildInvoiceMergeTags( \WC_Order $order ): array {
		$billing  = $order->get_address( 'billing' );
		$shipping = $order->get_address( 'shipping' );

		$merge_tags = array(
			'order_id'              => (string) $order->get_id(),
			'order_number'          => (string) $order->get_order_number(),
			'invoice_number'        => $this->resolveInvoiceNumber( $order ),
			'order_date'            => $order->get_date_created() ? $order->get_date_created()->date_i18n( get_option( 'date_format' ) ) : '',
			'order_status'          => wc_get_order_status_name( $order->get_status() ),
			'transaction_id'        => $order->get_transaction_id(),
			'payment_method'        => $order->get_payment_method_title(),
			'shipping_method'       => implode( ', ', array_map( fn( $item ) => $item->get_name(), $order->get_shipping_methods() ) ),
			'currency'              => $order->get_currency(),
			'currency_symbol'       => get_woocommerce_currency_symbol( $order->get_currency() ),
			'subtotal'              => $order->get_subtotal(),
			'total_tax'             => $order->get_total_tax(),
			'shipping_total'        => $order->get_shipping_total(),
			'total_discount'        => $order->get_discount_total(),
			'grand_total'           => $order->get_total(),
			'total_refunded'        => $order->get_total_refunded(),
			'order_total_due'       => $order->get_total() - $order->get_total_refunded(),
			'customer_id'           => (string) $order->get_customer_id(),
			'customer_note'         => (string) $order->get_customer_note(),
			'billing_first_name'    => $billing['first_name'] ?? '',
			'billing_last_name'     => $billing['last_name'] ?? '',
			'billing_company'       => $billing['company'] ?? '',
			'billing_email'         => $billing['email'] ?? '',
			'billing_phone'         => $billing['phone'] ?? '',
			'billing_address_1'     => $billing['address_1'] ?? '',
			'billing_address_2'     => $billing['address_2'] ?? '',
			'billing_city'          => $billing['city'] ?? '',
			'billing_state'         => $billing['state'] ?? '',
			'billing_postcode'      => $billing['postcode'] ?? '',
			'billing_country'       => $billing['country'] ?? '',
			'billing_address_full'  => $this->formatAddress( $billing, 'billing' ),
			'shipping_first_name'   => $shipping['first_name'] ?? '',
			'shipping_last_name'    => $shipping['last_name'] ?? '',
			'shipping_company'      => $shipping['company'] ?? '',
			'shipping_address_1'    => $shipping['address_1'] ?? '',
			'shipping_address_2'    => $shipping['address_2'] ?? '',
			'shipping_city'         => $shipping['city'] ?? '',
			'shipping_state'        => $shipping['state'] ?? '',
			'shipping_postcode'     => $shipping['postcode'] ?? '',
			'shipping_country'      => $shipping['country'] ?? '',
			'shipping_address_full' => $this->formatAddress( $shipping, 'shipping' ),
			'store_name'            => get_bloginfo( 'name' ),
			'store_phone'           => get_option( 'woocommerce_store_phone', '' ),
			'store_email'           => get_option( 'woocommerce_store_email', get_option( 'admin_email', '' ) ),
		);

		$merge_tags['store_address'] = $this->getStoreAddressString();
		$merge_tags['store_logo']    = $this->getStoreLogoUrl();
		$merge_tags['order_items']   = $this->buildOrderItemsMergeTags( $order );

		return (array) apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_invoice_merge_tag_context',
			$merge_tags,
			$order
		);
	}

	/**
	 * Resolve store logo URL.
	 *
	 * @return string
	 */
	private function getStoreLogoUrl(): string {
		$custom_logo_id = (int) get_theme_mod( 'custom_logo' );
		if ( $custom_logo_id <= 0 ) {
			return (string) apply_filters(
				'pdfdraft_pdf_invoices_for_woocommerce_store_logo_url',
				'',
				$custom_logo_id
			);
		}

		$url = (string) wp_get_attachment_image_url( $custom_logo_id, 'full' );
		return (string) apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_store_logo_url',
			$url,
			$custom_logo_id
		);
	}

	/**
	 * Resolve configured invoice generation mode.
	 *
	 * @return string
	 */
	private function getGenerationMode(): string {
		$option_key      = (string) apply_filters( 'pdfdraft_pdf_invoices_for_woocommerce_settings_option_key', 'pdfdraft_pdf_invoices_for_woocommerce_settings' );
		$saved_settings  = get_option( $option_key, array() );
		$generation_mode = $saved_settings['general']['invoice_settings']['generation_mode'] ?? 'automatic';
		if ( ! in_array( $generation_mode, array( 'automatic', 'manual' ), true ) ) {
			$generation_mode = 'automatic';
		}

		$generation_mode = (string) apply_filters(
			'pdfdraft_pdf_invoices_for_woocommerce_invoice_generation_mode',
			$generation_mode,
			$saved_settings
		);
		if ( ! in_array( $generation_mode, array( 'automatic', 'manual' ), true ) ) {
			return 'automatic';
		}

		return $generation_mode;
	}

	/**
	 * Retrieve the invoice template context (merge tags) for an order.
	 *
	 * @param WP_REST_Request $request REST request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_invoice_context( WP_REST_Request $request ) {
		$order_id = absint( $request['id'] );
		if ( ! $order_id ) {
			return new WP_Error(
				'rest_invalid_order_id',
				__( 'Invalid order ID.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 400 )
			);
		}

		$order = wc_get_order( $order_id );
		if ( ! $order instanceof \WC_Order ) {
			return new WP_Error(
				'rest_order_not_found',
				__( 'Order not found.', 'pdfdraft-pdf-invoices-for-woocommerce' ),
				array( 'status' => 404 )
			);
		}

		$design_slug     = get_option( 'pdfdraft_pdf_invoices_for_woocommerce_active_design_slug', '' );
		$merge_tags      = $this->buildInvoiceMergeTags( $order );
		$is_generated    = $this->orderInvoiceService->isGenerated( $order->get_id() );
		$generation_mode = $this->getGenerationMode();

		return new WP_REST_Response(
			array(
				'orderId'        => $order->get_id(),
				'designSlug'     => $design_slug,
				'mergeTags'      => $merge_tags,
				'isGenerated'    => $is_generated,
				'generationMode' => $generation_mode,
			)
		);
	}
}
