<?php
/**
 * This file belongs to the YIT Framework.
 *
 * This source file is subject to the GNU GENERAL PUBLIC LICENSE (GPL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl-3.0.txt
 *
 * @package YITH\PreOrder\Gateways
 * @author YITH <plugins@yithemes.com>
 */

if ( ! defined( 'ABSPATH' ) || ! defined( 'YITH_WCPO_VERSION' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Integration class for WooCommerce Gateway Stripe (New checkout experience).
 *
 * @extends WC_Stripe_UPE_Payment_Gateway
 */
class YITH_Pre_Order_WC_Stripe_UPE extends WC_Stripe_UPE_Payment_Gateway {
	/**
	 * Initialize pre-orders hook.
	 *
	 * @since 5.8.0
	 */
	public function maybe_init_pre_orders() {
		$this->supports[] = 'yith_pre_orders';
		add_action( 'ywpo_process_pre_order_release_payment_' . $this->id, array( $this, 'process_pre_order_release_payment' ) );
	}

	/**
	 * Checks if pre-orders are enabled on the site.
	 *
	 * @since 5.8.0
	 *
	 * @return bool
	 */
	public function is_pre_orders_enabled() {
		return true;
	}

	/**
	 * Is $order_id a pre-order?
	 *
	 * @since 5.8.0
	 *
	 * @param  int $order_id The order ID.
	 * @return bool
	 */
	public function has_pre_order( $order_id ) {
		return ywpo_order_has_pre_order( $order_id );
	}

	/**
	 * Returns boolean on whether current cart contains a pre-order item.
	 *
	 * @since 5.8.0
	 *
	 * @return bool
	 */
	public function is_pre_order_item_in_cart() {
		return ywpo_cart_contains_pre_order_product();
	}

	/**
	 * Checks if we need to process pre-orders when pre-orders are in the cart.
	 *
	 * @param int $order_id The order ID.
	 * @return bool
	 */
	public function maybe_process_pre_orders( $order_id ) {
		return apply_filters( 'ywpo_maybe_process_pre_orders', ywpo_order_has_pre_order( $order_id ) && ywpo_order_will_need_payment_token( $order_id ) && ! is_wc_endpoint_url( 'order-pay' ), $order_id );
	}

    /**
     * Checks if pre-order is charged upfront.
     *
     * @param WC_Product $product
     *
     * @return bool
     */
    public function is_pre_order_product_charged_upfront( $product ) {
        return ywpo_is_upfront_cart();
    }

	/**
	 * Marks the order as pre-ordered.
	 * The native function is wrapped, so we can call it separately and more easily mock it in our tests.
	 *
	 * @param WC_Order $order The order instance.
	 */
	public function mark_order_as_pre_ordered( $order ) {
		YITH_Pre_Order_Orders_Manager::set_as_pre_order_pending_payment( $order );
	}

	/**
	 * Process the pre-order when pay upon release is used.
	 *
	 * @param int $order_id The order ID.
	 *
	 * @throws WC_Stripe_Exception Stripe Exception in case the payment fails.
	 */
	public function process_pre_order( $order_id ) {
		try {
			$order = wc_get_order( $order_id );

			// This will throw exception if not valid.
			$this->validate_minimum_order_amount( $order );

			$prepared_source = $this->prepare_source( get_current_user_id(), true );

			// We need a source on file to continue.
			if ( empty( $prepared_source->customer ) || empty( $prepared_source->source ) ) {
				throw new WC_Stripe_Exception( __( 'Unable to store payment details. Please, try again.', 'woocommerce-gateway-stripe' ) );
			}

			// Set up the response early to allow later modifications.
			$response = array(
				'result'   => 'success',
				'redirect' => $this->get_return_url( $order ),
			);

			$this->save_source_to_order( $order, $prepared_source );

			// Try setting up a payment intent.
			$intent_secret = $this->setup_intent( $order, $prepared_source );
			if ( ! empty( $intent_secret ) ) {
				$response['setup_intent_secret'] = $intent_secret;
				return $response;
			}

			// Remove cart.
			WC()->cart->empty_cart();

			// Is pre-ordered!
			$this->mark_order_as_pre_ordered( $order );

			// Return thank you page redirect.
			return $response;
		} catch ( WC_Stripe_Exception $e ) {
			wc_add_notice( $e->getLocalizedMessage(), 'error' );
			WC_Stripe_Logger::log( 'YITH Pre-Order for WooCommerce Error: ' . $e->getMessage() );

			return array(
				'result'   => 'success',
				'redirect' => $order->get_checkout_payment_url( true ),
			);
		}
	}

	/**
	 * Process a pre-order payment when the pre-order is released.
	 *
	 * @param WC_Order $order The order instance.
	 * @param bool     $retry Whether to retry or not.
	 *
	 * @throws Exception Stripe Exception in case the payment fails.
	 * @throws WC_Stripe_Exception Stripe Exception in case the payment fails.
	 *
	 * @return void
	 */
	public function process_pre_order_release_payment( $order, $retry = true, $previous_error = false ) {
		try {
            $prepared_source = $this->prepare_order_source( $order );

            if ( ! $prepared_source ) {
                throw new WC_Stripe_Exception( WC_Stripe_Helper::get_localized_messages()['missing'] );
            }

            $source_object = $prepared_source->source_object;

            if ( ! $prepared_source->customer ) {
                throw new WC_Stripe_Exception('Failed to process YITH pre-order release payment for order ' . $order->get_id() . '. Stripe customer id is missing in the order' );
            }

            /*
             * If we're doing a retry and source is chargeable, we need to pass
             * a different idempotency key and retry for success.
             */
            if ( is_object( $source_object ) && empty( $source_object->error ) && $this->need_update_idempotency_key( $source_object, $previous_error ) ) {
                add_filter( 'wc_stripe_idempotency_key', array( $this, 'change_idempotency_key' ), 10, 2 );
            }

			$response                   = $this->create_and_confirm_intent_for_off_session( $order, $prepared_source );
			$is_authentication_required = $this->is_authentication_required_for_payment( $response );

			if ( ! empty( $response->error ) && ! $is_authentication_required ) {
				if ( ! $retry ) {
					throw new Exception( $response->error->message );
				}
				$this->remove_order_source_before_retry( $order );
				$this->process_pre_order_release_payment( $order, false, $response->error );
			} elseif ( $is_authentication_required ) {
				$charge = end( $response->error->payment_intent->charges->data );
				$id     = $charge->id;

				$order->set_transaction_id( $id );
				/* translators: %s is the charge ID */
				$order->update_status( 'failed', sprintf( __( 'Stripe charge awaiting authentication by user: %s.', 'woocommerce-gateway-stripe' ), $id ) );
				if ( is_callable( array( $order, 'save' ) ) ) {
					$order->save();
				}

				WC_Emails::instance();

				do_action( 'wc_gateway_stripe_process_payment_authentication_required', $order );

				throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message );
			} else {
				// Successful.
				$latest_charge = $this->get_latest_charge_from_intent( $response );
				// Use the last charge within the intent or the full response body in case of SEPA.
				$this->process_response( ( ! empty( $latest_charge ) ) ? $latest_charge : $response, $order );
			}
		} catch ( Exception $e ) {
			$error_message = is_callable( array( $e, 'getLocalizedMessage' ) ) ? $e->getLocalizedMessage() : $e->getMessage();
			/* translators: error message */
			$order_note = sprintf( __( 'Stripe transaction failed (%s)', 'woocommerce-gateway-stripe' ), $error_message );

			// Marks order as failed if not already set, otherwise, make sure we add the order note,
			// so we can detect when someone fails to check out multiple times.
			if ( ! $order->has_status( 'failed' ) ) {
				$order->update_status( 'failed', $order_note );
			} else {
				$order->add_order_note( $order_note );
			}
		}
	}
}
