<?php
/**
 * Form Processor
 *
 * @author    Wpayme <hi@wpayme.com>
 * @copyright 2024-2025 Wpayme
 * @license   GPL-3.0-or-later
 * @package   Wpayme\WordPress\Pay\Forms
 */

namespace Wpayme\WordPress\Pay\Forms;

use Exception;
use Wpayme\WordPress\Number\Number;
use Wpayme\WordPress\Money\Money;
use Wpayme\WordPress\Pay\Core\PaymentMethods;
use Wpayme\WordPress\Pay\ContactName;
use Wpayme\WordPress\Pay\Customer;
use Wpayme\WordPress\Pay\Payments\Payment;
use Wpayme\WordPress\Pay\Payments\PaymentLines;
use Wpayme\WordPress\Pay\Plugin;
use WP_Error;
use WP_User;

/**
 * Form Processor
 *
 * @author Remco Tolsma
 * @version 2.7.1
 * @since 3.7.0
 */
class FormProcessor {
	/**
	 * Construct form processor object.
	 */
	public function __construct() {
		// Actions.
		add_action( 'init', [ $this, 'init' ] );
	}

	/**
	 * Get amount.
	 *
	 * @return Money
	 */
	private function get_amount() {
		// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification in init method.
		$amount_string = 0;
		$applied_coupon = null;

		// Check if we have an applied coupon
		if (\array_key_exists('applied_coupon', $_POST) && !empty($_POST['applied_coupon'])) {
			// Sanitize the JSON string before decoding.
			$applied_coupon_json = sanitize_text_field( wp_unslash( $_POST['applied_coupon'] ) );
			$applied_coupon = json_decode( $applied_coupon_json, true );
			
			// Validate that the decoded data is an array and contains expected keys.
			if ( ! is_array( $applied_coupon ) ) {
				$applied_coupon = null;
			} else {
				// Sanitize and validate the coupon data.
				$applied_coupon = [
					'id'             => isset( $applied_coupon['id'] ) ? absint( $applied_coupon['id'] ) : 0,
					'discount_type'  => isset( $applied_coupon['discount_type'] ) ? sanitize_text_field( $applied_coupon['discount_type'] ) : '',
					'discount_amount' => isset( $applied_coupon['discount_amount'] ) ? filter_var( $applied_coupon['discount_amount'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION ) : 0,
				];
				
				// Validate discount_type is one of the allowed values.
				if ( ! in_array( $applied_coupon['discount_type'], [ 'percentage', 'fixed' ], true ) ) {
					$applied_coupon = null;
				}
			}
		}

		// Check if we have a calculated amount that includes quantity and other factors
		if (\array_key_exists('wpayme_pay_calculated_amount', $_POST)) {
			$calculated_amount = \sanitize_text_field(\wp_unslash($_POST['wpayme_pay_calculated_amount']));
			
			if (!empty($calculated_amount) && is_numeric($calculated_amount)) {
				$amount_string = $calculated_amount;
			}
		}
		
		// If no calculated amount, use the amount field
		if (empty($amount_string) || $amount_string == 0) {
			if (\array_key_exists('amount', $_POST)) {
				$amount_string = \sanitize_text_field(\wp_unslash($_POST['amount']));
			} elseif (\array_key_exists('wpayme_pay_amount', $_POST)) {
				$amount_string = \sanitize_text_field(\wp_unslash($_POST['wpayme_pay_amount']));

				if ('other' === $amount_string || 'custom' === $amount_string) {
					$amount_string = \array_key_exists('wpayme_pay_custom_amount', $_POST) ? 
						\sanitize_text_field(\wp_unslash($_POST['wpayme_pay_custom_amount'])) * 100 : 0;
				}
			}
		}

		$number = Number::from_string((string) $amount_string);

		// Get currency if available
		$currency = 'EUR'; // Default currency
		if (\array_key_exists('currency', $_POST)) {
			$selected_currency = \sanitize_text_field(\wp_unslash($_POST['currency']));
			if (!empty($selected_currency)) {
				$currency = $selected_currency;
			}
		} elseif (\array_key_exists('wpayme_pay_currency', $_POST)) {
			$selected_currency = \sanitize_text_field(\wp_unslash($_POST['wpayme_pay_currency']));
			if (!empty($selected_currency)) {
				$currency = $selected_currency;
			}
		}

		// phpcs:enable WordPress.Security.NonceVerification.Missing

		$money = new Money($number, $currency);

		// Apply coupon discount if available
		if ($applied_coupon) {
			// Increment coupon usage
			if (isset($applied_coupon['id'])) {
				CouponPostType::increment_usage($applied_coupon['id']);
			}

			// Apply discount
			if (isset($applied_coupon['discount_type']) && isset($applied_coupon['discount_amount'])) {
				$discount_type = $applied_coupon['discount_type'];
				$discount_amount = $applied_coupon['discount_amount'];

				if ($discount_type === 'percentage') {
					$discount = $money->multiply((float) $discount_amount / 100);
					$money = $money->subtract($discount);
				} else {
					$discount = new Money(Number::from_string((string) $discount_amount), $currency);
					$money = $money->subtract($discount);
				}

				// Don't allow negative amounts
				if ($money->get_value() < 0) {
					$money = new Money(Number::from_string('0'), $currency);
				}
			}
		}

		return $money;
	}

	/**
	 * Initialize.
	 *
	 * @return void
	 * @throws Exception When processing form fails on creating WordPress user.
	 */
	public function init() {
		global $wpayme_pay_errors;

		$wpayme_pay_errors = [];

		// Form submit.
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		if ( ! \array_key_exists( 'wpayme_pay', $_POST ) ) {
			return;
		}

		// Verify nonce.
		if ( ! isset( $_POST['wpayme_pay_nonce'] ) || ! \wp_verify_nonce( \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_nonce'] ) ), 'wpayme_pay_submit_form' ) ) {
			return;
		}

		// Validate.
		$valid = $this->validate();

		if ( ! $valid ) {
			return;
		}

		// Source.
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$source    = array_key_exists( 'wpayme_pay_source', $_POST ) ? \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_source'] ) ) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Missing
		$source_id = array_key_exists( 'wpayme_pay_source_id', $_POST ) ? (int) \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_source_id'] ) ) : 0;

		if ( ! FormsSource::is_valid( $source ) ) {
			return;
		}

		// Config ID.
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
		$config_id = filter_input( INPUT_POST, 'wpayme_pay_config_id', \FILTER_SANITIZE_NUMBER_INT );
		
		// Payment Method.
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
		$payment_method = array_key_exists( 'wpayme_pay_method', $_POST ) ? \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_method'] ) ) : null;

		if ( FormsSource::PAYMENT_FORM === $source ) {
			$config_id = get_post_meta( $source_id, '_wpayme_payment_form_config_id', true );
		}

		/*
		 * Start payment.
		 */
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
		$first_name = array_key_exists( 'wpayme_pay_first_name', $_POST ) ? \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_first_name'] ) ) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
		$last_name  = array_key_exists( 'wpayme_pay_last_name', $_POST ) ? \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_last_name'] ) ) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
		$email      = filter_input( INPUT_POST, 'wpayme_pay_email', FILTER_VALIDATE_EMAIL );
		$order_id   = (string) time();

		$description = null;

		if ( FormsSource::PAYMENT_FORM === $source ) {
			$description = get_post_meta( $source_id, '_wpayme_payment_form_description', true );

			if ( ! empty( $description ) ) {
				$description = sprintf( '%s %s', $description, $order_id );
			}
			
			// Add variant information to the description if available
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
			if ( \array_key_exists( 'wpayme_pay_variant', $_POST ) ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
				$variant = \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_variant'] ) );
				if ( ! empty( $variant ) ) {
					$description .= ' - ' . $variant;
				}
			}
		}

		if ( empty( $description ) ) {
			$description = sprintf(
				/* translators: %s: order id */
				__( 'Payment Form %s', 'wpayme' ),
				$order_id
			);
		}

		$payment = new Payment();

		$payment->title = sprintf(
			/* translators: %s: payment data title */
			__( 'Payment for %s', 'wpayme' ),
			$description
		);

		$payment->set_config_id( $config_id );
		$payment->set_description( $description );
		$payment->set_origin_id( $source_id );

		$payment->order_id  = $order_id;
		$payment->source    = $source;
		$payment->source_id = $source_id;

		// Name.
		$name = null;

		if ( ! empty( $first_name ) || ! empty( $last_name ) ) {
			$name = new ContactName();

			if ( ! empty( $first_name ) ) {
				$name->set_first_name( $first_name );
			}

			if ( ! empty( $last_name ) ) {
				$name->set_last_name( $last_name );
			}
		}

		// Customer.
		$customer = null;

		if ( null !== $name || ! empty( $email ) ) {
			$customer = new Customer();

			$customer->set_name( $name );

			if ( ! empty( $email ) ) {
				$customer->set_email( $email );
			}
		}

		$payment->set_customer( $customer );

		// Amount.
		$payment->set_total_amount( $this->get_amount() );

		// Method.
		$payment->set_payment_method( $payment_method );

		// Payment lines.
		$payment->lines = new PaymentLines();

		$line = $payment->lines->new_line();

		// Get quantity if available
		$quantity = 1;
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
		if ( \array_key_exists( 'wpayme_pay_quantity', $_POST ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere or not strictly required for this read.
			$qty = \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_quantity'] ) );
			if ( ! empty( $qty ) && is_numeric( $qty ) && $qty > 0 ) {
				$quantity = (int) $qty;
			}
		}

		// Calculate unit price (total amount divided by quantity)
		$total_amount = $payment->get_total_amount();
		$unit_price = $quantity > 0 ? $total_amount->divide( $quantity ) : $total_amount;

		// Set line properties.
		$line->set_id( strval( $order_id ) );
		$line->set_name( $description );
		$line->set_quantity( $quantity );
		$line->set_unit_price( $unit_price );
		$line->set_total_amount( $total_amount );

		// Gateway.
		$gateway = Plugin::get_gateway( $config_id );

		if ( null === $gateway ) {
			return;
		}

		// Start payment.
		try {
			$payment = Plugin::start_payment( $payment );
		} catch ( \Exception $e ) {
			Plugin::render_exception( $e );

			exit;
		}

		$gateway->redirect( $payment );

		exit;
	}

	/**
	 * Validate.
	 *
	 * @return boolean True if valid, false otherwise.
	 */
	private function validate() {
		global $wpayme_pay_errors;

		// Amount.
		try {
			$amount = $this->get_amount();
		} catch ( \Exception $e ) {
			$wpayme_pay_errors['amount'] = __( 'Please enter a valid amount', 'wpayme' );
		}

		// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification in init method.

		// First Name.
		if ( \array_key_exists( 'wpayme_pay_first_name', $_POST ) ) {
			$first_name = \sanitize_text_field( \wp_unslash( $_POST['wpayme_pay_first_name'] ) );

			if ( empty( $first_name ) ) {
				$wpayme_pay_errors['first_name'] = __( 'Please enter your first name', 'wpayme' );
			}
		}

		// phpcs:enable WordPress.Security.NonceVerification.Missing

		// E-mail.
		$email = filter_input( INPUT_POST, 'wpayme_pay_email', FILTER_VALIDATE_EMAIL );

		if ( empty( $email ) ) {
			$wpayme_pay_errors['email'] = __( 'Please enter a valid email address', 'wpayme' );
		}

		return empty( $wpayme_pay_errors );
	}

	/**
	 * Get payment amount with coupon discount applied if valid.
	 *
	 * @param int $form_id The form ID.
	 * @param array $coupon_data Coupon data.
	 * @return \Wpayme\WordPress\Money\Money The payment amount.
	 */
	private function get_payment_amount($form_id, $coupon_data = array()) {
		// Get the base amount
		$amount = $this->get_amount();
		
		// Apply coupon discount if available
		if (!empty($coupon_data) && isset($coupon_data['code'])) {
			// Validate the coupon again
			$coupon = CouponPostType::validate_coupon($coupon_data['code'], $form_id);
			
			if ($coupon) {
				// Apply the discount using our updated method
				$amount_value = $amount->get_value();
				$discounted_value = CouponPostType::apply_discount($coupon, $amount_value);
				
				// Create a new Money object with the discounted amount
				$amount = new \Wpayme\WordPress\Money\Money(
					\Wpayme\WordPress\Number\Number::from_string((string) $discounted_value), 
					$amount->get_currency()->get_code()
				);
			}
		}
		
		return $amount;
	}

	/**
	 * Process payment form.
	 */
	public function process_form() {
		// Verify nonce.
		\check_admin_referer('wpayme_pay_submit_form', 'wpayme_pay_nonce');
		
		// Handle coupon if applied.
		$applied_coupon = isset($_POST['wpayme_pay_applied_coupon']) ? \sanitize_text_field(\wp_unslash($_POST['wpayme_pay_applied_coupon'])) : '';
		$coupon_data = array();
		
		if (!empty($applied_coupon)) {
			$coupon_data = \json_decode(\stripslashes($applied_coupon), true);
			
			// Validate coupon again for security
			if (isset($coupon_data['code']) && isset($coupon_data['id'])) {
				$form_id = isset($_POST['form_id']) ? \intval($_POST['form_id']) : 0;
				$coupon = CouponPostType::validate_coupon($coupon_data['code'], $form_id);
				
				if (!$coupon) {
					// Coupon is no longer valid, ignore it
					$coupon_data = array();
				} else {
					// Increment usage count
					CouponPostType::increment_usage($coupon['id']);
				}
			}
		}
		
		$form_id = isset($_POST['form_id']) ? \absint($_POST['form_id']) : 0;
		
		if (empty($form_id)) {
			return;
		}
		
		$config_id = isset($_POST['wpayme_pay_config_id']) ? \absint($_POST['wpayme_pay_config_id']) : 0;
		
		if (empty($config_id)) {
			return;
		}
		
		$user = \wp_get_current_user();
		
		// Set description.
		$description = \get_the_title($form_id);
		
		// Create payment.
		$payment_method = isset($_POST['wpayme_pay_method']) ? \sanitize_text_field(\wp_unslash($_POST['wpayme_pay_method'])) : null;
		$issuer = isset($_POST['wpayme_pay_issuer']) ? \sanitize_text_field(\wp_unslash($_POST['wpayme_pay_issuer'])) : null;
		
		/**
		 * Filter payment method and issuer.
		 *
		 * @param string|null $payment_method Payment method or null.
		 * @param string|null $issuer Issuer ID or null.
		 */
		$payment_method = \apply_filters('wpayme_pay_forms_payment_method', $payment_method, $issuer);
		$issuer = \apply_filters('wpayme_pay_forms_payment_issuer', $issuer, $payment_method);
		
		// Start payment.
		$gateway = $this->gateway_factory->get_gateway($config_id);
		
		// Currency.
		$currency = isset($_POST['wpayme_pay_currency']) ? \sanitize_text_field(\wp_unslash($_POST['wpayme_pay_currency'])) : 'EUR';
		
		if (!empty($currency)) {
			$gateway->set_payment_currency($currency);
		}
		
		/**
		 * New payment.
		 *
		 * @return \Wpayme\Core\Payments\Payment
		 */
		$payment = $gateway->start_payment();
		
		if (null !== $payment_method) {
			$payment->set_method($payment_method);
		}
		
		if (null !== $issuer) {
			$payment->set_issuer($issuer);
		}
		
		// Customer data.
		if (isset($_POST['wpayme_pay_customer_name'])) {
			$payment->set_customer_name(\sanitize_text_field(\wp_unslash($_POST['wpayme_pay_customer_name'])));
		}
		
		if (isset($_POST['wpayme_pay_customer_email'])) {
			$payment->set_customer_email(\sanitize_email(\wp_unslash($_POST['wpayme_pay_customer_email'])));
		}
		
		// Set total amount.
		$money = $this->get_payment_amount($form_id, $coupon_data);

		$payment->set_total_amount($money);
		
		// Set meta data.
		$payment->set_meta('description', $description);
		$payment->set_meta('form_id', $form_id);
		
		// Add coupon data to payment meta if a coupon was applied
		if (!empty($coupon_data)) {
			$payment->set_meta('coupon_code', $coupon_data['code']);
			$payment->set_meta('coupon_discount_type', $coupon_data['discount_type']);
			$payment->set_meta('coupon_discount_amount', $coupon_data['discount_amount']);
		}
		
		/**
		 * Action before processing payment form.
		 *
		 * @param \Wpayme\Core\Payments\Payment $payment The payment object.
		 * @param array $post_data Sanitized post data.
		 */
		// Sanitize POST data before passing to action hook.
		$sanitized_post_data = [];
		if ( is_array( $_POST ) ) {
			foreach ( $_POST as $key => $value ) {
				$sanitized_key = sanitize_key( $key );
				if ( is_array( $value ) ) {
					$sanitized_post_data[ $sanitized_key ] = array_map( 'sanitize_text_field', $value );
				} else {
					$sanitized_post_data[ $sanitized_key ] = sanitize_text_field( wp_unslash( $value ) );
				}
			}
		}
		\do_action( 'wpayme_pay_forms_before_payment', $payment, $sanitized_post_data );
		
		// Set payment description.
		$quantity = 1;
		
		if (\array_key_exists('wpayme_pay_quantity', $_POST)) {
			$qty = \sanitize_text_field(\wp_unslash($_POST['wpayme_pay_quantity']));
			if (!empty($qty) && \is_numeric($qty) && $qty > 0) {
				$quantity = (int) $qty;
			}
		}
		
		// Add line item.
		$line_item = $payment->new_line();
		
		$line_item->set_name($description);
		$line_item->set_quantity($quantity);
		
		// Divide total amount by quantity.
		$line_money = $money->divide($quantity);
		
		$line_item->set_unit_price($line_money);
		$line_item->set_total_amount($money);
		
		$payment->add_line($line_item);
		
		// Process payment.
		$gateway->handle_payment($payment);
	}
}
