<?php
/**
 * Main class
 *
 * @author Your Inspiration Themes
 * @package YITH WooCommerce Stripe
 * @version 1.0.0
 */

use \Stripe\Error;

if ( ! defined( 'YITH_WCSTRIPE' ) ) {
	exit;
} // Exit if accessed directly

if ( ! class_exists( 'YITH_WCStripe_Gateway_Addons' ) ) {
	/**
	 * WooCommerce Stripe gateway class
	 *
	 * @since 1.0.0
	 */
	class YITH_WCStripe_Gateway_Addons extends YITH_WCStripe_Gateway_Advanced {

		/**
		 * Constructor.
		 *
		 * @return \YITH_WCStripe_Gateway_Addons
		 * @since 1.0.0
		 */
		public function __construct() {
			parent::__construct();
		}

		/**
		 * Process the payment
		 *
		 * @param  int $order_id
		 * @return array
		 */
		public function process_payment( $order_id ) {
			$order      = wc_get_order( $order_id );
			$this->_current_order = $order;

			// Processing subscription
			if ( in_array( $this->mode, array( 'standard', 'elements' ) ) && ( $this->order_contains_subscription( $order_id ) ) ) {
				return $this->process_subscription();

				// Processing regular product
			} else {
				return parent::process_payment( $order_id );
			}
		}

		/**
		 * Check if order contains subscriptions.
		 *
		 * @param  int $order_id
		 * @return bool
		 */
		protected function order_contains_subscription( $order_id ) {
			return function_exists( 'YITH_WC_Subscription' ) && YITH_WC_Subscription()->order_has_subscription( $order_id );
		}

		/**
		 * Process the subscription
		 *
		 * @param WC_Order $order
		 *
		 * @return array
		 * @throws Error\Api
		 * @internal param string $cart_token
		 */
		protected function process_subscription( $order = null ) {
			if ( empty( $order ) ) {
				$order = $this->_current_order;
			}
			
			$order_id = yit_get_order_id( $order );
			$order_items = $order->get_items( 'line_item' );

			try {

				// Initializate SDK and set private key
				$this->init_stripe_sdk();

				// Card selected during payment
				$selected_card = $this->get_credit_card_num();

				// Set the token with card ID selected
				if ( $this->save_cards && 'new' != $selected_card && empty( $this->token ) ) {
					$this->token = $selected_card;
				}

				if ( empty( $this->token ) ) {
					$error_msg = __( 'Please make sure that your card details have been entered correctly and that your browser supports JavaScript.', 'yith-woocommerce-stripe' );

					if ( 'test' == $this->env ) {
						$error_msg .= ' ' . __( 'Developers: Please make sure that you\'re including jQuery and that there are no JavaScript errors in the page.', 'yith-woocommerce-stripe' );
					}

					$this->log( 'Wrong token ' . $this->token . ': ' . print_r( $_POST, true ) );

					throw new Error\Api( $error_msg );
				}

				$subscription_total = 0;

				// create subscriptions
				if( $subscriptions = yit_get_prop( $order, 'subscriptions', true ) ) {
					foreach ( array_map( 'intval', $subscriptions ) as $subscription_id ) {
						$subscription       = ywsbs_get_subscription( $subscription_id );
						$plan               = $this->get_plan( $subscription );
						$customer           = $this->get_customer( $order );
						$order_item_id      = $subscription->order_item_id;
						$order_item         = isset( $order_items[ $order_item_id ] ) ? $order_items[ $order_item_id ] : false;
						$product            = method_exists( $order_item, 'get_product' ) ? $order_item->get_product() : $order->get_product_from_item( $order_item );
						$line_total         = $order->get_line_total( $order_item, true );
						$interval           = $subscription->price_time_option;
						$interval_count     = $subscription->price_is_per;
						$first_payment_time = strtotime( "+{$interval_count} {$interval}" );
						$first_payment_days = apply_filters( 'yith_wcstripe_first_payment_time', ( $first_payment_time - time() ) / DAY_IN_SECONDS, $subscription, $first_payment_time, $order );

						$get_currency = method_exists( $order, 'get_currency' ) ? 'get_currency' : 'get_order_currency';

						// create subscription on stripe; set billing cycle to start after one interval
						$stripe_subscription = $this->api->create_subscription( $customer, $plan->id, array_merge(
							array(
								'metadata' => apply_filters( 'yith_wcstripe_metadata', array(
									'subscription_id' => $subscription_id,
									'instance'        => $this->instance
								), 'create_subscription' )
							),
							! $plan->trial_period_days ? array(
								'prorate' => false,
								'billing_cycle_anchor' => $first_payment_days * DAY_IN_SECONDS + time(),
							) : array()
						) );

						// create first invoice (automatic plan will start after 1 interval)
						if( $line_total ){

							// create invoice item for first payment
							$this->api->create_invoice_item( $customer, array(
								'amount'       => YITH_WCStripe::get_amount( $line_total ), // Amount in cents!
								'currency'     => strtolower( $order->$get_currency() ? $order->$get_currency() : get_woocommerce_currency() ),
								'description'  => apply_filters( 'yith_wcstripe_first_invoice_description', strip_tags( html_entity_decode( $product->get_formatted_name() ) ) ),
								'discountable' => true,
								'subscription' => $stripe_subscription->id,
								'metadata'     => array(
									'subscription_id' => $subscription_id,
									'instance'        => $this->instance
								)
							) );

							// create invoice with previous invoice item
							$invoice = $this->api->create_invoice( $customer, array(
								'subscription' => $stripe_subscription->id,
								'description' => sprintf( __( '%s - Order %s', 'yith-woocommerce-stripe' ), esc_html( get_bloginfo( 'name' ) ), $order->get_order_number() ),
							) );

							// pay invoice
							$invoice = $invoice->pay();
							$charge_id = $invoice->charge;

							// update charge to add metadata and correct description
							$this->api->update_charge( $charge_id, array(
								'metadata' => array(
									'order_id' => $order_id,
									'instance' => $this->instance
								),
								'description' => apply_filters( 'yith_wcstripe_charge_description', sprintf( __( '%s - Order %s', 'yith-woocommerce-stripe' ), esc_html( get_bloginfo( 'name' ) ), $order->get_order_number() ), esc_html( get_bloginfo( 'name' ) ), $order->get_order_number() ),
							) );

							$subscription_total += $line_total;

							$order->add_order_note( sprintf( __( 'Subscription charged correctly (ID: <a href="%s">%s</a>)', 'yith-woocommerce-stripe' ), sprintf( $this->view_transaction_url, $charge_id ), $charge_id ) );
							$subscription->set( 'stripe_charge_id', $invoice->charge );
							wc_update_order_item_meta( $order_item_id, '_subscription_charge_id', $charge_id );
						}

						// set meta data
						$subscription->set( 'stripe_subscription_id', $stripe_subscription->id );
						$subscription->set( 'stripe_customer_id', $customer->id );
						$subscription->set( 'payment_due_date', $stripe_subscription->current_period_end );

						// set meta of order
						$user = $order->get_user();
						if ( $customer ) {
							yit_save_prop( $order, 'Subscriber ID', $customer->id );
						}
						if ( $user ) {
							yit_save_prop( $order, 'Subscriber first name', $user->first_name );
							yit_save_prop( $order, 'Subscriber last name', $user->last_name );
							yit_save_prop( $order, 'Subscriber address', $user->billing_email );
						}
						yit_save_prop( $order, 'Subscriber payment type', $this->id );
						yit_save_prop( $order, 'Stripe Subscribtion ID', $stripe_subscription->id );
					}
				}

				// pay
				update_post_meta( $order_id, '_stripe_subscription_total', $subscription_total );
				$response = $this->pay( $order, $order->get_total() - $subscription_total );

				if ( $response === true ) {
					$response = array(
						'result'   => 'success',
						'redirect' => $this->get_return_url( $order )
					);
				}

				return $response;

			} catch ( Error\Base $e ) {
				$body = $e->getJsonBody();
				$message = $e->getMessage();

				if ( $body ) {
					$err     = $body['error'];
					if ( isset( $err['code'] ) && isset( $this->errors[ $err['code'] ] ) ) {
						$message = $this->errors[ $err['code'] ];
					}

					$this->log( 'Stripe Error: ' . $e->getHttpStatus() . ' - ' . print_r( $e->getJsonBody(), true ) );

					// add order note
					$order->add_order_note( 'Stripe Error: ' . $e->getHttpStatus() . ' - ' . $e->getMessage() );

					// add block if there is an error on card
					if ( $err['type'] == 'card_error' ) {
						$this->add_block( "order_id={$order_id}" );
						WC()->session->refresh_totals = true;
					}
				}

				wc_add_notice( $message, 'error' );

				return array(
					'result'   => 'fail',
					'redirect' => ''
				);

			}
		}

		/**
		 * Process refund
		 *
		 * Overriding refund method
		 *
		 * @access      public
		 * @param       int $order_id
		 * @param       float $amount
		 * @param       string $reason
		 * @return      mixed True or False based on success, or WP_Error
		 */
		public function process_refund( $order_id, $amount = null, $reason = '' ) {
			$order = wc_get_order( $order_id );
			$transaction_id = $order->get_transaction_id();
			$captured = strcmp( $order->captured, 'yes' ) == 0;
			$refund = array_shift( $order->get_refunds() );

			if ( isset( $order->bitcoin_inbound_address ) || isset( $order->bitcoin_uri ) ) {
				return new WP_Error( 'yith_stripe_no_bitcoin',
					sprintf(
						__( "Refund not supported for bitcoin", 'yith-woocommerce-stripe' )
					)
				);
			}

			// subdivide refund among items
			$amounts = array();

			foreach( $order->get_items() as $item_id => $item ){
				$charge_id = wc_get_order_item_meta( $item_id, '_subscription_charge_id', true );
				$index = $charge_id ? $charge_id : $transaction_id;

				if( ! isset( $amounts[ $index ] ) ){
					$amounts[ $index ] = array(
						'total' => 0,
						'refund' => 0
					);
				}

				$amounts[ $index ]['total'] += $order->get_line_total( $item, true );

				foreach ( $refund->get_items() as $refunded_item ) {
					if ( isset( $refunded_item['refunded_item_id'] ) && $refunded_item['refunded_item_id'] == $item_id ) {
						$amounts[ $index ]['refund'] += abs( $refund->get_line_total( $refunded_item, true ) );
					}
				}
			}

			foreach ( $refund->get_items( 'shipping' ) as $refunded_item ) {
				$amounts[ $transaction_id ] = abs( $refund->get_line_total( $refunded_item, true ) );
			}

			$remaining_amount = abs( $refund->get_total() ) - array_sum( array_column( $amounts, 'refund' ) );

			if( $remaining_amount > 0 ){
				foreach( $amounts as & $amount ){
					$amount['refund'] += $remaining_amount * $amount['total'] / $order->get_total();
				}
			}

			try {

				// Initializate SDK and set private key
				$this->init_stripe_sdk();

				foreach( $amounts as $charge_id => $data ){
					$params = array(
						'amount' => YITH_WCStripe::get_amount( round( $data['refund'], 2 ) )
					);

					// If a reason is provided, add it to the Stripe metadata for the refund
					if ( $reason AND in_array( $reason, array( 'duplicate', 'fraudulent', 'requested_by_customer' ) ) ) {
						$params['reason'] = $reason;
					}

					$this->log( 'Stripe Refund Request: ' . print_r( $params, true ) );

					// Send the refund to the Stripe API
					$stripe_refund = $this->api->refund( $charge_id, $params );
					update_post_meta( $refund->id, '_refund_stripe_id', $stripe_refund->id );

					$this->log( 'Stripe Refund Response: ' . print_r( $stripe_refund, true ) );
				}

				return true;

			} catch ( Error\Card $e ) {
				$body = $e->getJsonBody();
				$err  = $body['error'];
				$message = isset( $this->errors[ $err['code'] ] ) ? $this->errors[ $err['code'] ] : $err['message'];

				$order->add_order_note(
					sprintf(
						__( 'Stripe Credit Card Refund Failed with message: "%s"', 'yith-woocommerce-stripe' ),
						$message
					)
				);

				// Something failed somewhere, send a message.
				return new WP_Error( 'yith_stripe_refund_error', $e->getMessage() );
			}
		}

		/**
		 * Get subscription ID by stripe subscription id
		 */
		public function get_subscription_id( $stripe_subscription_id ) {
			global $wpdb;
			return $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID AND p.post_type = %s WHERE pm.meta_value = %s ORDER BY pm.post_id DESC LIMIT 1", 'ywsbs_subscription', $stripe_subscription_id ) );
		}

		/**
		 * Retrieve the plan.
		 *
		 * If it doesn't exist, create a new one and returns it.
		 *
		 * @param $subscription YWSBS_Subscription
		 *
		 * @return \Stripe\Plan
		 */
		public function get_plan( $subscription, $order = null ) {
			$object_id = ! empty( $subscription->variation_id ) ? $subscription->variation_id : $subscription->product_id;
			$product = wc_get_product( $object_id );
			$order = $order ? $order : $this->_current_order;
			//$plan_amount = $subscription->line_total + $subscription->line_tax;
			//fixed the amount of the subscription plan
			$plan_amount = apply_filters( 'yith_wcstripe_subscription_amount', $subscription->order_total, $subscription, $order, $product );

			// translate the option saved on subscription options of product to values requested by stripe
			$interval_periods = array(
				'days'   => 'day',
				'weeks'  => 'week',
				'months' => 'month',
				'years'  => 'year'
			);

			// calculate trial days
			$interval          = str_replace( array_keys( $interval_periods ), array_values( $interval_periods ), yit_get_prop( $product, '_ywsbs_price_time_option', true ) );
			$interval_count    = intval( yit_get_prop( $product, '_ywsbs_price_is_per', true ) );
			$trial_period      = yit_get_prop( $product, '_ywsbs_trial_per', true );
			$trial_time_option = yit_get_prop( $product, '_ywsbs_trial_time_option', true );

			if ( ! empty( $trial_period ) && in_array( $trial_time_option, array( 'days', 'weeks', 'months', 'years' ) ) ) {
				$trial_end = strtotime( "+{$trial_period} {$trial_time_option}" );
				$trial = ( $trial_end - time() ) / DAY_IN_SECONDS;
			} else {
				$trial_end = time();
				$trial = 0;
			}

			$trial_period_days = apply_filters( 'yith_wcstripe_plan_trial_period', intval( $trial ), $subscription, $trial_end, $order );

			// hash used to prevent differences between subscription configuration
			$hash = md5( $plan_amount . $interval . $interval_count . $trial_period_days . $subscription->order_currency );

			// get plan if exists
			$plan_id = "product_{$object_id}_{$hash}";
			$plan = $this->api->get_plan( $plan_id );

			// if some parameter is changed with save plan, delete it to recreate it
			if ( $plan ) {
				return $plan;
			}

			// retrieve order currency
			$get_currency = method_exists( $order, 'get_currency' ) ? 'get_currency' : 'get_order_currency';
			$currency = $order->$get_currency() ? $order->$get_currency() : get_woocommerce_currency();

			// format the name of plan
			$product_name = strip_tags( html_entity_decode( $product->get_title() ) );
			$plan_name = '';

			if( $product->get_type() == 'variation' ){
				/**
				 * @var $product \WC_Product_Variation
				 */
				$plan_name .= $product->get_formatted_variation_attributes( true );
				$plan_name .= ' - ';
			}

			$formatted_interval = $interval_count == 1 ? $interval : $interval_count . ' ' . $interval_periods[ $interval ];
			$plan_name .= sprintf( '%s / %s', wc_price( $plan_amount, array( 'currency' => $currency ) ), $formatted_interval );

			if( $trial_period_days ){
				$plan_name .= sprintf( __( ' - %s days trialing', 'yith-woocommerce-stripe' ), $trial_period_days );
			}

			$plan_name = strip_tags( html_entity_decode( $plan_name ) );

			// retrieve product, if it already exists: otherwise it will created with the plan
			$product_id = "product_{$subscription->product_id}";

			try {
				$stripe_product = $this->api->get_product( $product_id );
			}
			catch( Exception $e ){
				$stripe_product = false;
			}

			// if it doesn't exist, create it
			$plan = $this->api->create_plan( array(
				'id'                => $plan_id,
				'product'           => $stripe_product ? $stripe_product->id : array(
					'id'                => $product_id,
					'name'              => $product_name,
				),
				'nickname'          => $plan_name,
				'currency'          => strtolower( $currency ),
				'interval'          => $interval,
				'interval_count'    => $interval_count,
				'amount'            => YITH_WCStripe::get_amount( $plan_amount ),
				'trial_period_days' => $trial_period_days,
				'metadata'          => apply_filters( 'yith_wcstripe_metadata', array(
					'product_id' => $object_id
				), 'create_plan' )
			) );

			return $plan;
		}
	}
}