<?php
namespace SabaiApps\Directories\Component\PaymentGateway\Helper;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Entity\Type\IEntity;
use SabaiApps\Directories\Component\Payment\IPlan;
use SabaiApps\Directories\Component\PaymentGateway\PaymentOrder;
use SabaiApps\Directories\Exception;

class OrderHelper {

	public function create( Application $application, IEntity $entity, IPlan $plan, $action = '', $gateway = '', array $discountCodes = array(), array $fees = array(), $status = null, array $data = array() ) {
		if ( $action === 'claim' ) {
			$claim = $entity;
			if ( ! $entity = $application->Entity_ParentEntity( $claim ) ) {
				throw new Exception\RuntimeException( 'Failed fetching claimed entity for claim (ID: ' . $claim->getId() . ').' );
			}
			$data['claim_id'] = $claim->getId();
		}
		$bundle = $application->Entity_Bundle( $entity, '', true );
		if ( ! isset( $status ) ) {
			$status = 'pending_payment';
		}
		$order_data  = $this->getData( $application, $plan, (array) $application->Payment_Fee_calculate( $entity, $plan, $action ) + $fees, $discountCodes );
		$order_data += array(
			'plan_name'      => $plan->paymentPlanTitle(),
			'plan_type'      => $plan->paymentPlanType(),
			'status_updated' => time(),
		);
		if ( $plan->paymentPlanType() === 'subscription' ) {
			$order_data += array(
				'plan_interval'       => $plan->paymentPlanInterval(),
				'plan_interval_count' => $plan->paymentPlanIntervalCount(),
			);
		}
		$order_bundle = $application->Entity_Bundle( 'paymentgateway_order', $bundle->group, true );
		$order        = $application->Entity_Save(
			$order_bundle,
			array(
				'author'               => $application->getUser()->id,
				'paymentgateway_order' => array(
					'parent_entity_id'   => $entity->getId(),
					'parent_entity_type' => $entity->getType(),
					'number'             => '',
					'action'             => $action,
					'status'             => $status,
					'plan_id'            => $plan->paymentPlanId(),
					'gateway'            => $gateway,
					'data'               => $order_data + $data,
					'notes'              => array(
						$application->PaymentGateway_Util_note(
							sprintf(
								__( 'New order for %1$s created with status %2$s.', 'directories-pro' ),
								'<em>' . $application->H( $plan->paymentPlanTitle() ) . '</em>',
								'<i>' . $application->H( $this->statusLabel( $application, $status ) ) . '</i>'
							)
						),
					),
				),
			)
		);

		if ( $plan->paymentPlanType() !== 'addon'
			&& $action !== 'claim'
			&& ! $entity->getAuthorId()
			&& $order->getAuthorId()
		) {
			// Guest has created an account on checkout
			try {
				$application->Entity_Save( $entity, array( 'author' => $order->getAuthorId() ) );
			} catch ( Exception\IException $e ) {
				$application->logError( 'Failed setting author for entity (ID: ' . $entity->getId() . ').' );
			}
		}

		// Create features
		$application->Payment_Features_create( $entity, $plan, $order->getId() );

		$application->Action( 'paymentgateway_order_created', array( $order ) );

		return $order;
	}

	public function getData( Application $application, IPlan $plan, array $fees = array(), array $discountCodes = array() ) {
		$payment_component = $application->Payment_Components_impl( 'default' );
		$settings          = $payment_component->getSettings();
		$price             = $plan->paymentPlanPrice();
		if ( ! empty( $fees ) ) {
			foreach ( array_keys( $fees ) as $fee_name ) {
				if ( ! isset( $fees[ $fee_name ]['price'] ) ) {
					unset( $fees[ $fee_name ] );
				}
			}
		}
		if ( ! empty( $settings['tax_enable'] )
			&& ! empty( $settings['tax']['rate'] )
		) {
			$tax = $settings['tax']['rate'];
		}

		$plan_entity = $plan->getPlan();
		if ( $discount = $application->PaymentGateway_Util_hasDiscount( $plan ) ) {
			$discountCodes[] = $discount->getSingleFieldValue( 'paymentgateway_discount', 'discount_code' );
		}
		if ( ! empty( $discountCodes ) ) {
			$plan_bundle     = $application->Entity_Bundle( $plan_entity, '', true );
			$discount_bundle = $application->Entity_Bundle( 'paymentgateway_discount', $plan_bundle->group, true );
			foreach ( array_keys( $discountCodes ) as $i ) {
				if ( ( ! $d = $application->Entity_Query( 'post', $discount_bundle->name )->fieldIs( 'paymentgateway_discount', $discountCodes[ $i ], 'discount_code' )->fetch( 1 ) )
					|| ( ! $d = array_shift( $d ) )
					|| ( ! $discountCodes[ $i ] = $application->PaymentGateway_Util_isValidDiscount( $d, $plan ) )
				) {
					throw new Exception\RuntimeException( sprintf( __( 'Invalid discount code: %s', 'directories-pro' ), $discountCodes[ $i ] ) );
				}
				if ( $discountCodes[ $i ]['discount_type'] === 'percentage'
					&& count( $discountCodes ) > 1
				) {
					throw new Exception\RuntimeException(
						sprintf(
							__( 'The discount (%s) cannot be used together with any other discounts.', 'directories-pro' ),
							$d->getTitle()
						)
					);
				}
				$discountCodes[ $i ] = array(
					'code'  => $discountCodes[ $i ]['discount_code'],
					'type'  => $discountCodes[ $i ]['discount_type'],
					'value' => $discountCodes[ $i ]['discount_value'],
					'label' => $d->getTitle(),
				);
			}
		}

		return array(
			'currency'  => $payment_component->paymentGetCurrency(),
			'price'     => $price,
			'tax'       => isset( $tax ) ? $tax : 0,
			'fees'      => $fees,
			'discounts' => $discountCodes,
		);
	}

	public function complete( Application $application, IEntity $order, $gateway = null, $gatewayOrderId = null, array $options = array() ) {
		$options['gateway']         = $gateway;
		$options['gateway_item_id'] = $gatewayOrderId;
		$this->updateStatus( $application, $order, 'completed', $options );
		$application->Payment_Util_triggerOrderEvent( 'completed', new PaymentOrder( $application, $order ) );
	}

	public function cancel( Application $application, IEntity $order, array $options = array() ) {
		$this->updateStatus( $application, $order, 'cancelled', $options );
		$application->Payment_Util_triggerOrderEvent( 'cancelled', new PaymentOrder( $application, $order ) );
	}

	public function refund( Application $application, IEntity $order, $reason, array $options = array() ) {
		$options['note'] = $reason;
		$this->updateStatus( $application, $order, 'refunded', $options );
		$application->Payment_Util_triggerOrderEvent( 'refunded', new PaymentOrder( $application, $order ) );
	}

	public function delete( Application $application, IEntity $order ) {
		$application->Entity_Types_impl( $order->getType() )->entityTypeDeleteEntities( array( $order->getId() => $order ) );
		if ( $entity = $application->Entity_ParentEntity( $order ) ) {
			$application->Payment_Features_delete( $entity->getBundleName(), $order->getId() );
		}
	}

	public function updateStatus( Application $application, IEntity $order, $status, array $options = array() ) {
		$value = $order->getSingleFieldValue( 'paymentgateway_order' );
		if ( isset( $value['status'] )
			&& $value['status'] === $status
		) {
			return;
		}

		$statuses = $this->statuses( $application );
		if ( ! isset( $statuses[ $status ] ) ) {
			throw new Exception\RuntimeException( 'Invalid status: ' . $status );
		}

		$options += array(
			'note'            => null,
			'user_id'         => null,
			'save'            => true,
			'gateway'         => null,
			'gateway_item_id' => null,
			'data'            => array(),
			'notes'           => null,
		);

		$old_status      = $value['status'];
		$value['status'] = $status;
		$note            = sprintf(
			__( 'Order status changed from %1$s to %2$s.', 'directories-pro' ),
			isset( $statuses[ $old_status ]['label'] ) ? '<i>' . $application->H( $statuses[ $old_status ]['label'] ) . '</i>' : '--',
			'<i>' . $application->H( $statuses[ $status ]['label'] ) . '</i>'
		);
		if ( isset( $options['note'] )
			&& strlen( $options['note'] )
		) {
			$note = sprintf( $options['note'], $note );
		}
		if ( isset( $options['notes'] )
			&& is_array( $options['notes'] )
		) {
			$value['notes'] = $options['notes'];
		}
		$value['notes'][]                = $application->PaymentGateway_Util_note( $note, null, $options['user_id'] );
		$value['data']                   = empty( $options['data'] ) || ! is_array( $options['data'] ) ? $value['data'] : $options['data'] + $value['data'];
		$value['data']['status_updated'] = time();
		if ( isset( $options['gateway'] ) ) {
			$value['gateway'] = $options['gateway'];
		}
		if ( isset( $options['gateway_item_id'] ) ) {
			$value['gateway_item_id'] = $options['gateway_item_id'];
		}
		if ( ! $options['save'] ) {
			return $value;
		}

		return $application->Entity_Save( $order, array( 'paymentgateway_order' => $value ) );
	}

	public function statusLabel( Application $application, $orderOrStatus, $html = false, $badge = false ) {
		if ( $orderOrStatus instanceof IEntity ) {
			if ( ! $status = $orderOrStatus->getSingleFieldValue( 'paymentgateway_order', 'status' ) ) {
				return;
			}
		} else {
			$status = $orderOrStatus;
		}

		$statuses = $this->statuses( $application );
		if ( ! isset( $statuses[ $status ] ) ) {
			return $status;
		}

		if ( ! $html ) {
			return $statuses[ $status ]['label'];
		}

		return $application->PaymentGateway_Util_renderLabel( $statuses[ $status ]['label'], $statuses[ $status ]['color'], $badge );
	}

	public function statuses( Application $application ) {
		return array(
			'pending_payment' => array(
				'label' => __( 'Pending payment', 'directories-pro' ),
				'color' => 'info',
			),
			'completed'       => array(
				'label' => __( 'Completed', 'directories-pro' ),
				'color' => 'success',
			),
			'cancelled'       => array(
				'label' => __( 'Cancelled', 'directories-pro' ),
				'color' => 'warning',
			),
			'failed'          => array(
				'label' => __( 'Failed', 'directories-pro' ),
				'color' => 'danger',
			),
		);
	}

	public function renderSummary( Application $application, $planOrOrder, ?IEntity $entity = null, $action = null ) {
		if ( $planOrOrder instanceof IEntity ) {
			if ( ! isset( $entity ) ) {
				$entity = $application->Entity_ParentEntity( $planOrOrder );
			}
			if ( ! $entity
				|| ( ! $order = $planOrOrder->getSingleFieldValue( 'paymentgateway_order' ) )
				|| empty( $order['plan_id'] )
				|| ( ! $payment_component = $application->Payment_Components_impl( 'default' ) )
				|| ( ! $plan = $payment_component->paymentGetPlanById( $entity->getBundleName(), $order['plan_id'] ) )
			) {
				return;
			}

			$currency    = $order['data']['currency'];
			$price       = $order['data']['price'];
			$tax         = $order['data']['tax'];
			$fees        = (array) $order['data']['fees'];
			$discounts   = (array) $order['data']['discounts'];
			$title       = $plan->paymentPlanTitle();
			$description = $plan->paymentPlanDescription();
		} elseif ( $planOrOrder instanceof IPlan ) {
			if ( ! isset( $entity )
				|| ! isset( $action )
			) {
				return;
			}

			$price    = $planOrOrder->paymentPlanPrice();
			$settings = $application->getComponent( 'Payment' )->getConfig( 'payment', 'components', 'default' );
			$currency = $settings['currency'];
			if ( ! empty( $settings['tax_enable'] )
				&& ! empty( $settings['tax']['rate'] )
			) {
				$tax = $settings['tax']['rate'];
			} else {
				$tax = 0;
			}
			$fees      = (array) $application->Payment_Fee_calculate( $entity, $planOrOrder, $action );
			$discounts = array();
			if ( $discount = $application->PaymentGateway_Util_hasDiscount( $planOrOrder ) ) {
				$discounts[] = array(
					'code'  => $discount->getSingleFieldValue( 'paymentgateway_discount', 'discount_code' ),
					'type'  => $discount->getSingleFieldValue( 'paymentgateway_discount', 'discount_type' ),
					'value' => $discount->getSingleFieldValue( 'paymentgateway_discount', 'discount_value' ),
					'label' => $discount->getTitle(),
				);
			}
			$title       = $planOrOrder->paymentPlanTitle();
			$description = $planOrOrder->paymentPlanDescription();
		} else {
			return;
		}
		return $application->PaymentGateway_Util_renderPaymentSummary( $title, $description, $price, $currency, $tax, $fees, $discounts );
	}

	public function message( Application $application, IEntity $order ) {
		return sprintf(
			$application->H( __( 'Order %1$s was placed on %2$s for item %3$s and is currently %4$s.', 'directories-pro' ) ),
			'<mark>' . $application->H( $order->getSingleFieldValue( 'paymentgateway_order', 'number' ) ) . '</mark>',
			'<mark>' . $application->System_Date( $order->getTimestamp(), true ) . '</mark>',
			'<mark>' . ( ( $entity = $application->Entity_ParentEntity( $order ) ) ? $entity->getTitle() : '--' ) . '</mark>',
			'<mark>' . $this->statusLabel( $application, $order, true ) . '</mark>'
		);
	}

	public function totalAmount( Application $application, IEntity $order, $includeFee = true, $includeDiscount = true, $format = false ) {
		$order_data = $order->getSingleFieldValue( 'paymentgateway_order', 'data' );
		$amount     = $order_data['price'];

		if ( $includeDiscount
			&& ! empty( $order_data['discounts'] )
		) {
			foreach ( $order_data['discounts'] as $discount ) {
				if ( ! is_array( $discount ) ) {
					continue;
				}

				if ( $discount['type'] === 'percentage' ) {
					$amount -= $amount * $discount['value'] / 100;
					break;
				} else {
					$amount -= $discount['value'];
				}
			}
		}

		if ( $includeFee
			&& ! empty( $order_data['fees'] )
		) {
			foreach ( $order_data['fees'] as $fee_name => $fee ) {
				if ( empty( $fee['is_discount'] ) ) {
					$amount += $fee['price'];
				} else {
					$amount -= $fee['price'];
				}
			}
		}

		if ( $amount < 0 ) {
			$amount = 0;
		}

		if ( $amount > 0
			&& ! empty( $order_data['tax'] )
		) {
			$amount = $amount * ( 1 + $order_data['tax'] / 100 );
		}
		if ( $format ) {
			$amount = $application->System_Currency_format( $amount, $order_data['currency'] );
		} elseif ( $currency_format = $application->System_Currency_formats( $order_data['currency'] ) ) {
				$amount = round( $amount, $currency_format[0] );
			if ( ! empty( $currency_format[0] ) ) {
				// Pad 0s or will not work with PayPal
				$amount = str_replace( ',', '', number_format( $amount, $currency_format[0] ) );
			}
		}

		return $amount;
	}
}
