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

use SabaiApps\Directories\Component\Entity\Type\IEntity;
use SabaiApps\Directories\Component\Payment\IOrder;
use SabaiApps\Directories\Component\Payment\IPlan;
use SabaiApps\Directories\Component\PaymentGateway\Model\Plan;
use SabaiApps\Directories\Context;
use SabaiApps\Directories\Exception\RuntimeException;
use SabaiApps\Directories\Request;

class StripeGateway extends AbstractGateway {

	protected function _paymentGatewayInfo() {
		return array(
			'label'            => 'Stripe',
			'default_settings' => array(
				'pub_key'            => null,
				'sec_key'            => null,
				'test_enable'        => false,
				'test_pub_key'       => null,
				'test_sec_key'       => null,
				'checkout_btn_label' => __( 'Pay with Stripe', 'directories-pro' ),
				'webhook_secret'     => null,
			),
			'plan_types'       => array( 'base', 'addon', 'subscription' ),
		);
	}

	public function paymentGatewaySettingsForm( array $settings, array $parents ) {
		return array(
			'pub_key'            => array(
				'#title'            => __( 'Publishable key', 'directories-pro' ),
				'#type'             => 'textfield',
				'#default_value'    => $settings['pub_key'],
				'#required'         => function ( $form ) use ( $parents ) {
					return $form->getValue( array_merge( $parents, array( 'test_enable' ) ) ) ? false : true; },
				'#display_required' => false,
			),
			'sec_key'            => array(
				'#title'            => __( 'Secret key', 'directories-pro' ),
				'#type'             => 'textfield',
				'#default_value'    => $settings['sec_key'],
				'#required'         => function ( $form ) use ( $parents ) {
					return $form->getValue( array_merge( $parents, array( 'test_enable' ) ) ) ? false : true; },
				'#display_required' => false,
			),
			'test_enable'        => array(
				'#title'         => __( 'Enable test mode', 'directories-pro' ),
				'#type'          => 'checkbox',
				'#default_value' => ! empty( $settings['test_enable'] ),
			),
			'test_pub_key'       => array(
				'#title'         => __( 'Test publishable key', 'directories-pro' ),
				'#type'          => 'textfield',
				'#default_value' => $settings['test_pub_key'],
				'#required'      => function ( $form ) use ( $parents ) {
					return (bool) $form->getValue( array_merge( $parents, array( 'test_enable' ) ) ); },
				'#states'        => array(
					'visible' => array(
						sprintf( '[name="%s"]', $this->_application->Form_Form_fieldName( array_merge( $parents, array( 'test_enable' ) ) ) ) => array(
							'type'  => 'checked',
							'value' => true,
						),
					),
				),
			),
			'test_sec_key'       => array(
				'#title'         => __( 'Test secret key', 'directories-pro' ),
				'#type'          => 'textfield',
				'#default_value' => $settings['test_sec_key'],
				'#required'      => function ( $form ) use ( $parents ) {
					return (bool) $form->getValue( array_merge( $parents, array( 'test_enable' ) ) ); },
				'#states'        => array(
					'visible' => array(
						sprintf( '[name="%s"]', $this->_application->Form_Form_fieldName( array_merge( $parents, array( 'test_enable' ) ) ) ) => array(
							'type'  => 'checked',
							'value' => true,
						),
					),
				),
			),
			'checkout_btn_label' => array(
				'#title'         => __( 'Checkout button label', 'directories-pro' ),
				'#type'          => 'textfield',
				'#default_value' => $settings['checkout_btn_label'],
				'#placeholder'   => $this->paymentGatewayInfo( 'default_settings' )['checkout_btn_label'],
			),
			'webhook_secret'     => array(
				'#title'                 => __( 'Webhook signing secret', 'directories-pro' ),
				'#type'                  => 'textfield',
				'#default_value'         => $settings['webhook_secret'],
				'#description'           => sprintf( $this->_application->H( __( 'The webhook endpoint URL is %s.', 'directories-pro' ) ), '<code class="' . DRTS_BS_PREFIX . 'user-select-all">' . $this->_application->MainUrl( '/_drts/paymentgateway/webhook', array( 'gateway' => 'stripe' ) ) . '</code>' ),
				'#description_no_escape' => true,
			),
		);
	}

	public function paymentGatewayCheckoutForm( Context $context, $planOrOrder, ?IEntity $entity = null, $action = null, array $parents = array(), $discountCodesFormFieldName = null ) {
		$settings    = $this->_getSettings();
		$button_id   = 'drts-payment-checkout-stripe-button';
		$session_url = (string) $this->_getCheckoutActionUrl( 'session', $context, 'json' );
		$js          = '
var stripe = Stripe("' . $this->_application->H( empty( $settings['test_enable'] ) ? $settings['pub_key'] : $settings['test_pub_key'] ) . '");
document.getElementById("' . $button_id . '").addEventListener("click", function(e) {
    e.preventDefault();
    DRTS.ajaxLoader(this);
    var form = e.target.closest("form");
    var inputs = form.querySelectorAll("input[name=\"' . $discountCodesFormFieldName . '[]\"]");
    var codes = Array.from(inputs).map(input => input.value);
    var formData = new FormData();
    codes.forEach(code => {
      formData.append("' . $discountCodesFormFieldName . '[]", code);
    });
    fetch("' . $session_url . '", {
        method: "POST",
        body: formData,
    })
    .then(function(response) {
        return response.json();
    })
    .then(function(session) {
        return stripe.redirectToCheckout({ sessionId: session.id });
    })
    .then(function(result) {
        if (result.error) {
            alert(result.error.message);
        }
    })
    .catch(function(error) {
        console.error("Error:", error);
    });
});';
		$this->_loadStripeJsFile();
		return array(
			'#type'     => 'item',
			'#markup'   => '<div class="' . DRTS_BS_PREFIX . 'd-grid"><button class="' . DRTS_BS_PREFIX . 'btn ' . DRTS_BS_PREFIX . 'btn-lg ' . DRTS_BS_PREFIX . 'btn-primary" id="' . $button_id . '" type="button">'
				. $this->_application->H( $settings['checkout_btn_label'] )
				. '</button></div>',
			'#js_ready' => $js,
		);
	}

	protected function _loadStripeJsFile() {
		$this->_application->getPlatform()->addJsFile( 'https://js.stripe.com/v3/', 'stripe', array(), false, false );
	}

	public function paymentGatewayOnLoadAssets() {
		$this->_loadStripeJsFile();
	}

	public function paymentGatewayOnCheckoutAction( Context $context, $checkoutAction, $planOrOrder = null, ?IEntity $entity = null, $action = null, array $discountCodes = array() ) {
		switch ( $checkoutAction ) {
			case 'session':
				if ( ! Request::isPostMethod() ) {
					return false;
				}

				if ( $planOrOrder instanceof IOrder ) {
					$order = $planOrOrder->getOrder();
					$plan  = $planOrOrder->paymentOrderPlan();
				} elseif ( $planOrOrder instanceof IPlan ) {
					if ( ! $order = $this->_createOrder( $planOrOrder, $entity, $action, $discountCodes ) ) {
						return false;
					}

					$plan = $planOrOrder;
				} else {
					return false;
				}

				$this->_initStripe();

				$session_data = array(
					'mode'                 => 'payment',
					'payment_method_types' => array( 'card' ),
					'metadata'             => array(
						'order_id' => $order->getId(),
					),
					'success_url'          => (string) $this->_getCheckoutActionUrl( 'success', $context ) . '&session_id={CHECKOUT_SESSION_ID}',
					'cancel_url'           => (string) $this->_getCheckoutActionUrl( 'cancel', $context ) . '&session_id={CHECKOUT_SESSION_ID}',
					'line_items'           => array(),
				);
				// Create customer if not exists
				if ( $customer_id = $this->_getCustomerId() ) {
					$session_data['customer'] = $customer_id;
				} else {
					$session_data['customer_email'] = $this->_application->getUser()->email ? $this->_application->getUser()->email : $this->_application->getPlatform()->getSiteEmail();
				}
				// Init price data and create session
				$order_data = $order->getSingleFieldValue( 'paymentgateway_order', 'data' );
				$currency   = strtolower( $order_data['currency'] );
				$price_data = array(
					'currency'    => $currency,
					'unit_amount' => $this->_toStripeUnitAmount( $this->_application->PaymentGateway_Order_totalAmount( $order, false ), $order_data['currency'] ),
				);
				if ( $plan->paymentPlanType() === 'subscription' ) {
					if ( empty( $order_data['discounts'] ) ) {
						// No discounts, create a recurring price
						$session_data['mode']    = 'subscription';
						$price_data['product']   = $this->_getProductId( $plan->getPlan() );
						$plan_data               = (array) $plan->getPlan()->getSingleFieldValue( 'paymentgateway_plan', 'data' );
						$price_data['recurring'] = array(
							'interval'       => $plan_data['interval'] ?? 'year',
							'interval_count' => $plan_data['interval_count'] ?? 1,
						);
					} else {
						// Discounts exist, create a one-time price
						$price_data['product_data'] = array(
							'name'        => $plan->getPlan()->getTitle(),
							'description' => sprintf( __( 'One-time discounted price for %s.', 'directories-pro' ), $plan->getPlan()->getTitle() ),
						);
					}
					if ( ! empty( $order_data['fees'] ) ) {
						foreach ( $order_data['fees'] as $fee_name => $fee ) {
							if ( ! empty( $fee['is_discount'] ) ) {
								continue;
							}

							$fee_amount = $fee['price'];
							if ( $order_data['tax'] ) {
								$fee_amount = $fee_amount * ( 1 + $order_data['tax'] / 100 );
							}
							$session_data['line_items'][] = array(
								'price_data' => array(
									'currency'     => $currency,
									'product_data' => array(
										'name'        => $fee_name,
										'description' => $this->_application->Payment_Fee_label( $fee_name ),
									),
									'unit_amount'  => $this->_toStripeUnitAmount( $fee_amount, $order_data['currency'] ),
								),
								'quantity'   => 1,
							);
						}
					}
					$session_data['line_items'][] = array(
						'price_data' => $price_data,
						'quantity'   => 1,
					);
				} else {
					$price_data['product']        = $this->_getProductId( $plan->getPlan() );
					$session_data['line_items'][] = array(
						'price_data' => $price_data,
						'quantity'   => 1,
					);
				}
				try {
					$session = \Stripe\Checkout\Session::create( $session_data );
				} catch ( \Exception $e ) {
					$this->_application->logError( $e->getMessage() );
					return false;
				}

				if ( ! $session ) {
					$this->_deleteOrder( $order );
					return false;
				}

				$context->addTemplate( 'system_list' )->setAttributes( array( 'list' => array( 'id' => $session->id ) ) );
				return true;

			case 'success':
			case 'cancel':
				if ( ! $session_id = $context->getRequest()->asStr( 'session_id' ) ) {
					return false;
				}

				$this->_initStripe();
				try {
					$session = \Stripe\Checkout\Session::retrieve( $session_id );
				} catch ( \Exception $e ) {
					$this->_application->logError( $e->getMessage() );
					return false;
				}
				if ( empty( $session->metadata->order_id )
					|| ( ! $order = $this->_application->Entity_Entity( 'post', $session->metadata->order_id ) )
				) {
					return false;
				}

				if ( $checkoutAction === 'success' ) {
					$context->paymentgateway_checkout_order_id = $order->getId();
				} else {
					if ( ! $this->_cancelOrder( $order, array( 'note' => __( 'Checkout process was cancelled.', 'directories-pro' ) ) ) ) {
						return false;
					}

					$context->paymentgateway_checkout_order_id = 0;
				}
				return true;
		}
	}

	protected function _toStripeUnitAmount( $amount, $currency ) {
		if ( ( $currency_format = $this->_application->System_Currency_formats( $currency ) )
			&& ! empty( $currency_format[0] )
		) {
			// Stripe expects an integer
			$amount *= pow( 10, $currency_format[0] );
		}
		return $amount;
	}

	protected function _getProductId( IEntity $plan ) {
		$settings  = $this->_getSettings();
		$data_key  = 'stripe_product_id' . ( empty( $settings['test_enable'] ) ? '_pub' : '_test' );
		$plan_info = $plan->getSingleFieldValue( 'paymentgateway_plan' );
		if ( empty( $plan_info['data'][ $data_key ] )
			|| ! $this->_productExists( $plan_info['data'][ $data_key ] )
		) {
			$product                        = \Stripe\Product::create(
				array(
					'name'        => $plan->getSlug(),
					'description' => $plan->getTitle(),
				)
			);
			$plan_info['data'][ $data_key ] = $product->id;
			$this->_application->Entity_Save(
				$plan,
				array( 'paymentgateway_plan' => $plan_info ),
			);
		}
		return $plan_info['data'][ $data_key ];
	}

	protected function _productExists( $product_id ) {
		try {
			$product = \Stripe\Product::retrieve( $product_id );
		} catch ( \Exception $e ) {
			return false;
		}
		return ! empty( $product->id );
	}

	protected function _getCustomerId() {
		if ( $this->_application->getUser()->isAnonymous()
			|| ! $this->_application->getUser()->email
		) {
			return;
		}

		$settings  = $this->_getSettings();
		$meta_name = 'paymentgateway_stripe_customer_id' . ( empty( $settings['test_enable'] ) ? '_pub' : '_test' );
		if ( ! $customer_id = $this->_application->getPlatform()->getEntityMeta( 'user', $this->_application->getUser()->id, $meta_name ) ) {
			$customer    = \Stripe\Customer::create(
				array(
					'email'    => $this->_application->getUser()->email,
					'name'     => $this->_application->getUser()->name,
					'metadata' => array(
						'id'       => $this->_application->getUser()->id,
						'username' => $this->_application->getUser()->username,
					),
				)
			);
			$customer_id = $customer->id;
			$this->_application->getPlatform()->setEntityMeta( 'user', $this->_application->getUser()->id, $meta_name, $customer_id );
		}
		return $customer_id;
	}

	public function paymentGatewayOrderUrl( IEntity $order ) {
		if ( ! $gateway_item_id = $order->getSingleFieldValue( 'paymentgateway_order', 'gateway_item_id' ) ) {
			return;
		}

		$settings = $this->_getSettings();
		$url      = empty( $settings['test_enable'] ) ? 'https://dashboard.stripe.com' : 'https://dashboard.stripe.com/test';
		if ( strpos( $gateway_item_id, 'sub_' ) === 0 ) {
			return $url . '/subscriptions/' . $gateway_item_id;
		}
		return $url . '/payments/' . $gateway_item_id;
	}

	public function paymentGatewayOnWebhook( Context $context ) {
		$settings = $this->_getSettings();
		if ( empty( $settings['webhook_secret'] ) ) {
			throw new RuntimeException( 'No Stripe webhook signing secret configured.' );
		}
		if ( ! $payload = @file_get_contents( 'php://input' ) ) {
			throw new RuntimeException( 'Invalid Stripe webhook payload.' );
		}

		$this->_initStripe();
		try {
			$stripe_event = \Stripe\Webhook::constructEvent( $payload, $_SERVER['HTTP_STRIPE_SIGNATURE'], $settings['webhook_secret'] );
		} catch ( \Exception $e ) {
			throw new RuntimeException( 'Stripe webhook error: ' . $e->getMessage() );
		}

		switch ( $stripe_event->type ) {
			case 'checkout.session.completed':
				$this->_application->logDebug( 'Stripe webhook ' . $stripe_event->type . ' received for order ID: ' . ( $session->metadata->order_id ?? 'N/A' ) );
				$session = $stripe_event->data->object;
				if ( empty( $session->metadata->order_id )
					|| ( ! $order = $this->_application->Entity_Entity( 'post', $session->metadata->order_id ) )
				) {
					throw new RuntimeException( 'Stripe webhook error: Order not found for order ID: ' . ( $session->metadata->order_id ?? 'N/A' ) );
				}
				if ( $order->getSingleFieldValue( 'paymentgateway_order', 'status' ) === 'completed' ) {
					return true;
				}
				if ( ! $order_data = $order->getSingleFieldValue( 'paymentgateway_order', 'data' ) ) {
					throw new RuntimeException( 'Stripe webhook error: Order data not found for order ID: ' . $order->getId() );
				}
				if ( ! $plan = $this->_application->Entity_Entity( 'post', $order->getSingleFieldValue( 'paymentgateway_order', 'plan_id' ) ) ) {
					throw new RuntimeException( 'Stripe webhook error: Plan not found for plan ID: ' . $order->getSingleFieldValue( 'paymentgateway_order', 'plan_id' ) );
				}

				switch ( $session->payment_status ) {
					case 'paid':
					case 'no_payment_required':
						$payment_intent = $session->payment_intent;
						if ( $plan->getSingleFieldValue( 'paymentgateway_plan', 'plan_type' ) === 'subscription' ) {
							if ( $session->mode !== 'subscription' ) {
								// Subscription plan but one-time payment (due to discount), create a subscription with first term as trial period
								$price = \Stripe\Price::create(
									array(
										'unit_amount' => $this->_toStripeUnitAmount( $this->_application->PaymentGateway_Order_totalAmount( $order, false, false ), $order_data['currency'] ),
										'currency'    => strtolower( $order_data['currency'] ),
										'recurring'   => array( 'interval' => $order_data['plan_interval'] ),
										'product'     => $this->_getProductId( $plan ),
									)
								);
								switch ( $order_data['plan_interval'] ) {
									case 'day':
										$trial_period_days = $order_data['plan_interval_count'] * 1;
										break;
									case 'week':
										$trial_period_days = $order_data['plan_interval_count'] * 7;
										break;
									case 'month':
										$trial_period_days = $order_data['plan_interval_count'] * 30;
										break;
									case 'year':
									default:
										$trial_period_days = $order_data['plan_interval_count'] * 365;
										break;
								}
								try {
									$subscription = \Stripe\Subscription::create(
										array(
											'customer' => $session->customer,
											'items'    => array(
												array(
													'price' => $price->id,
												),
											),
											'metadata' => array(
												'checkout_session_id' => $session->id,
											),
											'trial_period_days' => $trial_period_days,
										)
									);

								} catch ( \Stripe\Exception\ApiErrorException $e ) {
									throw new RuntimeException( 'Failed creating Stripe subscription for order (ID: ' . $order->getId() . '). Error: ' . $e->getMessage() );
								}
							} else {
								$subscription = null;
								if ( $session->subscription ) {
									try {
										$subscription     = \Stripe\Subscription::retrieve( $session->subscription );
										$invoice_payments = \Stripe\InvoicePayment::all(
											array(
												'invoice' => $subscription->latest_invoice,
											)
										);
										if ( count( $invoice_payments->data ) > 0 ) {
											$payment_intent = $invoice_payments->data[0]->payment->payment_intent;
										} else {
											$this->_application->logError( 'No Stripe invoice payments found for Stripe subscription (ID: ' . $subscription->id . ')' );
										}
									} catch ( \Exception $e ) {
										$this->_application->logError( 'Failed retrieving Stripe subscription or invoice payments for order (ID: ' . $order->getId() . '). Error: ' . $e->getMessage() );
									}
								} else {
									$this->_application->logError( 'No subscription ID found in Stripe checkout session for order (ID: ' . $order->getId() . ')' );
								}
							}
						} else {
							$subscription = null;
						}
						$this->_completeStripeOrder( $order, $payment_intent, $subscription );
						break;
					default:
						$this->_application->logWarning( 'Unexpected payment status: ' . $session->payment_status . ' for Stripe checkout session (ID: ' . $session->id . ')' );
						break;
				}
				break;
			case 'invoice.paid':
			case 'invoice.payment_failed':
				$invoice = $stripe_event->data->object;
				if ( empty( $invoice->parent->subscription_details->subscription ) ) {
					return;
				}

				$subscription_id = $invoice->parent->subscription_details->subscription;
				try {
					$subscription = \Stripe\Subscription::retrieve( $subscription_id );
				} catch ( \Exception $e ) {
					throw new RuntimeException( 'Stripe webhook error: ' . $e->getMessage() );
				}
				$this->_application->logDebug( 'Stripe webhook ' . $stripe_event->type . ' received for subscription (ID: ' . $subscription->id . ').' );
				if ( $stripe_event->type === 'invoice.paid' ) {
					$this->_application->PaymentGateway_Subscription_updateNextPaymentDate( $subscription->id, $subscription->current_period_end );
				} else {
					// The payment failed or the customer does not have a valid payment method.
					// The subscription becomes past_due. Notify your customer and send them to the
					// customer portal to update their payment information.
					if ( ! is_null( $invoice->next_payment_attempt ) ) {
						// will retry
					} else {
						// final
						$this->_application->PaymentGateway_Subscription_expire(
							$subscription->id,
							true,
							array( 'note' => __( 'Subscription payment failed by Stripe. %s', 'directories-pro' ) )
						);
					}
				}
				break;
			case 'customer.subscription.updated':
				$subscription = $stripe_event->data->object;
				$this->_application->logDebug( 'Stripe webhook ' . $stripe_event->type . ' received for subscription (ID: ' . $subscription->id . ').' );
				$this->_application->PaymentGateway_Subscription_updateNextPaymentDate( $subscription->id, $subscription->current_period_end );
				break;
			case 'customer.subscription.deleted':
				$subscription = $stripe_event->data->object;
				$this->_application->logDebug( 'Stripe webhook ' . $stripe_event->type . ' received for subscription (ID: ' . $subscription->id . ').' );
				$this->_application->PaymentGateway_Subscription_cancel(
					$subscription->id,
					! empty( $stripe_event->request ), // cancelled manually?
					true,
					array( 'note' => __( 'Subscription cancelled by Stripe. %s', 'directories-pro' ) )
				);
				break;
		}
	}

	protected function _completeStripeOrder( IEntity $order, $paymentIntent, ?\Stripe\Subscription $subscription = null ) {
		$data = array();
		if ( isset( $subscription ) ) {
			$data['subscription_id'] = $subscription->id;
			$data['next_payment_at'] = $subscription->trial_end ?? $subscription->current_period_end;
		}
		return $this->_completeOrder( $order, $paymentIntent, $data );
	}

	protected function _initStripe() {
		$settings = $this->_getSettings();
		\Stripe\Stripe::setApiKey( empty( $settings['test_enable'] ) ? $settings['sec_key'] : $settings['test_sec_key'] );
		\Stripe\Stripe::setApiVersion( $settings['api_version'] ?? '2025-08-27.basil' );
	}

	public function paymentGatewaySubscriptionUrl( IEntity $subscription ) {
		if ( ! $subscription_id = $subscription->getSingleFieldValue( 'paymentgateway_subscription', 'gateway_item_id' ) ) {
			return;
		}

		$settings = $this->_getSettings();
		$url      = empty( $settings['test_enable'] ) ? 'https://dashboard.stripe.com' : 'https://dashboard.stripe.com/test';
		return $url . '/subscriptions/' . $subscription_id;
	}

	public function paymentGatewayCancelSubscription( IEntity $subscription ) {
		$this->_initStripe();
		$subscription = \Stripe\Subscription::retrieve( $subscription->getSingleFieldValue( 'paymentgateway_subscription', 'gateway_item_id' ) );
		if ( $this->_application->getComponent( 'Payment' )->getConfig( 'plan', 'prorated_discount' ) ) {
			$time = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
			if ( $refund = $this->_prorateSubscription( $subscription, $time ) ) {
				$invoices = \Stripe\Invoice::all(
					array(
						'subscription' => $subscription->id,
					)
				);
				if ( $invoices->count() ) {
					$invoice = $invoices->data[0];
					\Stripe\Refund::create(
						array(
							'charge' => $invoice->charge,
							'amount' => $refund,
						)
					);
				}
			}
		}
		$subscription->cancel();
	}

	protected function _prorateSubscription( \Stripe\Subscription $subscription, \DateTime $time ) {
		$period_start = $subscription->current_period_start;
		$period_end   = $subscription->current_period_end;
		if ( ! $period_start || ! $period_end || $time->getTimestamp() >= $period_end ) {
			return 0;
		}

		$amount              = $subscription->plan->amount;
		$period_length       = $period_end - $period_start;
		$elapsed_since_start = $time->getTimestamp() - $period_start;
		$refund              = $amount - floor( ( $elapsed_since_start / $period_length ) * $amount );

		return $refund > 0 ? (int) $refund : 0;
	}
}
