<?php
if ( ! defined( 'ABSPATH' ) ) {
	die( 'You are not allowed to call this page directly.' );
}

class FrmTransLitePaymentsController extends FrmTransLiteCRUDController {

	/**
	 * @return void
	 */
	public static function menu() {
		$frm_settings = FrmAppHelper::get_settings();

		// Remove the PayPal submenu (PayPal payments will just appear in the regular Payments page).
		remove_action( 'admin_menu', 'FrmPaymentsController::menu', 26 );

		if ( in_array( FrmAppHelper::simple_get( 'action' ), array( 'edit', 'new', 'bulk_delete' ), true ) && is_callable( 'FrmPaymentsController::route' ) ) {
			// Use the PayPal addon for add new and edit routing if it is active.
			// This is required to support the "edit" link when using the Stripe Lite table view.
			// It is also required for the "Add New" button to work on the payments table page.
			$menu_route = 'FrmPaymentsController::route';
		} else {
			$menu_route = 'FrmTransLitePaymentsController::route';
		}

		$payments_string = __( 'Payments', 'formidable' );
		add_submenu_page(
			'formidable',
			$frm_settings->menu . ' | ' . $payments_string,
			self::payments_menu_title( $payments_string ),
			'frm_view_entries',
			'formidable-payments',
			$menu_route
		);
	}

	/**
	 * @since 6.11.1
	 *
	 * @param string $payments_string
	 *
	 * @return string
	 */
	private static function payments_menu_title( $payments_string ) {
		ob_start();
		echo esc_html( $payments_string );
		FrmAppHelper::show_pill_text();
		return ob_get_clean();
	}

	/**
	 * @return void
	 */
	public static function route() {
		$action     = isset( $_REQUEST['frm_action'] ) ? 'frm_action' : 'action';
		$action     = FrmAppHelper::get_param( $action, '', 'get', 'sanitize_title' );
		$type       = FrmAppHelper::get_param( 'type', '', 'get', 'sanitize_title' );
		$class_name = $type === 'subscriptions' ? 'FrmTransLiteSubscriptionsController' : 'FrmTransLitePaymentsController';

		if ( method_exists( $class_name, $action ) ) {
			$class_name::$action();
			return;
		}

		FrmTransLiteListsController::route( $action );
	}

	/**
	 * @param object $payment
	 *
	 * @return void
	 */
	public static function load_sidebar_actions( $payment ) {
		FrmTransLiteActionsController::actions_js();

		$date_format = __( 'M j, Y @ G:i', 'formidable' );
		$created_at  = FrmAppHelper::get_localized_date( $date_format, $payment->created_at );
		include FrmTransLiteAppHelper::plugin_path() . '/views/payments/sidebar_actions.php';
	}

	/**
	 * Echo a receipt link.
	 *
	 * @param object $payment
	 *
	 * @return void
	 */
	public static function show_receipt_link( $payment ) {
		$link   = esc_html( $payment->receipt_id );
		$paysys = $payment->paysys;

		if ( $payment->receipt_id !== 'None' && self::should_filter_receipt_link( $paysys ) ) {
			/**
			 * Filter a receipt link for a specific gateway.
			 * For example, Stripe uses frm_pay_stripe_receipt.
			 *
			 * @param string $link
			 */
			$link = apply_filters( 'frm_pay_' . $paysys . '_receipt', $link );
		}

		FrmAppHelper::kses_echo( $link, array( 'a' ) );
	}

	/**
	 * @param string $paysys
	 *
	 * @return bool
	 */
	private static function should_filter_receipt_link( $paysys ) {
		$allowed_types = array( 'stripe', 'authnet_aim' );
		return in_array( $paysys, $allowed_types, true );
	}

	/**
	 * Echo a refund link.
	 *
	 * @param object $payment
	 *
	 * @return void
	 */
	public static function show_refund_link( $payment ) {
		$link = self::refund_link( $payment );
		FrmTransLiteAppHelper::echo_confirmation_link( $link );
	}

	/**
	 * Show a link to a payment entry (unless it is deleted).
	 *
	 * @param object $payment
	 *
	 * @return void
	 */
	public static function show_entry_link( $payment ) {
		$entry = FrmDb::get_col( 'frm_items', array( 'id' => $payment->item_id ) );

		if ( ! $entry ) {
			// translators: %d: Entry ID.
			echo esc_html( sprintf( __( '%d (Deleted)', 'formidable' ), $payment->item_id ) );
			return;
		}

		// phpcs:disable Generic.WhiteSpace.ScopeIndent
		?>
		<a href="?page=formidable-entries&amp;action=show&amp;frm_action=show&amp;id=<?php echo absint( $payment->item_id ); ?>">
			<?php echo absint( $payment->item_id ); ?>
		</a>
		<?php
		// phpcs:enable Generic.WhiteSpace.ScopeIndent
	}

	/**
	 * Get a refund link.
	 *
	 * @param object $payment
	 *
	 * @return string
	 */
	public static function refund_link( $payment ) {
		if ( $payment->status === 'refunded' ) {
			$link = esc_html__( 'Refunded', 'formidable' );
		} else {
			$confirm = __( 'Are you sure you want to refund that payment?', 'formidable' );
			$link    = admin_url( 'admin-ajax.php?action=frm_trans_refund&payment_id=' . $payment->id . '&nonce=' . wp_create_nonce( 'frm_trans_ajax' ) );
			$link    = '<a href="' . esc_url( $link ) . '" class="frm_trans_ajax_link" data-frmverify="' . esc_attr( $confirm ) . '">';
			$link   .= esc_html__( 'Refund', 'formidable' );
			$link   .= '</a>';
		}

		$paysys = $payment->paysys;

		if ( self::should_filter_refund_link( $paysys ) ) {
			/**
			 * Filter the refund link for a specific gateway.
			 * For example, Stripe uses frm_pay_stripe_refund_link.
			 *
			 * @param string $link
			 * @param object $payment
			 */
			return apply_filters( 'frm_pay_' . $paysys . '_refund_link', $link, $payment );
		}

		return $link;
	}

	/**
	 * @param string $paysys
	 *
	 * @return bool
	 */
	private static function should_filter_refund_link( $paysys ) {
		$allowed_types = array( 'stripe', 'authnet_aim' );
		return in_array( $paysys, $allowed_types, true );
	}

	/**
	 * Process the ajax request to refund a payment.
	 *
	 * @return void
	 */
	public static function refund_payment() {
		FrmAppHelper::permission_check( 'frm_edit_entries' );
		check_ajax_referer( 'frm_trans_ajax', 'nonce' );

		$payment_id = FrmAppHelper::get_param( 'payment_id', '', 'get', 'absint' );

		if ( ! $payment_id ) {
			wp_die( esc_html__( 'Oops! No payment was selected for refund.', 'formidable' ) );
		}

		$payment = ( new FrmTransLitePayment() )->get_one( $payment_id );

		if ( ! $payment ) {
			wp_die( esc_html__( 'Oops! That payment does not exist.', 'formidable' ) );
		}

		$refunded = false;
		$reason   = '';
		$debug_id = '';
		$paysys   = $payment->paysys;

		switch ( $paysys ) {
			case 'stripe':
				$refunded = FrmStrpLiteAppHelper::call_stripe_helper_class( 'refund_payment', $payment->receipt_id );
				break;
			case 'square':
				$refunded = FrmSquareLiteConnectHelper::refund_payment( $payment->receipt_id );
				break;
			case 'paypal':
				$response = FrmPayPalLiteConnectHelper::refund_payment( $payment->receipt_id );

				// Check for structured error response with message and debug_id
				if ( is_object( $response ) && isset( $response->message ) && isset( $response->debug_id ) ) {
					$refunded = false;
					$reason   = $response->message;
					$debug_id = $response->debug_id;
				} elseif ( false === $response ) {
					$refunded = false;
					$reason   = self::get_paypal_refund_reason();
					$debug_id = FrmPayPalLiteConnectHelper::get_latest_debug_id_from_paypal_api();
				} elseif ( is_object( $response ) && isset( $response->refund_error ) ) {
					// Handle mock error responses from PayPal API
					$refunded = false;
					$reason   = $response->message ?? '';
					$debug_id = $response->debug_id ?? '';
				} else {
					$refunded = true;
				}

				break;
			default:
				$refunded = false;
				break;
		}//end switch

		if ( $refunded ) {
			self::change_payment_status( $payment, 'refunded' );
			$message = __( 'Refunded', 'formidable' );
			// phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
		} else {
			// If the reason is already a complete error message, use it directly
			// instead of wrapping it redundantly in "Refund Failed (...)"
			if ( $reason && ! preg_match( '/^[A-Z_]+$/', $reason ) ) {
				$message = $reason;
			} else {
				$message = __( 'Refund Failed', 'formidable' );

				if ( $reason ) {
					$message .= ' (' . $reason . ')';
				}
			}
		}

		if ( $debug_id ) {
			$message .= '<br><br>Debug ID: ' . esc_html( $debug_id );
		}

		wp_die(
			sprintf(
				'<div class="%1$s">%2$s</div>',
				$refunded ? 'frm_updated_message' : 'frm_error_style',
				wp_kses_post( $message )
			)
		);
	}

	/**
	 * Get a human-friendly reason from the latest PayPal refund error.
	 *
	 * Handles both uppercase issue codes (e.g. REFUND_FAILED_INSUFFICIENT_FUNDS)
	 * and human-friendly description strings from the PayPal API.
	 * Strips the {{debug_id:...}} token if present.
	 *
	 * @since 6.31
	 *
	 * @return string
	 */
	private static function get_paypal_refund_reason() {
		$error = FrmPayPalLiteConnectHelper::get_latest_error_from_paypal_api();

		if ( ! $error ) {
			return '';
		}

		$error = preg_replace( '/\{\{debug_id:[^}]+\}\}/', '', $error );
		$error = trim( $error );

		if ( preg_match( '/^[A-Z_]+$/', $error ) ) {
			return self::convert_uppercase_underscores_to_ucwords( $error, array( 'REFUND_FAILED_', 'REFUND_' ) );
		}

		return $error;
	}

	/**
	 * @param string $error             The uppercase underscored string to convert.
	 * @param array  $prefixes_to_strip Prefixes to remove before converting.
	 *
	 * @return string
	 */
	private static function convert_uppercase_underscores_to_ucwords( $error, $prefixes_to_strip = array() ) {
		if ( ! preg_match( '/^[A-Z_]+$/', $error ) ) {
			return '';
		}

		$reason = str_replace( $prefixes_to_strip, '', $error );
		return ucwords( strtolower( str_replace( '_', ' ', $reason ) ) );
	}

	/**
	 * Update the status of a payment.
	 *
	 * @param object $payment
	 * @param string $status
	 *
	 * @return void
	 */
	public static function change_payment_status( $payment, $status ) {
		if ( $status === $payment->status ) {
			// The payment status has not actually changed.
			return;
		}

		$frm_payment = new FrmTransLitePayment();
		$frm_payment->update( $payment->id, array( 'status' => $status ) );
		FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
	}

	/**
	 * @since 6.31
	 *
	 * @param array|string $expected_gateways
	 * @param array|string $selected_gateways
	 *
	 * @return void
	 */
	public static function maybe_hide_payment_setting( $expected_gateways, $selected_gateways ) {
		if ( ! array_intersect( (array) $expected_gateways, (array) $selected_gateways ) ) {
			echo ' frm_hidden';
		}
	}
}
