<?php

namespace WeDevs\DokanPro\Modules\PayPalMarketplace\REST\V1;

use Exception;
use WC_Order;
use WP_Error;
use WP_REST_Controller;

/**
 * Paypal order controller for the given Woocommerce Order ID.
 */
class PayPalController extends WP_REST_Controller {
    /**
	 * Endpoint namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'dokan/v1';

	/**
	 * Route base.
	 *
	 * @var string
	 */
	protected $rest_base = 'paypal-marketplace';

    /**
	 * Register the routes for Dokan PayPal Marketplace payment.
	 */
    public function register_routes() {
        register_rest_route(
            $this->namespace, '/' . $this->rest_base . '/create-payment/(?P<order_id>\d+)', array(
				'methods'  => 'POST',
				'callback' => array( $this, 'create_payment' ),
				'args'     => array(
					'order_id' => array(
						'validate_callback' => function ( $param, $request, $key ) {
							return is_numeric( $param );
						},
					),
				),
				'permission_callback' => array( $this, 'check_order_permission' ),
            )
        );

        register_rest_route(
            $this->namespace, '/' . $this->rest_base . '/create-payment', array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'create_cart_payment' ),
				'permission_callback' => array( $this, 'check_cart_permission' ),
            )
        );

        register_rest_route(
            $this->namespace, '/' . $this->rest_base . '/capture-payment/(?P<order_id>\d+)', array(
				'methods'  => 'POST',
				'callback' => array( $this, 'capture_payment' ),
				'args'     => array(
					'order_id' => array(
						'validate_callback' => function ( $param, $request, $key ) {
							return is_numeric( $param );
						},
					),
				),
				'permission_callback' => array( $this, 'check_order_permission' ),
            )
        );
    }

    /**
     * Check whether there is a cart to pay for.
     *
     * The cart route carries no order id, so the customer's own cart is the whole
     * authorization: they can only ever create a payment for what they are buying.
     *
     * @since 5.0.8
     *
     * @return bool|\WP_Error
     */
    public function check_cart_permission() {
        /*
         * WooCommerce loads neither the session nor the cart on REST requests, and
         * both are needed to build the order the customer is paying for.
         */
        if ( ! WC()->cart instanceof \WC_Cart && function_exists( 'wc_load_cart' ) ) { // @phpstan-ignore-line -- stubs type the cart non-null, but it is null on REST requests.
            wc_load_cart();
        }

        if ( ! WC()->cart instanceof \WC_Cart || WC()->cart->is_empty() ) {
            return new WP_Error(
                'dokan_paypal_empty_cart',
                __( 'There is nothing in your cart to pay for.', 'dokan' ),
                array( 'status' => 400 )
            );
        }

        return true;
    }

    /**
     * Create a PayPal order for the customer's cart.
     *
     * The Smart Payment Buttons need a PayPal order before the customer places their
     * order, but since WooCommerce 10.x the checkout block does not create an order
     * until then. So the draft order is created here, from the cart, using
     * WooCommerce's own builder — the same one the Store API uses — and recorded as
     * the session's draft order so that placing the order reuses it instead of
     * creating a second one.
     *
     * @since 5.0.8
     *
     * @return \WP_REST_Response
     */
    public function create_cart_payment() {
        try {
            $order = $this->get_draft_order();

            /**
             * Everything below this point — vendor orders, purchase units, platform
             * fees — is the same order based path the classic checkout uses.
             */
            $dokan_paypal    = dokan_pro()->module->paypal_marketplace->gateway_paypal;
            $process_payment = $dokan_paypal->process_payment( $order->get_id() );

            if ( isset( $process_payment['result'] ) && 'failure' === $process_payment['result'] ) {
                throw new Exception(
                    isset( $process_payment['message'] )
                        ? $process_payment['message']
                        : esc_html__( 'The PayPal payment could not be started.', 'dokan' )
                );
            }

            $process_payment['order_id'] = $order->get_id();
        } catch ( Exception $ex ) {
            $process_payment = new WP_Error( 'paypal_payment', $ex->getMessage(), array( 'status' => 500 ) );
        }

        return rest_ensure_response( $process_payment );
    }

    /**
     * Get the draft order the customer is checking out with, creating it if needed.
     *
     * @since 5.0.8
     *
     * @throws Exception If the order can not be built from the cart.
     *
     * @return \WC_Order
     */
    protected function get_draft_order() {
        // The session is guaranteed here: `check_cart_permission()` initializes it.
        $session        = WC()->session;
        $draft_order_id = absint( $session->get( 'store_api_draft_order', 0 ) );
        $draft_order    = $draft_order_id ? wc_get_order( $draft_order_id ) : false;

        // Reuse the draft order WooCommerce would reuse, so placing the order does not create a second one.
        if ( $draft_order instanceof WC_Order && ( $draft_order->has_status( 'checkout-draft' ) || $draft_order->needs_payment() ) ) {
            return $draft_order;
        }

        if ( ! class_exists( '\Automattic\WooCommerce\StoreApi\Utilities\OrderController' ) ) {
            throw new Exception( esc_html__( 'This version of WooCommerce cannot start a PayPal payment from the cart.', 'dokan' ) );
        }

        $order_controller = new \Automattic\WooCommerce\StoreApi\Utilities\OrderController();
        $order            = $order_controller->create_order_from_cart();

        if ( ! $order instanceof WC_Order ) {
            throw new Exception( esc_html__( 'The order could not be created from your cart.', 'dokan' ) );
        }

        $order->save();

        // Record it the way the Store API does, so placing the order reuses this one.
        $session->set( 'store_api_draft_order', $order->get_id() );

        return $order;
    }

    /**
     * Check whether the current customer is allowed to pay for the requested order.
     *
     * These routes take an order id straight from the URL, so without this check any
     * visitor could create or capture a PayPal payment against somebody else's order.
     * Payment is only allowed for the order the current customer is checking out with,
     * or for an order that belongs to the logged in customer.
     *
     * @since 5.0.8
     *
     * @param \WP_REST_Request $request Request object.
     *
     * @return bool|\WP_Error
     */
    public function check_order_permission( $request ) {
        $order = wc_get_order( absint( $request['order_id'] ) );

        if ( ! $order instanceof WC_Order ) {
            return new WP_Error(
                'dokan_paypal_order_not_found',
                __( 'No order was found to pay for.', 'dokan' ),
                array( 'status' => 404 )
            );
        }

        if ( ! $this->is_order_payable_by_current_customer( $order ) ) {
            return new WP_Error(
                'dokan_paypal_cannot_pay_order',
                __( 'You are not allowed to pay for this order.', 'dokan' ),
                array( 'status' => rest_authorization_required_code() )
            );
        }

        return true;
    }

    /**
     * Determine if the given order belongs to the customer making the request.
     *
     * @since 5.0.8
     *
     * @param \WC_Order $order The order being paid for.
     *
     * @return bool
     */
    protected function is_order_payable_by_current_customer( WC_Order $order ) {
        $order_id = $order->get_id();

        /*
         * WooCommerce does not initialize the session on REST requests, and the two
         * session grants below are what allow guests to pay, so hydrate it from the
         * customer's session cookie first.
         */
        if ( ! WC()->session instanceof \WC_Session && is_callable( [ WC(), 'initialize_session' ] ) ) { // @phpstan-ignore-line -- stubs type the session non-null, but it is null on REST requests.
            WC()->initialize_session();
        }

        $session = WC()->session;

        // WooCommerce types this property as non-null, but there is no session until it is initialized.
        if ( $session instanceof \WC_Session ) {
            // The draft order the customer is currently checking out with (checkout block).
            if ( $order_id === absint( $session->get( 'store_api_draft_order', 0 ) ) ) {
                return true;
            }

            // The order the customer was sent to the gateway with (classic checkout).
            if ( $order_id === absint( $session->get( 'order_awaiting_payment', 0 ) ) ) {
                return true;
            }
        }

        $customer_id = $order->get_customer_id();

        if ( $customer_id && get_current_user_id() === $customer_id ) {
            return true;
        }

        return false;
    }

    /**
     * Create a PayPal order for the Woocommerce order ID.
     *
	 * @since 3.15.0
     * @param \WP_REST_Request $request
	 * @return \WP_REST_Response
     */
    public function create_payment( $request ) {
        // Get order ID from request
        $order_id = $request['order_id'];

        try {
            /**
             * The order is already known to exist — `check_order_permission()` ran first.
             *
             * The gateway decides whether a PayPal order created earlier for this
             * WooCommerce order can be reused or has to be recreated, so that the
             * customer is never shown an amount that no longer matches the order.
             */
            $dokan_paypal    = dokan_pro()->module->paypal_marketplace->gateway_paypal;
            $process_payment = $dokan_paypal->process_payment( $order_id );
        } catch ( Exception $ex ) {
            $error_code = 'paypal_payment';
            $status_code = 500;

            $process_payment = new WP_Error( $error_code, $ex->getMessage(), array( 'status' => $status_code ) );
        }

        return rest_ensure_response( $process_payment );
    }

    /**
     * Capture the Paypal payment for the given Woocommerce Order ID.
     *
	 * @since 3.15.0
     * @param \WP_REST_Request $request
	 * @return \WP_REST_Response
     */
    public function capture_payment( $request ) {
        // Get order ID from request
        $order_id = $request['order_id'];

        try {
            $order = $this->get_order( $order_id );

            $paypal_order_id = $order->get_meta( '_dokan_paypal_order_id' );

            if ( ! $paypal_order_id ) {
                throw new Exception( __( 'No PayPal order id found.', 'dokan' ) );
            }

            $order_controller = dokan_pro()->module->paypal_marketplace->order_controller;

            $process_payment = $order_controller->handle_capture_payment_validation( $order_id, $paypal_order_id );
        } catch ( Exception $ex ) {
            $error_code = 'paypal_capture_payment';
            $status_code = 404;

            $process_payment = new WP_Error( $error_code, $ex->getMessage(), array( 'status' => $status_code ) );
        }

        return rest_ensure_response( $process_payment );
    }

    /**
     * Get the order by order_id.
     *
	 * @since 3.15.0
     * @throws Exception if there is no Woocommerce Order by the given ID.
     * @param int $order_id The order ID.
     * @return \WC_Order
     */
    protected function get_order( $order_id ) {
        $order = wc_get_order( $order_id );

        if ( ! $order ) {
            throw new Exception( esc_html__( 'Dokan PayPal Marketplace payment order not found in session', 'dokan' ) );
        }

        return $order;
    }
}
