<?php
/**
 * Stripe Frontend - Frontend payment processing
 *
 * @package SureDonation
 */

namespace SureDonation\Inc\Payments\Stripe;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use SureDonation\Inc\Database\Tables\Donations;
use SureDonation\Inc\Database\Tables\Donors;
use SureDonation\Inc\Helper;
use SureDonation\Inc\Payments\Payment_Helper;
use SureDonation\Inc\Traits\Get_Instance;

/**
 * Stripe_Frontend class
 * Handles frontend Stripe payment processing
 *
 * @since 0.0.1
 */
class Stripe_Frontend {
	use Get_Instance;

	/**
	 * Constructor
	 *
	 * @since 0.0.1
	 */
	public function __construct() {
		// AJAX handlers for both logged in and non-logged in users.
		add_action( 'wp_ajax_suredonation_create_payment_intent', [ $this, 'create_payment_intent' ] );
		add_action( 'wp_ajax_nopriv_suredonation_create_payment_intent', [ $this, 'create_payment_intent' ] );

		// AJAX handler for completing donation after payment confirmation (captures and updates status).
		add_action( 'wp_ajax_suredonation_complete_donation', [ $this, 'complete_donation' ] );
		add_action( 'wp_ajax_nopriv_suredonation_complete_donation', [ $this, 'complete_donation' ] );

		// Enqueue Stripe.js on frontend.
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_stripe_scripts' ] );
	}

	/**
	 * Enqueue Stripe.js
	 *
	 * @return void
	 * @since 0.0.1
	 */
	public function enqueue_stripe_scripts() {
		// Only enqueue on pages with donation form.
		if ( ! has_block( 'suredonation/donation-form' ) ) {
			return;
		}

		// Register Stripe.js library from CDN.
		// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Stripe CDN ignores version; version param is included to keep linter happy.
		wp_enqueue_script(
			'stripe-js',
			'https://js.stripe.com/v3/',
			[],
			SUREDONATION_VER,
			true
		);

		// Pass publishable key to frontend.
		$publishable_key = Stripe_Helper::get_stripe_publishable_key();

		wp_localize_script(
			'suredonation-donation-form',
			'suredonationStripe',
			[
				'publishableKey' => $publishable_key,
				'ajaxUrl'        => admin_url( 'admin-ajax.php' ),
				'mode'           => Payment_Helper::get_payment_mode(),
			]
		);

		// Pass currency and form data to frontend.
		$currency = Payment_Helper::get_currency();

		wp_localize_script(
			'suredonation-donation-form',
			'suredonationData',
			[
				'ajaxurl'        => admin_url( 'admin-ajax.php' ),
				'nonce'          => wp_create_nonce( 'suredonation_donation_form' ),
				'currency'       => $currency,
				'currencySymbol' => Payment_Helper::get_currency_symbol( $currency ),
				'isZeroDecimal'  => Payment_Helper::is_zero_decimal_currency( $currency ),
			]
		);
	}

	/**
	 * Create Payment Intent via AJAX
	 *
	 * @return void
	 * @since 0.0.1
	 */
	public function create_payment_intent() {
		// Verify nonce.
		check_ajax_referer( 'suredonation_donation_form', 'nonce' );

		// Get form data.
		$campaign_id = isset( $_POST['campaign_id'] ) ? absint( $_POST['campaign_id'] ) : 0;
		$amount      = isset( $_POST['amount'] ) ? floatval( $_POST['amount'] ) : 0;
		$base_amount = isset( $_POST['base_amount'] ) ? floatval( $_POST['base_amount'] ) : $amount;
		$cover_fees  = isset( $_POST['cover_fees'] ) && 'true' === $_POST['cover_fees'];
		$donor_email = isset( $_POST['donor_email'] ) ? sanitize_email( wp_unslash( $_POST['donor_email'] ) ) : '';
		$donor_name  = isset( $_POST['donor_name'] ) ? sanitize_text_field( wp_unslash( $_POST['donor_name'] ) ) : '';

		// Get form_id and block_id for secure amount validation.
		$form_id  = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
		$block_id = isset( $_POST['block_id'] ) ? sanitize_text_field( wp_unslash( $_POST['block_id'] ) ) : '';

		// Calculate fees covered if donor opted to cover transaction fees.
		$fees_covered = 0.0;
		if ( $cover_fees && $base_amount > 0 ) {
			$fees_covered = $amount - $base_amount;
			// Ensure fees_covered is positive and matches expected calculation.
			if ( $fees_covered < 0 ) {
				$fees_covered = 0.0;
			}
		}

		// Validate required fields.
		if ( empty( $campaign_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid campaign', 'suredonation' ) ] );
		}

		if ( empty( $donor_email ) ) {
			wp_send_json_error( [ 'message' => __( 'Email address is required', 'suredonation' ) ] );
		}

		// Get currency for validation.
		$currency = Payment_Helper::get_currency();

		if ( $form_id > 0 && ! empty( $block_id ) ) {
			$donation_amount = $amount - $fees_covered;

			$validation_result = Payment_Helper::validate_payment_amount( $donation_amount, $currency, $form_id, $block_id );
			if ( ! $validation_result['valid'] ) {
				wp_send_json_error( [ 'message' => esc_html( $validation_result['message'] ) ] );
			}

			// Additional security: Verify that the fee calculation is legitimate.
			if ( $cover_fees && $fees_covered > 0 ) {
				$fee_percentage = 0.029; // Default 2.9%.
				$fee_fixed      = 0.30;  // Default $0.30.
				$expected_fees  = ( $donation_amount * $fee_percentage ) + $fee_fixed;
				$fee_difference = abs( $fees_covered - $expected_fees );

				// Allow small difference (0.02) for rounding.
				if ( $fee_difference > 0.02 ) {
					wp_send_json_error( [ 'message' => __( 'Invalid fee calculation.', 'suredonation' ) ] );
				}
			}
		}

		// Check if Stripe is connected.
		if ( ! Stripe_Helper::is_stripe_connected() ) {
			wp_send_json_error( [ 'message' => __( 'Payment gateway not configured', 'suredonation' ) ] );
		}

		// Get or create Stripe customer.
		$customer_id = $this->get_or_create_stripe_customer( $donor_email, $donor_name );

		if ( is_wp_error( $customer_id ) ) {
			wp_send_json_error( [ 'message' => esc_html( $customer_id->get_error_message() ) ] );
		}

		// Get campaign details.
		$campaign = get_post( $campaign_id );
		if ( ! $campaign || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
			wp_send_json_error( [ 'message' => __( 'Invalid campaign', 'suredonation' ) ] );
		}

		// Check campaign is published and active.
		if ( 'publish' !== $campaign->post_status ) {
			wp_send_json_error( [ 'message' => __( 'This campaign is not available for donations.', 'suredonation' ) ] );
		}

		$campaign_status = Helper::get_campaign_meta_value( $campaign_id, 'campaign_status', 'active' );
		if ( 'paused' === $campaign_status || 'completed' === $campaign_status ) {
			wp_send_json_error( [ 'message' => __( 'This campaign is not currently accepting donations.', 'suredonation' ) ] );
		}

		// Convert amount to Stripe format (cents).
		$amount_in_cents = Payment_Helper::amount_to_stripe_format( $amount, $currency );

		// Convert base amount and fees to cents for metadata.
		$base_amount_cents  = Payment_Helper::amount_to_stripe_format( $base_amount, $currency );
		$fees_covered_cents = Payment_Helper::amount_to_stripe_format( $fees_covered, $currency );

		// Create Payment Intent via middleware (handles platform fees automatically).
		$intent_data = [
			'amount'        => $amount_in_cents,
			'currency'      => strtolower( $currency ),
			'customer'      => $customer_id,
			'receipt_email' => $donor_email,
			'description'   => sprintf(
				// translators: %s is the campaign title.
				__( 'Donation to %s', 'suredonation' ),
				$campaign->post_title
			),
			'metadata'      => [
				'campaign_id'     => $campaign_id,
				'donor_email'     => $donor_email,
				'donor_name'      => $donor_name,
				'original_amount' => $amount_in_cents, // Store original amount for verification.
				'base_amount'     => $base_amount_cents,
				'fees_covered'    => $fees_covered_cents,
				'cover_fees'      => $cover_fees ? 'yes' : 'no',
			],
		];

		// Use middleware for payment intent creation (handles platform fees based on license).
		$payment_intent = Stripe_Helper::create_payment_intent_via_middleware( $intent_data );

		if ( is_wp_error( $payment_intent ) ) {
			wp_send_json_error( [ 'message' => esc_html( $payment_intent->get_error_message() ) ] );
		}

		// Extract payment intent ID - ensure string type.
		$payment_intent_id = isset( $payment_intent['id'] ) && is_string( $payment_intent['id'] ) ? $payment_intent['id'] : '';
		if ( empty( $payment_intent_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid payment intent response.', 'suredonation' ) ] );
		}

		// Security: Store payment intent metadata for webhook verification.
		// This allows the webhook to verify the actual charged amount matches expected.
		Payment_Helper::store_payment_intent_metadata(
			$payment_intent_id,
			[
				'amount_cents' => $amount_in_cents,
				'currency'     => $currency,
				'campaign_id'  => $campaign_id,
				'created_at'   => time(),
			]
		);

		// Create pending donation record with transaction ID and customer ID.
		$donation_id = $this->create_pending_donation(
			$campaign_id,
			$amount,
			$donor_email,
			$donor_name,
			$payment_intent_id,
			$customer_id,
			$fees_covered
		);

		if ( is_wp_error( $donation_id ) ) {
			wp_send_json_error( [ 'message' => esc_html( $donation_id->get_error_message() ) ] );
		}

		// Return client secret to frontend.
		wp_send_json_success(
			[
				'clientSecret' => $payment_intent['client_secret'],
				'donationId'   => $donation_id,
			]
		);
	}

	/**
	 * Complete donation via AJAX after successful payment confirmation.
	 *
	 * This method follows the SureForms pattern where after Stripe.confirmPayment()
	 * succeeds on the frontend, the server-side code:
	 * 1. Captures the payment via middleware (since capture_method='manual')
	 * 2. Updates the donation status from 'pending' to 'completed'
	 *
	 * @return void
	 * @since 0.0.1
	 */
	public function complete_donation() {
		// Verify nonce.
		check_ajax_referer( 'suredonation_donation_form', 'nonce' );

		// Get payment intent ID and donation ID.
		$payment_intent_id = isset( $_POST['payment_intent_id'] ) ? sanitize_text_field( wp_unslash( $_POST['payment_intent_id'] ) ) : '';
		$donation_id       = isset( $_POST['donation_id'] ) ? absint( $_POST['donation_id'] ) : 0;

		if ( empty( $payment_intent_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Payment intent ID is required', 'suredonation' ) ] );
		}

		if ( empty( $donation_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Donation ID is required', 'suredonation' ) ] );
		}

		// Validate payment intent ID format.
		if ( ! preg_match( '/^pi_[a-zA-Z0-9]+$/', $payment_intent_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid payment intent ID', 'suredonation' ) ] );
		}

		// Verify donation exists and matches the payment intent.
		$donation = Donations::get( $donation_id );
		if ( ! $donation ) {
			wp_send_json_error( [ 'message' => __( 'Donation not found', 'suredonation' ) ] );
		}

		// Verify transaction ID matches.
		if ( $donation['transaction_id'] !== $payment_intent_id ) {
			wp_send_json_error( [ 'message' => __( 'Payment verification failed', 'suredonation' ) ] );
		}

		// Capture the payment via middleware.
		$capture_result = Stripe_Helper::capture_payment_intent_via_middleware( $payment_intent_id );

		if ( is_wp_error( $capture_result ) ) {
			wp_send_json_error( [ 'message' => esc_html( $capture_result->get_error_message() ) ] );
		}

		// Check if payment was captured successfully.
		$payment_status = isset( $capture_result['status'] ) && is_string( $capture_result['status'] ) ? $capture_result['status'] : '';

		if ( ! in_array( $payment_status, [ 'succeeded', 'requires_capture' ], true ) ) {
			wp_send_json_error( [ 'message' => __( 'Payment capture failed', 'suredonation' ) ] );
		}

		// Update donation status to completed.
		$update_result = Donations::update(
			$donation_id,
			[
				'payment_status' => 'completed',
			]
		);

		if ( ! $update_result ) {
			// Payment was captured but status update failed - log it but don't fail the request.
			// The webhook will handle this as a backup.
			Donations::add_log(
				$donation_id,
				'warning',
				__( 'Payment captured but donation status update failed', 'suredonation' ),
				[
					'payment_status' => $payment_status,
				]
			);
		} else {
			// Add success log entry.
			Donations::add_log(
				$donation_id,
				'completed',
				__( 'Payment captured and donation completed', 'suredonation' ),
				[
					'payment_status'    => $payment_status,
					'payment_intent_id' => $payment_intent_id,
				]
			);
		}

		wp_send_json_success(
			[
				'status'            => $payment_status,
				'payment_intent_id' => $payment_intent_id,
				'donation_id'       => $donation_id,
			]
		);
	}

	/**
	 * Get or create Stripe customer based on email.
	 *
	 * This method checks for existing customers in the following order:
	 * 1. WordPress user meta (for logged-in users)
	 * 2. Donors table (for returning donors)
	 * 3. Creates a new Stripe customer if not found
	 *
	 * Note: Cached customer IDs are verified with Stripe to handle mode switches
	 * (test/live) or deleted customers gracefully.
	 *
	 * @param string $email Customer email.
	 * @param string $name  Customer name.
	 * @return string|\WP_Error Customer ID or error.
	 * @since 0.0.1
	 */
	private function get_or_create_stripe_customer( $email, $name ) {
		$user                 = get_user_by( 'email', $email );
		$existing_customer_id = null;

		// 1. Check if customer exists in WordPress user meta (logged-in users).
		if ( $user ) {
			$customer_id = get_user_meta( $user->ID, '_stripe_customer_id', true );

			if ( ! empty( $customer_id ) && is_string( $customer_id ) ) {
				$existing_customer_id = $customer_id;
			}
		}

		// 2. Check if customer exists in donors table (returning donors).
		if ( empty( $existing_customer_id ) ) {
			$existing_customer_id = Donors::get_stripe_customer_id_by_email( $email );
		}

		// 3. Verify the existing customer still exists in Stripe.
		if ( ! empty( $existing_customer_id ) ) {
			$verified = Stripe_Helper::verify_customer_exists( $existing_customer_id );

			if ( $verified ) {
				// Customer exists, save to user meta if logged in for faster lookup.
				if ( $user ) {
					update_user_meta( $user->ID, '_stripe_customer_id', $existing_customer_id );
				}
				return $existing_customer_id;
			}

			// Customer doesn't exist in Stripe (deleted or mode switch), clear cached ID.
			if ( $user ) {
				delete_user_meta( $user->ID, '_stripe_customer_id' );
			}
			Donors::clear_stripe_customer_id_by_email( $email );
		}

		// 4. Create new Stripe customer.
		$customer_data = [
			'email' => $email,
			'name'  => $name,
		];

		$customer = Stripe_Helper::create_customer( $customer_data );

		if ( is_wp_error( $customer ) ) {
			return $customer;
		}

		$customer_id = isset( $customer['id'] ) && is_string( $customer['id'] ) ? $customer['id'] : '';

		if ( empty( $customer_id ) ) {
			return new \WP_Error( 'customer_creation_failed', __( 'Failed to create customer', 'suredonation' ) );
		}

		// Save customer ID to user meta if logged in.
		if ( $user ) {
			update_user_meta( $user->ID, '_stripe_customer_id', $customer_id );
		}

		// Save customer ID to donors table for future lookups.
		Donors::set_stripe_customer_id_by_email( $email, $customer_id );

		return $customer_id;
	}

	/**
	 * Create pending donation record in database table
	 *
	 * @param int    $campaign_id    Campaign ID.
	 * @param float  $amount         Donation amount (total including fees if covered).
	 * @param string $donor_email    Donor email.
	 * @param string $donor_name     Donor name.
	 * @param string $transaction_id Stripe payment intent ID.
	 * @param string $customer_id    Stripe customer ID.
	 * @param float  $fees_covered   Transaction fees covered by donor.
	 * @return int|\WP_Error Donation ID or error.
	 * @since 0.0.1
	 */
	private function create_pending_donation( $campaign_id, $amount, $donor_email, $donor_name, $transaction_id, $customer_id, $fees_covered = 0.0 ) {
		// Get or create donor.
		$donor_id = Donors::get_or_create( $donor_email, $donor_name );

		// Create donation in database table.
		$donation_id = Donations::add(
			[
				'campaign_id'    => $campaign_id,
				'donor_id'       => $donor_id ? $donor_id : 0,
				'amount'         => $amount,
				'fees_covered'   => $fees_covered,
				'currency'       => Payment_Helper::get_currency(),
				'gateway'        => 'stripe',
				'payment_status' => 'pending',
				'payment_mode'   => Payment_Helper::get_payment_mode(),
				'donor_name'     => $donor_name,
				'donor_email'    => $donor_email,
				'donation_type'  => 'one-time',
				'transaction_id' => $transaction_id,
				'customer_id'    => $customer_id,
				'ip_address'     => Helper::get_client_ip(),
				'user_agent'     => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
				'referer_url'    => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '',
			]
		);

		if ( ! $donation_id ) {
			return new \WP_Error( 'donation_failed', __( 'Failed to create donation record', 'suredonation' ) );
		}

		// Add initial log entry.
		Donations::add_log(
			$donation_id,
			'created',
			__( 'Donation created with pending payment', 'suredonation' ),
			[
				'transaction_id' => $transaction_id,
				'customer_id'    => $customer_id,
			]
		);

		return $donation_id;
	}
}
