<?php
/**
 * Coupon reward functionality trait.
 *
 * Provides core coupon reward functionality for issue, claim, redeem operations.
 * This trait is shared between Lite and Pro reward types that work with coupons.
 *
 * @package Awesomemotive\Rewardswp\Traits
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\Traits;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Awesomemotive\Rewardswp\Abstracts\AbstractIntegrationWithCoupon;
use Awesomemotive\Rewardswp\Models\Referral;
use Awesomemotive\Rewardswp\Models\Reward;
use Awesomemotive\Rewardswp\Notifications\Emails\AdvocateRewardEmail;
use Awesomemotive\Rewardswp\Settings\Options\Referrals\RewardsOption;
use Awesomemotive\Rewardswp\Views\Panel\Friend\ClaimRewardFormView;
use Awesomemotive\Rewardswp\Views\Panel\Friend\ClaimRewardSuccessView;
use Awesomemotive\Rewardswp\Views\Panel\Parts\EmptyRewardsView;
use Awesomemotive\Rewardswp\Views\Panel\RewardTypes\BaseRewardView;
use Awesomemotive\Rewardswp\Views\Panel\RewardTypes\DiscountCodeView;
use Exception;
use function Awesomemotive\Rewardswp\Helpers\create_reward;
use function Awesomemotive\Rewardswp\Helpers\get_member_by;
use function Awesomemotive\Rewardswp\Helpers\get_date_diff_in_days;
use function Awesomemotive\Rewardswp\Helpers\get_option_value;
use function Awesomemotive\Rewardswp\Helpers\get_reward_expiration_date;
use function Awesomemotive\Rewardswp\Helpers\get_user_full_name;

/**
 * Coupon Reward Functionality Trait
 *
 * Provides all the core functionality for reward types that work with coupons.
 * This includes issuing, claiming, and redeeming rewards, as well as view preparation.
 *
 * Classes using this trait must have these properties defined:
 * - IntegrationInterface $integration
 * - MailerFactory $mailer_factory
 * - ViewsFactory $views_factory
 * - RewardsService $rewards_service
 * - TrackingService $tracking_service
 *
 * @since 1.0.0
 */
trait CouponRewardFunctionalityTrait {
	/**
	 * Issue an advocate's reward.
	 *
	 * @since 1.0.0
	 *
	 * @param Referral $referral Referral instance.
	 * @param array    $args     Optional arguments. Can include 'status' to set reward status (default 'issued').
	 *
	 * @return Reward|null
	 *
	 * @throws Exception If the integration is not compatible or reward creation fails.
	 */
	private function issue_advocate_reward( Referral $referral, array $args = [] ): ?Reward {
		if (
			! is_subclass_of( $this->integration, AbstractIntegrationWithCoupon::class ) ||
			! in_array( $this->integration->get_source(), $this->available_for(), true )
		) {
			throw new Exception(
				esc_html__( 'The current site integration is incompatible with this reward type.', 'rewardswp' )
			);
		}

		$reward_options = get_option_value( RewardsOption::class );

		// Get status from args, default to 'issued'.
		$reward_status = isset( $args['status'] ) ? sanitize_text_field( $args['status'] ) : 'issued';

		// Only allow 'pending' or 'issued' status.
		if ( ! in_array( $reward_status, [ 'pending', 'issued' ], true ) ) {
			$reward_status = 'issued';
		}

		// Create the advocate reward.
		$reward = create_reward(
			[
				'referral_id'    => $referral->id,
				'advocate_id'    => $referral->advocate_id,
				'reward_type'    => $reward_options['advocate']['type'] ?? '',
				'recipient_type' => 'advocate',
				'status'         => $reward_status,
				'amount'         => $reward_options['advocate']['reward_amount'] ?? 0,
			]
		);

		$advocate    = get_member_by( 'id', $referral->advocate_id );
		$coupon_code = $this->integration->generate_coupon(
			$advocate->email,
			'advocate-',
			array_merge(
				$reward_options['advocate'],
				[
					'expiry_date' => get_reward_expiration_date( 'advocate' ),
					'name'        => $this->get_reward_formatted_amount( $reward_options['advocate']['reward_amount'] ?? 0 ),
				]
			)
		);

		$reward->update_meta( 'coupon_code', $coupon_code );

		// Save free_product_id meta for free_product rewards.
		if ( 'free_product' === ( $reward_options['advocate']['type'] ?? '' ) ) {
			$products_included = $reward_options['advocate']['products_included'] ?? [];

			if ( ! empty( $products_included[0]['id'] ) ) {
				$product_id = $products_included[0]['id'];
				$reward->update_meta( 'free_product_id', $product_id );

				// Also save product name for display purposes.
				if ( method_exists( $this->integration, 'get_product' ) ) {
					$product_data = $this->integration->get_product( $product_id );

					if ( is_array( $product_data ) && ! empty( $product_data['name'] ) ) {
						$reward->update_meta( 'free_product_name', (string) $product_data['name'] );
					}
				}
			}

			// Save product_value (reward_amount) for display purposes (shows "$X off" instead of "Free" in the widget).
			$product_value = $reward_options['advocate']['reward_amount'] ?? 0;
			if ( floatval( $product_value ) > 0 ) {
				$reward->update_meta( 'product_value', floatval( $product_value ) );
			}

			// Save reward_description (custom name) for display in the widget.
			$reward_description = $reward_options['advocate']['reward_description'] ?? '';
			if ( ! empty( $reward_description ) ) {
				$reward->update_meta( 'reward_description', sanitize_text_field( $reward_description ) );
			}
		}

		// Only send email if status is 'issued'.
		// For 'pending' status, email will be sent when the reward is activated.
		if ( 'issued' === $reward_status ) {
			$advocate_reward_mailer = $this->mailer_factory->get_mailer( AdvocateRewardEmail::class );

			$days_to_expire = get_date_diff_in_days( $reward->expires_at );

			$friend_name = empty( $referral->order_id )
				? get_user_full_name( $referral->referred_user_id ?? 0 )
				: $this->integration->get_customer_name( $referral->order_id );

			// Set the reward instance so get_reward_formatted_amount can access the product name.
			$this->set_reward( $reward );

			// Notify the advocate with the reward he just won.
			$advocate_reward_mailer
				->set_recipient( $advocate->email )
				->set_data(
					[
						'reward_coupon'          => $coupon_code,
						'reward_amount'          => $this->get_reward_formatted_amount( $reward->amount, 'value_only' ),
						'friend_name'            => empty( $friend_name )
							? esc_html__( 'A friend', 'rewardswp' )
							: $friend_name,
						'reward_expiration_time' => sprintf(
							/* translators: %s is the number of days to expire */
							_n( '%s day', '%s days', $days_to_expire, 'rewardswp' ),
							$days_to_expire
						),
					]
				)
				->send();
		}

		return $reward;
	}

	/**
	 * Issues a reward for a recipient: advocate or friend.
	 *
	 * @since 1.0.0
	 *
	 * @param Referral $referral Referral instance.
	 * @param string   $recipient_type Recipient type ('advocate' or 'friend').
	 * @param array    $args Optional reward arguments. Can include 'status' to set reward status (default 'issued').
	 *
	 * @return Reward|null
	 *
	 * @throws Exception If reward creation fails.
	 */
	public function issue_reward( Referral $referral, string $recipient_type, array $args = [] ): ?Reward {
		if ( 'advocate' === $recipient_type ) {
			$reward = $this->issue_advocate_reward( $referral, $args );
		} else {
			$reward = $this->issue_friend_reward( $args );
		}

		if ( $reward ) {
			/**
			 * Fires after a reward is issued to an advocate or friend.
			 *
			 * @since 1.3.0
			 *
			 * @param Reward   $reward         The issued reward.
			 * @param Referral $referral       The triggering referral.
			 * @param string   $recipient_type The recipient type ('advocate' or 'friend').
			 */
			do_action( 'rewardswp_reward_issued', $reward, $referral, $recipient_type );
		}

		return $reward;
	}

	/**
	 * Claim reward.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Any data to pass to the method.
	 *
	 * @return Reward|null
	 *
	 * @throws Exception If the integration is not compatible or reward creation fails.
	 */
	public function claim_reward( array $args = [] ): ?Reward {
		if (
			! is_subclass_of( $this->integration, AbstractIntegrationWithCoupon::class ) ||
			! in_array( $this->integration->get_source(), $this->available_for(), true )
		) {
			throw new Exception(
				esc_html__( 'The current site integration is incompatible with this reward type.', 'rewardswp' )
			);
		}

		$this->reward = $this->issue_friend_reward( $args );

		return $this->reward;
	}

	/**
	 * Process rewards.
	 *
	 * Look for pending rewards for a given referral and redeem them.
	 *
	 * @since 1.0.0
	 *
	 * @param Reward $reward  The reward to redeem.
	 * @param array  $args    Any data to pass to the method to handle the reward.
	 *
	 * @return bool|null Will be true, once the reward is issued and advocate notified, false otherwise.
	 *
	 * @throws Exception If the integration is not compatible or redemption fails.
	 */
	public function redeem_reward( Reward $reward, array $args = [] ): ?bool {
		if (
			! is_subclass_of( $this->integration, AbstractIntegrationWithCoupon::class ) ||
			! in_array( $this->integration->get_source(), $this->available_for(), true )
		) {
			throw new Exception(
				esc_html__( 'The current site integration is incompatible with this reward type.', 'rewardswp' )
			);
		}

		if ( 'issued' !== $reward->status ) {
			return false; // Bail because we just want to process issued rewards.
		}

		// Update the reward with the applied data and status.
		$reward->set_as_redeemed();

		return true;
	}

	/**
	 * Retrieve an array with the HTML used to render the reward in the advocate's panel.
	 *
	 * @since 1.0.0
	 *
	 * @param Reward[] $rewards Array of reward instances.
	 * @param array    $args Optional arguments for rendering.
	 *
	 * @return array
	 */
	public function prepare_rewards_objects_for_view( array $rewards, array $args = [] ): array {
		return array_filter(
			array_map(
				function ( $reward ) use ( $args ) {
					if ( ! is_a( $reward, Reward::class ) ) {
						return null;
					}

					// We only want the issued ones.
					if ( 'issued' !== $reward->status ) {
						return null;
					}

					$base_reward_view   = $this->views_factory->get_view( BaseRewardView::class );
					$discount_code_view = $this->views_factory->get_view( DiscountCodeView::class );

					$this->set_reward( $reward );

					return $base_reward_view->get_contents(
						array_merge(
							[
								'icon_name'       => $this->get_icon_name(),
								'reward_title'    => $this->get_panel_title(),
								'reward_subtitle' => $this->get_panel_subtitle(),
								'reward_content'  => $discount_code_view->get_contents(
									array_filter(
										[
											'discount_code_instructions' => $this->get_panel_instructions(),
											'discount_code_notice' => '',
											'discount_code_coupon' => $reward->get_meta( 'coupon_code' ),
											'reward_id' => $reward->id,
										]
									)
								),
							],
							$args
						)
					);
				},
				$rewards
			)
		);
	}

	/**
	 * Display the renewal discounts to improve feature discoverability.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public function get_empty_rewards_content(): string {
		return $this->views_factory->get_view( EmptyRewardsView::class )->get_contents();
	}

	/**
	 * Returns the content to display the claiming panel.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Args to pass to the views.
	 *
	 * @return string
	 */
	public function get_claim_panel_content( array $args = [] ): string {
		$pending_rewards = $this->rewards_service->get_unused_friend_rewards(
			$this->tracking_service->get_cookie( $this->tracking_service::VISIT_ID_COOKIE_NAME ) ?? 0
		);

		$coupon_redeemed = ! empty( $pending_rewards )
			? $pending_rewards[0]->get_meta( 'coupon_code' )
			: '';

		$view = empty( $coupon_redeemed )
			? $this->views_factory->get_view( ClaimRewardFormView::class )
			: $this->views_factory->get_view( ClaimRewardSuccessView::class );

		if ( is_null( $view ) ) {
			return esc_html__( 'Failed to render view. Please contact the site administrator', 'rewardswp' );
		}

		$reward = $this->rewards_service->get_reward_by_coupon_code( $coupon_redeemed );

		if ( is_a( $reward, Reward::class ) ) {
			$this->set_reward( $reward );
		}

		return $view->get_contents(
			array_filter(
				array_merge(
					$args,
					array_filter(
						[
							'coupon_code'     => $coupon_redeemed,
							'reward_id'       => is_a( $reward, Reward::class )
								? $reward->id
								: 0,
							'success_message' => 'free_product' === $this->get_type()
								? sprintf(
									'<p class="mb-4 text-gray-600">%s</p><p class="mb-4 text-gray-600">%s</p>',
									esc_html__( 'Your reward has been claimed!', 'rewardswp' ),
									$this->get_panel_instructions()
								)
								: '',
						]
					)
				)
			)
		);
	}

	/**
	 * Retrieve the panel subtitle.
	 *
	 * @since 1.0.0
	 *
	 * @param Reward|null $reward Optional reward instance.
	 *
	 * @return string
	 */
	public function get_panel_subtitle( ?Reward $reward = null ): string {
		if ( empty( $this->reward ) ) {
			return esc_html__( 'Limited time offer', 'rewardswp' );
		}

		// A reward with no expiry date (e.g. "never expires" coupons, the Lite default)
		// stores a null expires_at; guard against it before computing the day diff.
		if ( empty( $this->reward->expires_at ) ) {
			return esc_html__( 'Limited time offer', 'rewardswp' );
		}

		$days_to_expire = get_date_diff_in_days( $this->reward->expires_at );

		return empty( $days_to_expire )
			? esc_html__( 'Reward expires today', 'rewardswp' )
			: sprintf(
			/* translators: %s is the number of days to expire */
				_n( 'Reward expires in %s day.', 'Reward expires in %s days', $days_to_expire, 'rewardswp' ),
				$days_to_expire
			);
	}
}

