<?php
/**
 * Order admin surface.
 *
 * @package Harbor_EU_EInvoicing_For_WooCommerce
 */

defined( 'ABSPATH' ) || exit;

class HEIN_Order_Actions {

	public static function init(): void {
		add_action( 'add_meta_boxes', [ __CLASS__, 'add_metabox' ] );
		add_action( 'woocommerce_process_shop_order_meta', [ __CLASS__, 'save_order_meta' ], 10, 2 );
		add_action( 'admin_post_hein_generate_invoice', [ __CLASS__, 'handle_generate' ] );
		add_action( 'admin_post_hein_download_invoice', [ __CLASS__, 'handle_download' ] );

		// An admin_post action with no nopriv counterpart returns a blank 200 to a logged-out user
		add_action( 'admin_post_nopriv_hein_generate_invoice', [ __CLASS__, 'deny' ] );
		add_action( 'admin_post_nopriv_hein_download_invoice', [ __CLASS__, 'deny' ] );
	}

	public static function add_metabox(): void {
		$screens = [ 'shop_order' ];

		if ( function_exists( 'wc_get_page_screen_id' ) ) {
			$screens[] = wc_get_page_screen_id( 'shop-order' );
		}

		add_meta_box(
			'hein-invoice',
			__( 'E-invoice', 'harbor-eu-einvoicing-for-woocommerce' ),
			[ __CLASS__, 'render_metabox' ],
			array_values( array_unique( array_filter( $screens ) ) ),
			'side',
			'default'
		);
	}

	public static function render_metabox( $post_or_order ): void {
		$hein_order = $post_or_order instanceof WC_Order ? $post_or_order : wc_get_order( $post_or_order );

		// The Add-new-order screen would otherwise build order_id=0 links
		if ( ! $hein_order instanceof WC_Order || ! $hein_order->get_id() ) {
			return;
		}

		$hein_id      = $hein_order->get_id();
		$hein_formats = HEIN_Generator_Registry::enabled();

		// Only formats this build can actually produce. A store that lapses from premium keeps its rows
		// in the table, and listing them would print a paid format's name in a build that has no
		// generator for it, which is exactly what wp.org Guideline 5 forbids
		$hein_known  = HEIN_Generator_Registry::all();
		$hein_stored = array_values(
			array_filter(
				HEIN_Invoice_Store::list_for_order( $hein_id ),
				static function ( array $row ) use ( $hein_known ): bool {
					return isset( $hein_known[ (string) $row['format'] ] );
				}
			)
		);

		$hein_notice   = self::notice( $hein_id );
		$hein_number   = trim( (string) $hein_order->get_meta( '_hein_invoice_number' ) );
		$hein_refunded = (float) $hein_order->get_total_refunded();
		$hein_profile  = (string) HEIN_Admin_Settings::get( 'profile', 'peppol' );

		require HEIN_PATH . 'templates/order-metabox.php';
	}

	/**
	 * This hook fires on BOTH the HPOS and the legacy edit screen, and is the only thing that
	 * persists an admin's VAT or PO correction
	 */
	public static function save_order_meta( int $order_id, $post_or_order ): void {
		$nonce = isset( $_POST['hein_order_meta_nonce'] )
			? sanitize_text_field( wp_unslash( $_POST['hein_order_meta_nonce'] ) )
			: '';

		if ( ! self::can_manage( $order_id ) || ! wp_verify_nonce( $nonce, 'hein_order_meta_' . $order_id ) ) {
			return;
		}

		$order = $post_or_order instanceof WC_Order ? $post_or_order : wc_get_order( $order_id );

		if ( ! $order instanceof WC_Order ) {
			return;
		}

		HEIN_Checkout_Fields::save(
			$order,
			isset( $_POST['hein_vat_number'] ) ? sanitize_text_field( wp_unslash( $_POST['hein_vat_number'] ) ) : null,
			isset( $_POST['hein_po_reference'] ) ? sanitize_text_field( wp_unslash( $_POST['hein_po_reference'] ) ) : null
		);

		$order->save();

		do_action( 'hein_save_order_fields', $order );
	}

	public static function handle_generate(): void {
		$order_id = isset( $_GET['order_id'] ) ? absint( wp_unslash( $_GET['order_id'] ) ) : 0;
		$format   = isset( $_GET['format'] ) ? sanitize_key( wp_unslash( $_GET['format'] ) ) : '';
		$nonce    = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
		$order    = $order_id ? wc_get_order( $order_id ) : null;

		if (
			! $order instanceof WC_Order
			|| ! self::can_manage( $order_id )
			|| ! wp_verify_nonce( $nonce, 'hein_generate_invoice_' . $order_id )
		) {
			self::deny();
		}

		try {
			$row = HEIN_Invoice_Service::generate( $order, $format );

			set_transient(
				self::notice_key( $order_id ),
				[ 'success', $format, (string) $row['invoice_number'] ],
				MINUTE_IN_SECONDS
			);
		} catch ( HEIN_Invoice_Exception $e ) {
			set_transient(
				self::notice_key( $order_id ),
				[ 'error', $e->error_code(), $e->getMessage() ],
				MINUTE_IN_SECONDS
			);
		}

		wp_safe_redirect( self::order_url( $order_id ) );
		exit;
	}

	/**
	 * Streams the canonical bytes from the database, never a redirect to the uploads URL, so the
	 * file's public reachability is irrelevant to the feature working
	 */
	public static function handle_download(): void {
		$order_id = isset( $_GET['order_id'] ) ? absint( wp_unslash( $_GET['order_id'] ) ) : 0;
		$format   = isset( $_GET['format'] ) ? sanitize_key( wp_unslash( $_GET['format'] ) ) : '';
		$nonce    = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';

		// Per-order, so a leaked nonce cannot be replayed against another order
		if ( ! $order_id || ! self::can_manage( $order_id ) || ! wp_verify_nonce( $nonce, 'hein_download_invoice_' . $order_id ) ) {
			self::deny();
		}

		$row = HEIN_Invoice_Store::get( $order_id, $format );

		if ( null === $row ) {
			self::deny();
		}

		$content = (string) $row['content'];

		nocache_headers();
		header( 'Content-Type: ' . $row['mime'] );
		header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( (string) $row['filename'] ) . '"' );
		header( 'Content-Length: ' . strlen( $content ) );
		header( 'X-Content-Type-Options: nosniff' );

		echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		exit;
	}

	public static function deny(): void {
		wp_die(
			esc_html__( 'You are not allowed to do that.', 'harbor-eu-einvoicing-for-woocommerce' ),
			esc_html__( 'Forbidden', 'harbor-eu-einvoicing-for-woocommerce' ),
			[ 'response' => 403 ]
		);
	}

	public static function generate_url( int $order_id, string $format ): string {
		return self::action_url( 'hein_generate_invoice', $order_id, $format );
	}

	public static function download_url( int $order_id, string $format ): string {
		return self::action_url( 'hein_download_invoice', $order_id, $format );
	}

	/**
	 * ONE check on the WooCommerce meta capability: a '|| manage_woocommerce' fallback would let
	 * every shop manager through regardless of the filter and make the filter decorative
	 */
	public static function can_manage( int $order_id ): bool {
		return current_user_can( apply_filters( 'hein_manage_capability', 'edit_shop_order' ), $order_id );
	}

	public static function notice( int $order_id ): ?array {
		$key    = self::notice_key( $order_id );
		$notice = get_transient( $key );

		if ( ! is_array( $notice ) || 3 !== count( $notice ) ) {
			return null;
		}

		delete_transient( $key );

		return [
			'type'    => (string) $notice[0],
			'code'    => (string) $notice[1],
			'message' => (string) $notice[2],
		];
	}

	private static function action_url( string $action, int $order_id, string $format ): string {
		return wp_nonce_url(
			add_query_arg(
				[
					'action'   => $action,
					'order_id' => $order_id,
					'format'   => $format,
				],
				admin_url( 'admin-post.php' )
			),
			$action . '_' . $order_id
		);
	}

	private static function order_url( int $order_id ): string {
		if ( function_exists( 'wc_get_container' ) && class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) ) {
			return \Automattic\WooCommerce\Utilities\OrderUtil::get_order_admin_edit_url( $order_id );
		}

		return admin_url( 'post.php?post=' . $order_id . '&action=edit' );
	}

	private static function notice_key( int $order_id ): string {
		return 'hein_notice_' . get_current_user_id() . '_' . $order_id;
	}
}
