<?php
/**
 * Stripe Credit Card Gateway.
 *
 * Provides a credit card payment gateway.
 *
 * @class       CreditCard
 * @extends     PaymentGateway
 * @version     2.0.0
 * @package     Masteriyo\Classes\Payment
 */

namespace Masteriyo\Addons\Stripe;

use Exception;
use Masteriyo\Abstracts\PaymentGateway;
use Masteriyo\Contracts\PaymentGateway as PaymentGatewayInterface;
use Masteriyo\Enums\OrderItemType;
use Masteriyo\Pro\Enums\SubscriptionStatus;
use Masteriyo\Pro\Models\Subscription;
use Stripe\Customer;
use Stripe\PaymentIntent;
use Stripe\Price;
use Stripe\Stripe;
use Stripe\Subscription as StripeSubscription;

defined( 'ABSPATH' ) || exit;

/**
 * Stripe credit card Class.
 */
#[\AllowDynamicProperties]
class CreditCard extends PaymentGateway implements PaymentGatewayInterface {

	/**
	 * Payment gateway name.
	 *
	 * @since 2.0.0
	 *
	 * @var string
	 */
	protected $name = 'stripe';

	/**
	 * True if the gateway shows fields on the checkout.
	 *
	 * @since 2.0.0
	 *
	 * @var bool
	 */
	protected $has_fields = true;

	/**
	 * Whether or not logging is enabled
	 *
	 * @var bool
	 */
	public static $log_enabled = false;

	/**
	 * Logger instance
	 *
	 * @var Logger
	 */
	public static $log = false;

	/**
	 * Supported features such as 'default_credit_card_form', 'refunds'.
	 *
	 * @since 2.6.10
	 *
	 * @var array
	 */
	protected $supports = array( 'course', 'subscription' );


	/**
	 * Constructor for the gateway.
	 *
	 * @since 2.0.0
	 */
	public function __construct() {
		$this->order_button_text = __( 'Confirm Payment', 'learning-management-system' );
		$this->method_title      = __( 'Stripe (Credit Card)', 'learning-management-system' );
		/* translators: %s: Link to Masteriyo system status page */
		$this->method_description = __( 'CreditCard Standard redirects customers to CreditCard to enter their payment information.', 'learning-management-system' );

		// Load the settings.
		$this->init_settings();

		// Define user set variables.
		$this->title       = Setting::get_title();
		$this->description = Setting::get_description();
		$this->sandbox     = Setting::is_sandbox_enable();
		$this->debug       = false;
		self::$log_enabled = $this->debug;

		if ( $this->sandbox ) {
			/* translators: %s: Link to CreditCard sandbox testing guide page */
			$this->description .= ' ' . sprintf( __( 'SANDBOX ENABLED.', 'learning-management-system' ) );
			$this->description  = trim( $this->description );
		}

		if ( $this->enabled ) {
			add_filter( 'masteriyo_thankyou_order_received_text', array( $this, 'order_received_text' ), 10, 2 );
		}
	}

	/**
	 * Logging method.
	 *
	 * @since 2.0.0
	 *
	 * @param string $message Log message.
	 * @param string $level Optional. Default 'info'. Possible values:
	 *                      emergency|alert|critical|error|warning|notice|info|debug.
	 */
	public static function log( $message, $level = 'info' ) {
	}

	/**
	 * Other methods.
	 */

	/**
	 * Init settings for gateways.
	 *
	 * @since 2.0.0
	 */
	public function init_settings() {
		$this->enabled = Setting::is_enable();
	}

	/**
	 * Process the payment and return the result.
	 *
	 * @since 2.0.0
	 *
	 * @param  int $order_id Order ID.
	 * @return array
	 */
	public function process_payment( $order_id ) {
		masteriyo_get_logger()->info( 'Stripe process_payment: ' . $order_id, array( 'source' => 'payment-stripe' ) );
		try {
			Stripe::setApiKey( Setting::get_secret_key() );

			$order   = masteriyo_get_order( $order_id );
			$session = masteriyo( 'session' );

			if ( ! $order ) {
				masteriyo_get_logger()->error( 'Stripe process_payment: Order not found', array( 'source' => 'payment-stripe' ) );
				throw new Exception( __( 'Invalid order ID or order does not exist', 'learning-management-system' ) );
			}

			if ( ! $session ) {
				masteriyo_get_logger()->error( 'Stripe process_payment: Session not found', array( 'source' => 'payment-stripe' ) );
				throw new Exception( __( 'Session not found.', 'learning-management-system' ) );
			}

			$payment_intent_id = $session->get( 'stripe_payment_intent_id' );
			$order->set_transaction_id( $payment_intent_id );
			$order->save();

			// Update payment intent with order id.
			PaymentIntent::update(
				$payment_intent_id,
				array(
					'receipt_email' => $order->get_billing_email(),
					'metadata'      => array( 'order_id' => $order->get_id() ),
					'amount'        => Helper::convert_cart_total_to_stripe_amount( $order->get_total(), $order->get_currency() ),
				)
			);

			masteriyo_get_logger()->info( 'Payment intent updated.', array( 'source' => 'payment-stripe' ) );

			if ( masteriyo_order_contains_recurring_courses( $order ) ) {
				masteriyo_get_logger()->info( 'Order contains recurring courses.', array( 'source' => 'payment-stripe' ) );
				$courses             = array_map(
					function( $order_item ) {
						return $order_item->get_course();
					},
					$order->get_items()
				);
				$expire_after        = current( $courses )->get_billing_expire_after();
				$price_objects       = $this->create_price_objects( $courses, $order->get_currency(), $order->get_total() );
				$stripe_customer     = $this->create_stripe_customer();
				$stripe_subscription = $this->create_stripe_subscription( $stripe_customer, $price_objects, $expire_after );
				$subscription        = false;

				// Check whether the subscription is already created and attached to the order.
				$subscription = masteriyo_get_subscription_from_order( $order );

				if ( $subscription ) {
					$subscription = masteriyo_get_subscription( $subscription->get_id() );
				}

				if ( ! $subscription ) {
					$subscription = $this->create_subscription( $order, $stripe_subscription );
				}

				if ( ! $subscription ) {
					masteriyo_get_logger()->error( 'Stripe process_payment: Subscription not found', array( 'source' => 'payment-stripe' ) );
					throw new Exception( __( 'Unable to create subscription', 'learning-management-system' ) );
				}

				PaymentIntent::update(
					$payment_intent_id,
					array(
						'receipt_email' => $order->get_billing_email(),
						'metadata'      => array(
							'subscription_id' => $subscription->get_id(),
						),
					)
				);

				\Stripe\Subscription::update(
					$stripe_subscription->id,
					array(
						'metadata' => array(
							'subscription_id' => $subscription->get_id(),
						),
					)
				);
				masteriyo_get_logger()->info( 'Subscription updated.', array( 'source' => 'payment-stripe' ) );
			}
			masteriyo_get_logger()->info( 'Stripe process_payment: Success', array( 'source' => 'payment-stripe' ) );
			return array(
				'result'   => 'success',
				'redirect' => $this->get_return_url( $order ),
			);
		} catch ( Exception $e ) {
			masteriyo_get_logger()->error( $e->getMessage(), array( 'source' => 'payment-stripe' ) );
		}
	}

	/**
	 * Process refund.
	 *
	 * If the gateway declares 'refund' support, this will allow it to refund.
	 * a passed in amount.
	 *
	 * @since 2.0.0
	 *
	 * @param  int        $order_id Order ID.
	 * @param  float|null $amount Refund amount.
	 * @param  string     $reason Refund reason.
	 * @return boolean True or false based on success, or a WP_Error object.
	 */
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
		return false;
	}

	/**
	 * Custom CreditCard order received text.
	 *
	 * @since 2.0.0
	 * @param string   $text Default text.
	 * @param \Masteriyo\Models\Order\Order $order Order data.
	 * @return string
	 */
	public function order_received_text( $text, $order ) {
		masteriyo_get_logger()->info( 'Stripe order_received_text : Start', array( 'source' => 'payment-stripe' ) );
		if ( $order && $this->name === $order->get_payment_method() ) {
			masteriyo_get_logger()->info( 'Stripe order_received_text: Success', array( 'source' => 'payment-stripe' ) );
			return esc_html__( 'Thank you for your payment. Your transaction has been completed, and a receipt for your purchase has been emailed to you. Log into your Stripe account to view transaction details.', 'learning-management-system' );
		}

		return $text;
	}

	/**
	 * Display fields.
	 */
	public function payment_fields() {
		$description = $this->get_description();

		if ( $description ) {
			echo wp_kses_post( wpautop( wptexturize( $description ) ) );
		}

		echo '<div id="masteriyo-stripe-method" style="width: 100%; height: 228px;" ><div id="masteriyo-stripe-payment-element"></div></div>';
	}

	/**
	 * Create subscription model.
	 *
	 * @since 2.6.10
	 *
	 * @param \Masteriyo\Models\Order\Order $order
	 * @param \Stripe\Subscription $stripe_subscription
	 */
	public function create_subscription( $order, $stripe_subscription ) {
		masteriyo_get_logger()->info( 'Stripe create_subscription: Start', array( 'source' => 'payment-stripe' ) );
		$order_data = $order->get_data();
		unset( $order_data['id'] );

		$subscription = Subscription::instance();
		$subscription->set_props( $order_data );

		$order_item = current( $order->get_items() );
		$course     = masteriyo_get_course( $order_item->get_course_id() );

		$subscription->set_props(
			array(
				'billing_period'          => $course->get_billing_period(),
				'billing_interval'        => $course->get_billing_interval(),
				'billing_expire_after'    => $course->get_billing_expire_after(),
				'requires_manual_renewal' => false,
				'status'                  => SubscriptionStatus::ACTIVE,
				'parent_id'               => $order->get_id(),
				'subscription_id'         => $stripe_subscription->id,
				'recurring_amount'        => $order->get_total(),
			)
		);

		foreach ( $order->get_items( OrderItemType::all() ) as $order_item ) {
			$subscription->add_item( $order_item );
		}

		$subscription->save();

		masteriyo_get_logger()->info( 'Stripe create_subscription: Success', array( 'source' => 'payment-stripe' ) );

		return $subscription;
	}

	/**
	 * Create a stripe price objects for subscription.
	 *
	 * @since 2.6.10
	 *
	 * @param \Masteriyo\Models\Course[] $courses
	 * @param string $currency_code
	 * @param float|string $total
	 *
	 * @return array
	 */
	protected function create_price_objects( $courses, $currency_code, $total ) {
		masteriyo_get_logger()->info( 'Stripe create_price_objects: Start', array( 'source' => 'payment-stripe' ) );
		$price_objects = array_map(
			function( $course ) use ( $currency_code, $total ) {
				return Price::create(
					array(
						'currency'     => $currency_code,
						'unit_amount'  => masteriyo_round( $total * 100, 2 ),
						'recurring'    => array(
							'interval_count' => $course->get_billing_interval(),
							'interval'       => $course->get_billing_period(),
						),
						'product_data' => array(
							'name'     => $course->get_name(),
							'metadata' => array(
								'course_id'            => $course->get_id(),
								'billing_interval'     => $course->get_billing_interval(),
								'billing_period'       => $course->get_billing_period(),
								'billing_expire_after' => $course->get_billing_expire_after(),
							),
						),
					)
				);
			},
			$courses
		);

		masteriyo_get_logger()->info( 'Stripe create_price_objects: Success', array( 'source' => 'payment-stripe' ) );

		return array_values( $price_objects );
	}

	/**
	 * Create or return stripe customer id.
	 *
	 * @since 2.6.10
	 *
	 * @return object
	 */
	protected function create_stripe_customer() {
		masteriyo_get_logger()->info( 'Stripe create_stripe_customer: Start', array( 'source' => 'payment-stripe' ) );
		$user               = masteriyo_get_current_user();
		$stripe_customer_id = get_user_meta( $user->get_id(), '_stripe_customer', true );
		$stripe_customer    = null;

		if ( ! empty( $stripe_customer_id ) ) {
			$stripe_customer = Customer::retrieve( $stripe_customer_id );
		}

		if ( ! $stripe_customer ) {
			$stripe_customer = Customer::create(
				array(
					'phone'    => $user->get_billing_phone(),
					'email'    => $user->get_billing_email(),
					'name'     => $user->get_billing_first_name() . ' ' . $user->get_billing_last_name(),
					'address'  => $user->get_billing_address(),
					'metadata' => array(
						'customer_id'    => $user->get_id(),
						'customer_email' => $user->get_email(),
					),
				)
			);

			update_post_meta( $user->get_id(), '_stripe_customer', $stripe_customer->id );
		}

		masteriyo_get_logger()->info( 'Stripe create_stripe_customer: Success', array( 'source' => 'payment-stripe' ) );
		masteriyo_get_logger()->info( 'Stripe create_stripe_customer: Customer ID: ' . $stripe_customer->id, array( 'source' => 'payment-stripe' ) );

		return $stripe_customer;
	}


	/**
	 * Create subscription.
	 *
	 * @since 2.6.10
	 *
	 * @param object $stripe_customer
	 * @param array $stripe_objects
	 * @param int $expire_after Expire/Cancel subscription after months.
	 */
	protected function create_stripe_subscription( $stripe_customer, $price_objects, $expire_after ) {
		masteriyo_get_logger()->info( 'Stripe create_stripe_subscription: Start', array( 'source' => 'payment-stripe' ) );

		if ( empty( $price_objects ) ) {
			masteriyo_get_logger()->info( 'Stripe create_stripe_subscription: No price objects', array( 'source' => 'payment-stripe' ) );
			return;
		}

		$items = array_map(
			function( $price_object ) {
				return array(
					'price' => $price_object->id,
				);
			},
			$price_objects
		);

		$subscription = StripeSubscription::create(
			array(
				'customer'         => $stripe_customer->id,
				'items'            => $items,
				'payment_behavior' => 'default_incomplete',
				'payment_settings' => array( 'save_default_payment_method' => 'on_subscription' ),
				'expand'           => array( 'latest_invoice.payment_intent' ),
				'cancel_at'        => $expire_after ? time() + intval( $expire_after ) * MONTH_IN_SECONDS : null,
			)
		);

		masteriyo_get_logger()->info( 'Stripe create_stripe_subscription: Success', array( 'source' => 'payment-stripe' ) );

		return $subscription;
	}
}
