<?php
/**
 * PayPal Admin Handler Class.
 *
 * @package sureforms-pro
 * @since 2.4.0
 */

namespace SRFM_Pro\Inc\Business\Payments\PayPal;

use SRFM\Inc\Database\Tables\Payments;
use SRFM\Inc\Helper;
use SRFM\Inc\Payments\Payment_Helper;
use SRFM\Inc\Traits\Get_Instance;
use SRFM_Pro\Inc\Business\Payments\PayPal\Helper as PayPal_Helper;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * PayPal Admin Handler.
 *
 * Handles PayPal payment refund operations for one-time payments and subscriptions.
 *
 * IMPORTANT - Subscription Refunds:
 * ================================
 * As of the latest update, subscription refunds are now properly supported.
 *
 * How PayPal Subscription IDs Are Stored:
 * ----------------------------------------
 * 1. subscription_id: Stores the PayPal subscription ID (e.g., "I-123456789")
 * 2. transaction_id: Stores the capture_id from actual payment transactions (for refunds)
 *
 * When a subscription is created/activated:
 * - If the capture_id is available from billing_info, it's stored in transaction_id
 * - If not available yet, transaction_id is left empty and will be populated via webhook
 *
 * PayPal's Refund API Requirements:
 * ---------------------------------
 * - Endpoint: POST /v2/payments/captures/{capture_id}/refund
 * - Requires: capture_id from actual payment transactions (not subscription_id)
 *
 * Refund Process:
 * ---------------
 * 1. For subscriptions with capture_id: Refund works immediately using transaction_id
 * 2. For subscriptions without capture_id: Error message asking user to wait or refund manually
 * 3. Webhook integration will populate capture_id when PAYMENT.SALE.COMPLETED event is received
 *
 * @since 2.4.0
 */
class Admin_PayPal_Handler {
	use Get_Instance;

	/**
	 * Constructor.
	 *
	 * @since 2.4.0
	 */
	public function __construct() {
		// Hook into unified refund filter system.
		add_filter( 'srfm_process_transaction_refund', [ $this, 'process_paypal_refund' ], 10, 2 );

		// Hook into subscription details filter to add PayPal-specific fields.
		add_filter( 'srfm_subscription_details_response', [ $this, 'add_paypal_subscription_fields' ], 10, 2 );
	}

	/**
	 * Process PayPal refund via filter system.
	 *
	 * This method is called by the unified refund handler in admin-handler.php
	 * when a refund is requested for a PayPal payment.
	 *
	 * @param array $refund_result The default refund result.
	 * @param array $refund_args   The refund arguments.
	 * @since 2.4.0
	 * @return array The refund result with success status, message, and data.
	 */
	public function process_paypal_refund( $refund_result, $refund_args ) {
		// Only process if this is a PayPal payment.
		if ( empty( $refund_args['gateway'] ) || 'paypal' !== $refund_args['gateway'] ) {
			return $refund_result;
		}

		// Extract arguments.
		$payment        = $refund_args['payment'] ?? [];
		$payment_id     = $refund_args['payment_id'] ?? 0;
		$transaction_id = $refund_args['transaction_id'] ?? '';
		$refund_amount  = $refund_args['refund_amount'] ?? 0;
		$refund_notes   = $refund_args['refund_notes'] ?? '';
		$entry_id       = isset( $payment['entry_id'] ) && is_numeric( $payment['entry_id'] ) ? intval( $payment['entry_id'] ) : 0;

		// Validate payment data.
		if ( empty( $payment ) || empty( $transaction_id ) ) {
			return [
				'success' => false,
				'message' => __( 'Invalid payment data. Please try again.', 'sureforms-pro' ),
				'data'    => [],
			];
		}

		// Check if this is a subscription payment.
		// NOTE: As of the latest update, PayPal subscriptions now correctly store:
		// - subscription_id: The PayPal subscription ID (I-2.4.0)
		// - transaction_id: The capture_id from actual payment transactions (for refunds)
		// If transaction_id is empty for a subscription, it means the capture_id hasn't been populated yet.
		$is_subscription = ! empty( $payment['type'] ) && 'subscription' === $payment['type'];

		if ( $is_subscription && ! isset( $payment['subscription_id'] ) ) {
			return [
				'success' => false,
				'message' => __(
					'This subscription payment does not have a capture ID yet. The capture ID is populated when the first payment is processed. Please try again later or process the refund manually through your PayPal dashboard.',
					'sureforms-pro'
				),
				'data'    => [],
			];
		}

		// Validate refund amount.
		if ( $refund_amount <= 0 ) {
			return [
				'success' => false,
				'message' => __( 'Invalid refund amount.', 'sureforms-pro' ),
				'data'    => [],
			];
		}

		// Get payment mode from payment record.
		$payment_mode = ! empty( $payment['mode'] ) && is_string( $payment['mode'] ) ? $payment['mode'] : Payment_Helper::get_payment_mode();

		try {
			// Verify PayPal credentials are configured.
			$client_id     = PayPal_Helper::get_paypal_client_id( $payment_mode );
			$client_secret = PayPal_Helper::get_paypal_client_secret( $payment_mode );

			if ( empty( $client_id ) || empty( $client_secret ) ) {
				return [
					'success' => false,
					'message' => __( 'PayPal credentials are not configured. Please check your PayPal settings.', 'sureforms-pro' ),
					'data'    => [],
				];
			}

			// Prepare refund data for PayPal API.
			$currency         = $payment['currency'] ?? 'USD';
			$formatted_amount = PayPal_Helper::convert_cents_to_paypal_format( $refund_amount, $currency );

			$refund_body = [
				'amount' => [
					'currency_code' => strtoupper( $currency ),
					'value'         => $formatted_amount,
				],
			];

			// Create refund via PayPal API using API layer.
			$refund_response = API_Payments::refund_capture( $transaction_id, $refund_body, $payment_mode );

			// Check if API request was successful.
			if ( empty( $refund_response['success'] ) ) {
				return [
					'success' => false,
					'message' => $refund_response['message'] ?? __( 'Failed to process refund with PayPal.', 'sureforms-pro' ),
					'data'    => [],
				];
			}

			// Extract refund ID from response.
			$refund_id = ! empty( $refund_response['id'] ) && is_string( $refund_response['id'] ) ? $refund_response['id'] : '';
			$status    = ! empty( $refund_response['status'] ) && is_string( $refund_response['status'] ) ? $refund_response['status'] : 'COMPLETED';

			// Store refund data in database.
			$refund_stored = $this->update_refund_data( $payment_id, $refund_response, $refund_amount, $payment['currency'] ?? 'USD', $entry_id, $refund_notes );

			if ( ! $refund_stored ) {
				return [
					'success' => false,
					'message' => __( 'Refund was processed by PayPal but failed to update the database.', 'sureforms-pro' ),
					'data'    => [],
				];
			}

			return [
				'success' => true,
				'message' => __( 'Payment refunded successfully.', 'sureforms-pro' ),
				'data'    => [
					'refund_id' => $refund_id,
					'status'    => $status,
				],
			];
		} catch ( \Exception $e ) {
			return [
				'success' => false,
				'message' => __( 'Failed to process refund. Please try again.', 'sureforms-pro' ),
				'data'    => [],
			];
		}
	}

	/**
	 * Add PayPal-specific subscription fields to subscription details response.
	 *
	 * Hooks into the 'srfm_subscription_details_response' filter to add
	 * PayPal subscription ID, interval, and next payment date.
	 *
	 * @param array $response_data Response data containing subscription and payments.
	 * @param array $args          Additional arguments including the main subscription record.
	 * @since 2.4.0
	 * @return array Modified response data with PayPal fields added.
	 */
	public function add_paypal_subscription_fields( $response_data, $args ) {
		// Only process if this is a PayPal subscription.
		$main_subscription = isset( $args['subscription'] ) && is_array( $args['subscription'] ) ? $args['subscription'] : [];

		if ( empty( $main_subscription ) || empty( $main_subscription['gateway'] ) || 'paypal' !== $main_subscription['gateway'] ) {
			return $response_data;
		}

		// Extract PayPal subscription ID.
		$paypal_subscription_id = ! empty( $main_subscription['subscription_id'] ) && is_string( $main_subscription['subscription_id'] ) ? sanitize_text_field( $main_subscription['subscription_id'] ) : '';

		// Extract interval and next payment date from payment_data.
		$payment_data = ! empty( $main_subscription['payment_data'] ) ? Helper::get_array_value( $main_subscription['payment_data'] ) : [];

		// Get interval from subscription_details.
		$interval = $this->get_paypal_subscription_interval( $payment_data );

		// Get next payment date from subscription_details.billing_info.next_billing_time.
		$next_payment_date = $this->get_paypal_next_payment_date( $payment_data );

		// Add PayPal-specific fields to subscription data.
		if ( isset( $response_data['subscription'] ) && is_array( $response_data['subscription'] ) ) {
			$response_data['subscription']['paypal_subscription_id'] = $paypal_subscription_id;
			$response_data['subscription']['interval']               = $interval;
			$response_data['subscription']['next_payment_date']      = $next_payment_date;
		}

		return $response_data;
	}

	/**
	 * Update refund data in the database.
	 *
	 * @param int    $payment_id    Payment ID.
	 * @param array  $refund_data   Refund data from PayPal API.
	 * @param int    $refund_amount Refund amount in cents.
	 * @param string $currency      Currency code.
	 * @param int    $entry_id      Entry ID.
	 * @param string $refund_notes  Refund notes.
	 * @since 2.4.0
	 * @return bool True on success, false on failure.
	 */
	private function update_refund_data( $payment_id, $refund_data, $refund_amount, $currency, $entry_id = 0, $refund_notes = '' ) {
		if ( empty( $payment_id ) || empty( $refund_data ) ) {
			return false;
		}

		// Get payment record.
		$payment = Payments::get( $payment_id );
		if ( ! $payment ) {
			return false;
		}

		// Prepare refund data for payment_data column (following Stripe pattern).
		$refund_entry = [
			'refund_id'      => is_string( $refund_data['id'] ) ? sanitize_text_field( $refund_data['id'] ) : '',
			'amount'         => absint( $refund_amount ),
			'currency'       => sanitize_text_field( strtoupper( $currency ) ),
			'status'         => is_string( $refund_data['status'] ) ? sanitize_text_field( $refund_data['status'] ) : 'COMPLETED',
			'created'        => time(),
			'reason'         => 'requested_by_customer',
			'description'    => '',
			'receipt_number' => '',
			'refunded_by'    => is_string( wp_get_current_user()->display_name ) ? sanitize_text_field( wp_get_current_user()->display_name ) : 'System',
			'refunded_at'    => gmdate( 'Y-m-d H:i:s' ),
		];

		// Validate refund amount to prevent over-refunding.
		$original_amount  = floatval( $payment['total_amount'] );
		$existing_refunds = floatval( $payment['refunded_amount'] ); // Use column directly.

		// Convert cents to proper currency format (handles zero-decimal currencies).
		if ( PayPal_Helper::is_zero_decimal_currency( $currency ) ) {
			// Zero-decimal currencies: amount is already in the smallest unit.
			$new_refund_amount = floatval( $refund_amount );
		} else {
			// Standard currencies: convert cents to dollars.
			$new_refund_amount = $refund_amount / 100;
		}

		$total_after_refund = $existing_refunds + $new_refund_amount;

		if ( $total_after_refund > $original_amount ) {
			return false;
		}

		// Add refund data to payment_data column (for audit trail).
		Payments::add_refund_to_payment_data( $payment_id, $refund_entry );

		// Update the refunded_amount column.
		$refund_amount_result = Payments::add_refund_amount( $payment_id, $new_refund_amount );

		// Calculate appropriate payment status.
		$payment_status = 'succeeded'; // Default to current status.
		if ( $total_after_refund >= $original_amount ) {
			$payment_status = 'refunded'; // Fully refunded.
		} elseif ( $total_after_refund > 0 ) {
			$payment_status = 'partially_refunded'; // Partially refunded.
		}

		// Update payment status and log.
		$current_logs = Helper::get_array_value( $payment['log'] );
		$refund_type  = $total_after_refund >= $original_amount ? __( 'Full', 'sureforms-pro' ) : __( 'Partial', 'sureforms-pro' );

		// Build log messages array.
		$log_messages = [
			sprintf(
				/* translators: %s: refund ID */
				__( 'Refund ID: %s', 'sureforms-pro' ),
				is_string( $refund_data['id'] ) ? $refund_data['id'] : 'N/A'
			),
			sprintf(
				/* translators: %s: payment gateway name */
				__( 'Payment Gateway: %s', 'sureforms-pro' ),
				'PayPal'
			),
			sprintf(
				/* translators: 1: refund amount, 2: currency */
				__( 'Refund Amount: %1$s %2$s', 'sureforms-pro' ),
				PayPal_Helper::is_zero_decimal_currency( $currency )
					? number_format( $refund_amount, 0 )
					: number_format( $refund_amount / 100, 2 ),
				strtoupper( $currency )
			),
			sprintf(
				/* translators: 1: total refunded, 2: currency, 3: original total, 4: currency */
				__( 'Total Refunded: %1$s %2$s of %3$s %4$s', 'sureforms-pro' ),
				PayPal_Helper::is_zero_decimal_currency( $currency )
					? number_format( $total_after_refund, 0 )
					: number_format( $total_after_refund, 2 ),
				strtoupper( $currency ),
				PayPal_Helper::is_zero_decimal_currency( $currency )
					? number_format( $original_amount, 0 )
					: number_format( $original_amount, 2 ),
				strtoupper( $currency )
			),
			sprintf(
				/* translators: %s: status */
				__( 'Refund Status: %s', 'sureforms-pro' ),
				is_string( $refund_data['status'] ) ? $refund_data['status'] : 'COMPLETED'
			),
			sprintf(
				/* translators: %s: payment status */
				__( 'Payment Status: %s', 'sureforms-pro' ),
				ucfirst( str_replace( '_', ' ', $payment_status ) )
			),
			sprintf(
				/* translators: %s: user display name */
				__( 'Refunded by: %s', 'sureforms-pro' ),
				wp_get_current_user()->display_name
			),
			sprintf(
				/* translators: %s: entry ID */
				__( 'Entry ID: %s', 'sureforms-pro' ),
				is_numeric( $entry_id ) ? intval( $entry_id ) : 'N/A'
			),
		];

		// Add refund notes to log if provided.
		if ( ! empty( $refund_notes ) ) {
			$log_messages[] = sprintf(
				/* translators: %s: refund notes */
				__( 'Refund Notes: %s', 'sureforms-pro' ),
				$refund_notes
			);
		}

		$new_log        = [
			'title'      => sprintf(
				/* translators: %s: refund type (Full or Partial) */
				__( '%s Payment Refund', 'sureforms-pro' ),
				$refund_type
			),
			'created_at' => current_time( 'mysql' ),
			'messages'   => $log_messages,
		];
		$current_logs[] = $new_log;

		$update_data = [
			'status' => $payment_status,
			'log'    => $current_logs,
		];

		// Update payment record with status and log.
		$payment_update_result = Payments::update( $payment_id, $update_data );

		return ! empty( $payment_update_result ) && ! empty( $refund_amount_result );
	}

	/**
	 * Get PayPal subscription interval from payment data.
	 *
	 * Extracts billing frequency (day/week/month/year) from PayPal subscription details.
	 *
	 * Sample data path: payment_data.subscription_details.billing_info.cycle_executions[0].tenure_type
	 * Or from plan data if available.
	 *
	 * @param array $payment_data Payment data from subscription record.
	 * @since 2.4.0
	 * @return string Interval (e.g., 'Day', 'Month', 'Year') or 'Unknown'.
	 */
	private function get_paypal_subscription_interval( $payment_data ) {
		// Try to extract interval from subscription_details.
		if ( isset( $payment_data['subscription_details'] ) ) {
			// Convert stdClass to array if needed using Helper.
			$subscription_details = Helper::get_array_value( $payment_data['subscription_details'] );

			if ( ! empty( $subscription_details ) && is_array( $subscription_details ) ) {
				// Check billing_info.cycle_executions for interval.
				if ( isset( $subscription_details['billing_info']['cycle_executions'] ) && is_array( $subscription_details['billing_info']['cycle_executions'] ) ) {
					foreach ( $subscription_details['billing_info']['cycle_executions'] as $cycle ) {
						if ( isset( $cycle['tenure_type'] ) && 'REGULAR' === $cycle['tenure_type'] ) {
							// PayPal doesn't directly provide interval in cycle_executions.
							// We need to get it from the plan or infer from next_billing_time.
							break;
						}
					}
				}

				// Try to infer from billing cycle by comparing dates.
				if ( isset( $subscription_details['billing_info']['next_billing_time'] ) && is_string( $subscription_details['billing_info']['next_billing_time'] ) && isset( $subscription_details['billing_info']['last_payment']['time'] ) && is_string( $subscription_details['billing_info']['last_payment']['time'] ) ) {
					$next_billing = strtotime( $subscription_details['billing_info']['next_billing_time'] );
					$last_payment = strtotime( $subscription_details['billing_info']['last_payment']['time'] );

					if ( false !== $next_billing && false !== $last_payment ) {
						$diff_days = abs( $next_billing - $last_payment ) / 86400;

						// Estimate interval based on days difference.
						if ( $diff_days >= 350 && $diff_days <= 380 ) {
							return 'Year';
						}
						if ( $diff_days >= 28 && $diff_days <= 31 ) {
							return 'Month';
						}
						if ( $diff_days >= 6 && $diff_days <= 8 ) {
							return 'Week';
						}
						if ( $diff_days >= 0.9 && $diff_days <= 1.1 ) {
							return 'Day';
						}
					}
				}
			}
		}

		// Try to get from payment_value if available.
		if ( isset( $payment_data['payment_value'] ) && is_array( $payment_data['payment_value'] ) ) {
			$payment_value = $payment_data['payment_value'];

			// Check for interval field.
			if ( isset( $payment_value['interval'] ) && is_string( $payment_value['interval'] ) ) {
				return ucfirst( sanitize_text_field( $payment_value['interval'] ) );
			}
		}

		return __( 'Unknown', 'sureforms-pro' );
	}

	/**
	 * Get PayPal next payment date from payment data.
	 *
	 * Extracts next billing time from PayPal subscription details.
	 *
	 * Sample data path: payment_data.subscription_details.billing_info.next_billing_time
	 *
	 * @param array $payment_data Payment data from subscription record.
	 * @since 2.4.0
	 * @return string|null Next payment date in 'Y-m-d H:i:s' format or null.
	 */
	private function get_paypal_next_payment_date( $payment_data ) {
		// Convert stdClass to array if needed.
		if ( isset( $payment_data['subscription_details'] ) ) {
			$subscription_details = Helper::get_array_value( $payment_data['subscription_details'] );

			if ( ! empty( $subscription_details ) && is_array( $subscription_details ) && isset( $subscription_details['billing_info']['next_billing_time'] ) && is_string( $subscription_details['billing_info']['next_billing_time'] ) ) {
				$next_billing_time = $subscription_details['billing_info']['next_billing_time'];

				// Convert ISO 8601 format to MySQL datetime.
				$timestamp = strtotime( $next_billing_time );

				if ( false !== $timestamp ) {
					return gmdate( 'Y-m-d H:i:s', $timestamp );
				}
			}
		}

		return null;
	}
}
