<?php
/**
 * Store Credit Base Integration Class
 *
 * @package     AffiliateWP
 * @subpackage  Payouts/Methods/StoreCredit
 * @copyright   Copyright (c) 2025, Sandhills Development, LLC
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       2.29.0
 */

namespace AffiliateWP\Core\Payouts\Methods\StoreCredit;

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

/**
 * Base class for Store Credit integrations.
 *
 * @since 2.29.0
 */
abstract class AffiliateWP_Store_Credit_Base {

	/**
	 * Defines the context of this integration
	 * The $context variable should be defined in $this->init()
	 *
	 * @var $context  A string defining the name of the integration
	 */
	public $context;

	public function __construct() {

		$this->init();

		add_action( 'affwp_set_referral_status', [ $this, 'process_payout' ], 10, 3 );
		add_action( 'affwp_process_update_referral', [ $this, 'process_payout' ], 0 );
		add_action( 'affwp_add_referral', [ $this, 'process_payout' ] );
	}

	/**
	 * Define the $this->context here,
	 * as well any hooks specific to
	 * the integration being created.
	 *
	 * @since  2.0.0
	 *
	 * @return void
	 */
	public function init() {}

	/**
	 * Set the expiration date of the coupon, if available
	 *
	 * @since  2.1
	 *
	 * @return int|date The future date on which this coupon expires.
	 *                  Defaults to 2 days after coupon creation.
	 */
	public function coupon_expires() {

		$expires = date( 'Y-m-d-s', strtotime( '+2 days', current_time( 'timestamp' ) ) );

		return apply_filters( 'affwp_store_credit_expires', $expires );
	}

	/**
	 * Check if the integration is available.
	 *
	 * @since 2.29.0
	 *
	 * @return bool True if available, false otherwise.
	 */
	abstract public function is_available();

	/**
	 * Get integration-specific settings.
	 *
	 * @since 2.29.0
	 *
	 * @return array Settings array.
	 */
	public function get_settings() {
		return [];
	}

	/**
	 * Validates usage of the coupon.
	 *
	 * @since  2.1
	 * @param  int    $order_id   The ID of an order
	 * @param  object $data       The order data
	 *
	 * @return void    Since the manners by which coupon usage may be
	 *                 validated vary greatly by integration, this
	 *                 method does not supply any direct validation
	 *                 itself.
	 *
	 *                 Generalized validation, such as typecasting,
	 *                 defining arbitrary $desired and $actual vars,
	 *                 and comparisons may be added as integrations
	 *                 continue to be extended in this add-on.
	 */
	public function validate_coupon_usage( $order_id, $data ) {
		$order_id = '';
		$data     = '';
	}

	/**
	 * Processes payouts.
	 *
	 * @since  0.1
	 * @access public
	 * @param  int    $referral_id  The referral ID.
	 * @param  string $new_status   The new status.
	 * @param  string $old_status   The old status.
	 * @return void
	 */
	public function process_payout( $referral_id = 0, $new_status = '', $old_status = '' ) {

		$affwp_store_credit = affwp_store_credit();

		// Bail if no referral ID is provided. new referral status, or old referral status is not provided.
		if ( ! $referral_id || 0 === $referral_id ) {
			$affwp_store_credit->log( 'AffiliateWP Store Credit: The referral ID could not be determined when processing this payout.' );
			return;
		}

		$referral = affwp_get_referral( $referral_id );

		// Bail if the affiliate is not enabled to receive store credit.
		if ( ! $this->can_receive_store_credit( $referral, $referral->affiliate_id ) ) {
			$affwp_store_credit->log( 'AffiliateWP Store Credit: This affiliate is not enabled to receive store credit.' );
			return;
		}

		// Bail if the new referral status for this referral is unset or an empty string.
		if ( ! isset( $new_status ) || '' === $new_status ) {
			$affwp_store_credit->log( 'AffiliateWP Store Credit: The new referral status could not be determined.' );
			return;
		}

		// Also bail if the old referral status for this referral is unset or an empty string.
		if ( ! isset( $old_status ) || '' === $old_status ) {
			$affwp_store_credit->log( 'AffiliateWP Store Credit: The old referral status could not be determined.' );
			return;
		}

		if ( 'paid' === $new_status ) {
			$this->add_payment( $referral_id );
		} elseif ( ( 'paid' === $old_status ) && ( 'unpaid' === $new_status ) ) {
			$this->remove_payment( $referral_id );
		}
	}

	/**
	 * Add a payment to a referrer.
	 *
	 * @access protected
	 * @since  Unknown
	 *
	 * @param  int $referral_id The Referral (ID).
	 */
	abstract protected function add_payment( $referral_id );

	/**
	 * Remove a payment from a referrer.
	 *
	 * @access protected
	 * @since  Unknown
	 *
	 * @param int $referral_id The Referral (ID).
	 */
	abstract protected function remove_payment( $referral_id );

	/**
	 * Can the affiliate receive store credit?
	 *
	 * @since  2.3
	 *
	 * @param int            $affiliate_id Affiliate ID.
	 * @param AffWP\Referral $referral     Referral object.
	 *
	 * @return bool
	 */
	public function can_receive_store_credit( $referral, $affiliate_id = 0 ) {

		/*
		 * Use the usable resolver, not the raw one: a Store Credit pick/override that's been
		 * disabled site-wide falls back to the default and must NOT credit store credit. Batch
		 * runs make the processor authoritative, so this gate governs non-batch status changes.
		 */
		$ret = function_exists( 'affwp_get_affiliate_usable_payout_method' )
			? ( 'store_credit' === affwp_get_affiliate_usable_payout_method( $affiliate_id ) )
			: ( 'store_credit' === affwp_get_affiliate_payout_method( $affiliate_id ) );

		/**
		 * Filters whether the affiliate can receive store credit.
		 *
		 * @since 2.3
		 *
		 * @param bool           True if the affiliate can receive store credit, otherwise false.
		 *                       Defaults to false.
		 * @param int            $affiliate Affiliate ID.
		 * @param AffWP\Referral $referral  Referral object.
		 */
		return apply_filters( 'affwp_store_credit_can_receive_store_credit', $ret, $affiliate_id, $referral );
	}
}
