<?php

/**
 * Prodigy cart class
 *
 * @version    2.0.7
 * @package    Prodigy
 * @subpackage Prodigy/includes
 */

namespace Prodigy\Includes;

use Prodigy\Includes\Content\Prodigy_Request_Maker;
use Prodigy\Includes\Frontend\Cart_Error_Handler;
use Prodigy\Includes\Helpers\Prodigy_Cookies;
use Prodigy\Includes\Helpers\Prodigy_Logger;
use Prodigy\Includes\Helpers\Prodigy_Url_Helper;
use Prodigy\includes\models\Prodigy_Order;

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

/**
 * Class Prodigy_Cart
 */
class Prodigy_Cart {

	public const CART_COOKIE_NAME = 'wordpress_prodigy_cart';

	/** @var Prodigy_Request_Maker */
	private Prodigy_Request_Maker $request_maker;

	/** @var Prodigy_Order */
	private Prodigy_Order $order;
	/** @var Cart_Error_Handler */
	private Cart_Error_Handler $cart_error_handler;

	/**
	 * @param  Prodigy_Request_Maker                         $request_maker
	 * @param  Prodigy_Order                                 $order
	 * @param  \Prodigy\Includes\Frontend\Cart_Error_Handler $cart_error_handler
	 */
	public function __construct(
		Prodigy_Request_Maker $request_maker,
		Prodigy_Order $order,
		Cart_Error_Handler $cart_error_handler
	) {
		$this->request_maker      = $request_maker;
		$this->order              = $order;
		$this->cart_error_handler = $cart_error_handler;
	}

	/**
	 * @param  int    $order_number
	 * @param  string $order_token
	 *
	 * @return string
	 */
	public static function get_checkout_url( int $order_number, string $order_token ): string {
		$base_url     = sprintf(
			'%1$s%2$s.%3$s/orders/%4$s/checkout/login',
			PRODIGY_PROTOCOL_DOMAIN,
			Prodigy_Url_Helper::get_hs_store_subdomain(),
			PRODIGY_CHECKOUT_DOMAIN,
			$order_number
		);
		$redirect_url = add_query_arg( 'token', $order_token, home_url() . '/api-listener' );

		return add_query_arg(
			array(
				'token'               => $order_token,
				'plugin_redirect_url' => rawurlencode( $redirect_url ),
			),
			$base_url
		);
	}

	/**
	 * @param  string $token
	 *
	 * @return bool|string
	 */
	public function clear_order( string $token ) {
		$result       = '';
		$delete_order = $this->request_maker->do_delete_order_request( $token );
		if ( isset( $delete_order['error'] ) ) {
			$error_message = $delete_order['error'] ?? 'Delete order error';
			$this->cart_error_handler->log_error( $error_message );
		}
		if ( $delete_order['code'] === \WP_Http::NO_CONTENT ) {
			$result = $this->order->delete_local_order( $token );
		}

		return $result;
	}

	/**
	 * @return void
	 */
	public function move_guest_order_to_customer_order(): void {
		$guest_user_session = Prodigy_Cookies::get_user_cart_cookie();
		// If guest session cookie is missing, do not try to move/attach any guest order.
		// Otherwise we may accidentally match orders with empty local_user_hash and "leak" carts between users.
		if ( empty( $guest_user_session ) ) {
			return;
		}
		$order = Prodigy_Order::get_last_guest_active_order_data( $guest_user_session );

		$user_id = Prodigy_User::get_user_session();
		if ( ! isset( $order ) && Prodigy_User::is_logged_in() ) {
			$order = Prodigy_Order::get_last_user_active_order_data( $user_id );
			if ( isset( $user_id, $order ) ) {
				$old_user_hash = $order['local_user_hash'];
				$new_user_hash = $guest_user_session;
				$this->order->update_user_order( $order['token'], $old_user_hash, $new_user_hash, $user_id );
			}

			return;
		}

		if ( isset( $order ) && Prodigy_User::is_logged_in() ) {
			$active_order = $this->order->get_active_user_order( (int) $user_id );
			if ( isset( $active_order ) && $active_order['local_user_hash'] !== $guest_user_session ) {
				$this->order->close_active_user_order( $user_id );
				$this->request_maker->do_delete_order_request( $active_order['token'] );
			}
		}

		if ( isset( $user_id, $order ) ) {
			$this->order->update_user_order( $order['token'], $guest_user_session, $guest_user_session, $user_id );
		}
	}

	/**
	 * @param  string $order_token
	 * @param  array  $cart_response
	 *
	 * @return bool
	 */
	public function refresh_order_data( string $order_token, array $cart_response ): bool {
		$result = false;
		if ( ! empty( $cart_response ) ) {
			$result = $this->order->update_order_data( $order_token, Prodigy_Cookies::get_user_cart_cookie(), $cart_response );
		}

		return $result;
	}

	/**
	 * @param  string|null $current_session
	 *
	 * @return void
	 * @throws \Exception
	 */
	public function set_cart_user_session( ?string $current_session = null ): void {
		$setting_time     = ! empty( get_option( 'pg_custom_expiration_time' ) ) ? get_option( 'pg_custom_expiration_time' ) : get_option( 'pg_cart_expiration_time' );
		$expiry_timestamp = strtotime( '+' . $setting_time . ' hours' );

		$existing_cookie = Prodigy_Cookies::has_cookie( self::CART_COOKIE_NAME ) ? Prodigy_Cookies::get_user_cart_cookie() : null;

		if ( empty( $existing_cookie ) ) {
			// Unique per browser: UUID generated on first add-to-cart, stored only in this visitor's cookie jar.
			$session = ! empty( $current_session ) ? $current_session : $this->generate_uuid();

			$current_domain = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : parse_url( home_url(), PHP_URL_HOST );
			$current_domain = strtok( $current_domain, ':' );
			$cookie_domain  = ltrim( $current_domain, '.' );
			if ( strpos( $cookie_domain, '.' ) === 0 ) {
				$cookie_domain = ltrim( $cookie_domain, '.' );
			}

			$cookie_options = array(
				'expires'  => $expiry_timestamp,
				'path'     => '/',
				'domain'   => $cookie_domain,
				'secure'   => Prodigy_Cookies::is_https_request(),
				'httponly' => false,
				'samesite' => Prodigy_Cookies::get_same_site_attribute(),
			);
			if ( PHP_VERSION_ID >= 70300 ) {
				@setcookie( self::CART_COOKIE_NAME, $session, $cookie_options );
			} else {
				@setcookie( self::CART_COOKIE_NAME, $session, $expiry_timestamp, '/', $cookie_domain, Prodigy_Cookies::is_https_request(), false );
			}
			$_COOKIE[ self::CART_COOKIE_NAME ] = $session;
		}
	}

	/**
	 * Generate unique hash
	 *
	 * @return string
	 */
	public function generate_uuid(): string {
		if ( function_exists( 'random_bytes' ) ) {
			try {
				$bytes    = random_bytes( 16 );
				$bytes[6] = chr( ord( $bytes[6] ) & 0x0f | 0x40 ); // Version 4
				$bytes[8] = chr( ord( $bytes[8] ) & 0x3f | 0x80 ); // Variant bits
				return vsprintf( '%s%s-%s-%s-%s-%s%s%s', str_split( bin2hex( $bytes ), 4 ) );
			} catch ( \Exception $e ) {
			}
		}

		$entropy = microtime( true ) . uniqid( '', true );
		if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
			$entropy .= $_SERVER['REMOTE_ADDR'];
		}
		if ( isset( $_SERVER['REQUEST_TIME_FLOAT'] ) ) {
			$entropy .= $_SERVER['REQUEST_TIME_FLOAT'];
		}
		$entropy .= wp_rand( 0, PHP_INT_MAX );

		$hash = md5( $entropy );
		return sprintf(
			'%s-%s-%s-%s-%s',
			substr( $hash, 0, 8 ),
			substr( $hash, 8, 4 ),
			'4' . substr( $hash, 13, 3 ), // Version 4
			dechex( ( hexdec( substr( $hash, 16, 1 ) ) & 0x3f ) | 0x80 ) . substr( $hash, 17, 3 ), // Variant bits
			substr( $hash, 20, 12 )
		);
	}

	/**
	 * @param  array  $remote_product
	 * @param  string $count
	 *
	 * @return array
	 */
	public function prepare_line_item_request( array $remote_product, string $count ): array {
		if ( isset( $remote_product['remote_product_id'] ) ) {
			$product_id = filter_var( $remote_product['remote_product_id'], FILTER_VALIDATE_INT );
		}

		if ( isset( $remote_product['subscription_id'] ) ) {
			$subscription_id = filter_var( $remote_product['subscription_id'], FILTER_VALIDATE_INT );
		}

		$request = array(
			'line_item'   =>
				array(
					'quantity'                  => $count ?? 0,
					'variant_id'                => $product_id ?? '',
					'subscription_condition_id' => $subscription_id ?? '',
					'logo_option_ids'           => $item['logos_ids'] ?? array(),
				),
			'order_token' => $remote_product['token'],
		);

		if ( isset( $request['line_items'] ) && ! $request['line_items'][0]['subscription_condition_id'] ) {
			unset( $request['line_items'][0]['subscription_condition_id'] );
		}

		return $request;
	}

	/**
	 * @param  array $item
	 * @param  null  $token
	 *
	 * @return array
	 */
	public function set_cart_prepare_request( array $item, $token = null ): array {
		$count             = $this->validate_int( $item, 'count' );
		$subscription_id   = $this->validate_int( $item, 'subscription_id' );
		$is_bulk           = ( isset( $item['is_bulk'] ) && $item['is_bulk'] !== 'false' );
		$remote_product_id = null;
		if ( isset( $item['variants'][0]['remote_variant_id'] ) || isset( $item['variants']['remote_variant_id'] ) ) {
			$remote_product_id = $is_bulk ? $item['variants'][0]['remote_variant_id'] : $item['variants']['remote_variant_id'];
		}

		if ( isset( $item['remote_product_id'] ) ) {
			$remote_product_id = $item['remote_product_id'];
		}

		$request = array(
			'include'    => 'line-items',
			'line_items' => array(),
		);

		if ( $is_bulk && is_array( $item['variants'] ) ) {
			$this->prepare_bulk_request( $request, $item );
		} else {
			$this->prepare_single_request( $request, $item, $count, $remote_product_id, $subscription_id );
		}

		$this->clean_up_request( $request );

		if ( ! empty( $token ) ) {
			$request['order_token'] = $token;
		}

		$external_checkout_id = Prodigy_User::get_external_checkout_id();
		if ( $external_checkout_id !== null ) {
			$request['external_checkout_id'] = $external_checkout_id;
			if ( PRODIGY_DEBUG_MODE ) {
				Prodigy_Logger::add_record(
					Prodigy_Logger::TYPE_INFO,
					'External checkout id added to cart request',
					'prodigy-cart',
					wp_json_encode(
						array(
							'external_checkout_id' => $external_checkout_id,
							'has_order_token'      => ! empty( $token ),
						)
					)
				);
			}
		}

		return $request;
	}

	/**
	 * @param  array  $item
	 * @param  string $key
	 *
	 * @return int
	 */
	private function validate_int( array $item, string $key ): int {
		if ( isset( $item[ $key ] ) ) {
			return filter_var( $item[ $key ], FILTER_VALIDATE_INT );
		}

		return 0;
	}

	/**
	 * @param  array $request
	 * @param  array $item
	 *
	 * @return void
	 */
	private function prepare_bulk_request( array &$request, array $item ): void {
		foreach ( $item['variants'] as $key => $one ) {
			if ( is_numeric( $key ) ) {
				$line_item = array(
					'quantity'   => $item['attributes'][ $key ]['quantity'] ?? 0,
					'variant_id' => $one['remote_variant_id'] ?? '',
				);

				if ( isset( $one['logo_options_ids'] ) ) {
					$line_item['logo_option_ids'] = array_unique( array_map( 'intval', $one['logo_options_ids'] ) );
				}

				$personalization = array();
				if ( isset( $item['personalization'] ) ) {
					$personalization = $this->sanitize_personalization_fields( $item['personalization'] );
				}

				if ( isset( $item['personalization'][0] ) && $this->is_personalization_filled( $personalization ) ) {
					$this->add_personalization( $line_item, $personalization );
				}

				$request['line_items'][ $key ] = $line_item;
			}
		}
	}

	/**
	 * @param  array $personalization
	 *
	 * @return array
	 */
	private function sanitize_personalization_fields( array $personalization ): array {
		foreach ( $personalization as $key => $value ) {
			$personalization[ $key ]['value'] = sanitize_text_field( wp_unslash( $value['value'] ) );
		}

		return $personalization;
	}

	/**
	 * @param  array $personalization
	 *
	 * @return bool
	 */
	public function is_personalization_filled( array $personalization ): bool {
		$is_filled = false;
		foreach ( $personalization as $field ) {
			if ( ! empty( $field['value'] ) ) {
				$is_filled = true;
			}
		}

		return $is_filled;
	}

	/**
	 * @param $line_item
	 * @param $personalization
	 *
	 * @return void
	 */
	private function add_personalization( &$line_item, $personalization ): void {
		$line_item['personalization_id']     = $personalization[0]['personalization_id'] ?? 0;
		$line_item['personalization_fields'] = $personalization;
	}

	/**
	 * @param  array $request
	 * @param  array $item
	 * @param  int   $count
	 * @param  int   $remote_product_id
	 * @param  int   $subscription_id
	 *
	 * @return void
	 */
	private function prepare_single_request( array &$request, array $item, int $count, int $remote_product_id, int $subscription_id ): void {
		$line_item = array(
			'quantity'                  => $count,
			'variant_id'                => $remote_product_id,
			'subscription_condition_id' => $subscription_id,
		);

		if ( isset( $item['variants']['logo_options_ids'] ) ) {
			if ( is_array( $item['variants']['logo_options_ids'] ) ) {
				$line_item['logo_option_ids'] = array_unique( array_map( 'intval', $item['variants']['logo_options_ids'] ) );
			} else {
				$line_item['logo_option_ids'][] = filter_var( wp_unslash( $item['variants']['logo_options_ids'] ), FILTER_SANITIZE_NUMBER_INT );
			}
		}

		$personalization = array();
		if ( isset( $item['personalization'] ) ) {
			$personalization = $this->sanitize_personalization_fields( $item['personalization'] );
		}

		if ( isset( $item['personalization'][0] ) && $this->is_personalization_filled( $personalization ) ) {
			$this->add_personalization( $line_item, $personalization );
		}

		$request['line_items'][] = $line_item;
	}

	/**
	 * @param $request
	 *
	 * @return void
	 */
	private function clean_up_request( &$request ): void {
		if ( is_array( $request['line_items'] ) && array_key_exists( 'bulk_total_price', $request['line_items'] ) ) {
			unset( $request['line_items']['bulk_total_price'] );
		}

		foreach ( $request['line_items'] as &$line_item ) {
			if ( empty( $line_item['subscription_condition_id'] ) ) {
				unset( $line_item['subscription_condition_id'] );
			}
		}

		$request['redirect_to'] = '/api-listener';
	}
}
