<?php
/**
 * Claim controller for handling reward claims.
 *
 * @package Awesomemotive\Rewardswp\Controllers
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\Controllers;

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

use Awesomemotive\Rewardswp\Factories\RewardTypesFactory;
use Awesomemotive\Rewardswp\Interfaces\ControllerInterface;
use Awesomemotive\Rewardswp\Interfaces\IntegrationInterface;
use Awesomemotive\Rewardswp\Interfaces\IntegrationWithCouponInterface;
use Awesomemotive\Rewardswp\Services\AntiSpamService;
use Awesomemotive\Rewardswp\Services\PendingRewardService;
use Awesomemotive\Rewardswp\Services\ReferralCodeService;
use Awesomemotive\Rewardswp\Services\RewardManagerService;
use Awesomemotive\Rewardswp\Services\RewardsService;
use Awesomemotive\Rewardswp\Views\Panel\Friend\ClaimRewardFormView;
use Awesomemotive\Rewardswp\Views\Panel\Friend\ClaimRewardSuccessView;
use function Awesomemotive\Rewardswp\Helpers\can_access_rewardswp_feature;
use function Awesomemotive\Rewardswp\Helpers\get_referral_reward_display_title;
use function Awesomemotive\Rewardswp\Helpers\get_reward_config_from_model;


/**
 * Claim Controller.
 *
 * @since 1.0.0
 */
class ClaimController implements ControllerInterface {
	/**
	 * AJAX nonce action for friend claim.
	 *
	 * @since 1.0.0
	 */
	const AJAX_NONCE = 'rewardswp_friend_form_nonce';

	/**
	 * Current integration instance.
	 *
	 * @since 1.0.0
	 *
	 * @var IntegrationInterface|IntegrationWithCouponInterface
	 */
	private IntegrationInterface $integration;

	/**
	 * Friend claim form view.
	 *
	 * @since 1.0.0
	 *
	 * @var ClaimRewardFormView
	 */
	private ClaimRewardFormView $claim_reward_view;

	/**
	 * Friend claim success view.
	 *
	 * @since 1.0.0
	 *
	 * @var ClaimRewardSuccessView
	 */
	private ClaimRewardSuccessView $claim_reward_success_view;

	/**
	 * Reward manager service instance.
	 *
	 * @since 1.0.0
	 *
	 * @var RewardManagerService
	 */
	private RewardManagerService $reward_manager_service;

	/**
	 * Reward types factory instance.
	 *
	 * @since 1.0.0
	 *
	 * @var RewardTypesFactory
	 */
	private RewardTypesFactory $reward_types_factory;

	/**
	 * Anti-spam service instance.
	 *
	 * @since 1.2.0
	 *
	 * @var AntiSpamService
	 */
	private AntiSpamService $anti_spam_service;

	/**
	 * Pending reward service instance.
	 *
	 * @since 1.2.0
	 *
	 * @var PendingRewardService
	 */
	private PendingRewardService $pending_reward_service;

	/**
	 * Rewards service instance.
	 *
	 * @since 1.2.0
	 *
	 * @var RewardsService
	 */
	private RewardsService $rewards_service;

	/**
	 * Referral code service instance.
	 *
	 * @since 1.4.1
	 *
	 * @var ReferralCodeService
	 */
	private ReferralCodeService $referral_code_service;

	public function __construct(
		IntegrationInterface $integration,
		ClaimRewardFormView $claim_reward_view,
		ClaimRewardSuccessView $claim_reward_success_view,
		RewardManagerService $reward_manager_service,
		RewardTypesFactory $reward_types_factory,
		AntiSpamService $anti_spam_service,
		PendingRewardService $pending_reward_service,
		RewardsService $rewards_service,
		ReferralCodeService $referral_code_service
	) {
		$this->integration               = $integration;
		$this->claim_reward_view         = $claim_reward_view;
		$this->claim_reward_success_view = $claim_reward_success_view;
		$this->reward_manager_service    = $reward_manager_service;
		$this->reward_types_factory      = $reward_types_factory;
		$this->anti_spam_service         = $anti_spam_service;
		$this->pending_reward_service    = $pending_reward_service;
		$this->rewards_service           = $rewards_service;
		$this->referral_code_service     = $referral_code_service;
	}

	/**
	 * Run integration hooks.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function init(): void {
		// The coupon application is a resource that can be used by both the advocate and the friend.
		add_action( 'wp_ajax_nopriv_rewardswp_apply_coupon_to_cart', [ $this, 'ajax_apply_coupon_to_cart' ] );
		add_action( 'wp_ajax_rewardswp_apply_coupon_to_cart', [ $this, 'ajax_apply_coupon_to_cart' ] );

		// Friend rewards are a Pro feature - bail if not accessible.
		if ( ! can_access_rewardswp_feature( 'friend_reward' ) ) {
			return;
		}

		if ( ! $this->reward_manager_service->can_claim_rewards() ) {
			return; // Bail if this feature is deactivated.
		}

		add_action( 'wp_ajax_nopriv_rewardswp_claim_reward', [ $this, 'ajax_claim_reward' ] );
		add_action( 'wp_ajax_rewardswp_claim_reward', [ $this, 'ajax_claim_reward' ] );
	}

	/**
	 * Claim reward ajax handler.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function ajax_claim_reward(): void {
		check_ajax_referer( self::AJAX_NONCE );

		// Validate anti-spam protection.
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above.
		if ( ! $this->anti_spam_service->validate( $_POST ) ) {
			wp_send_json_error(
				[
					'message' => $this->anti_spam_service->get_last_error(),
				],
				400
			);
		}

		$email         = sanitize_email( filter_input( INPUT_POST, 'email' ) );
		$referral_code = sanitize_text_field( filter_input( INPUT_POST, 'referral_code' ) );

		$reward = $this->reward_manager_service->claim_reward( $email, $referral_code );

		if ( $this->reward_manager_service->has_error() ) {

			wp_send_json_error(
				[
					'template' => $this->claim_reward_view->get_contents(
						[
							'email'           => $email,
							'referral_code'   => $referral_code,
							'error_message'   => $this->reward_manager_service->get_error_message(),
							'form_nonce'      => wp_create_nonce( self::AJAX_NONCE ),
							'shouldOpenPanel' => true,
						]
					),
				]
			);

			wp_die();

		}

		// Clear persisted referral code after successful claim.
		$this->referral_code_service->clear_persistence();

		$reward_type = $this->reward_types_factory->get_reward_type( $reward->reward_type );

		$coupon_code     = $this->reward_manager_service->get_coupon_code();
		$reward_config   = get_reward_config_from_model( $reward );
		$reward_title    = get_referral_reward_display_title( $reward_config );
		$success_message = is_null( $reward_type ) || 'free_product' !== $reward_type->get_type()
			? esc_html__( 'Reward claimed successfully!', 'rewardswp' )
			: sprintf(
				'%s %s',
				esc_html__( 'Your reward has been claimed!', 'rewardswp' ),
				$reward_type->get_panel_instructions()
			);

		wp_send_json_success(
			[
				'coupon_code'     => $coupon_code,
				'reward_id'       => $reward->id,
				'reward_title'    => $reward_title,
				'success_message' => $success_message,
				'template'        => $this->claim_reward_success_view->get_contents(
					array_filter(
						[
							'coupon_code'     => $coupon_code,
							'reward_id'       => $reward->id,
							'success_message' => sprintf(
								'<p class="mb-4 text-gray-600">%s</p>',
								$success_message
							),
						]
					)
				),
			]
		);
	}

	/**
	 * Apply coupon to cart ajax handler.
	 *
	 * Handles applying reward coupons to cart with support for:
	 * - Coupon swapping (removes other RewardsWP coupons first)
	 * - Eligibility checks via PendingRewardService (Pro feature)
	 * - Product addition before coupon application
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Added pending reward support and coupon swapping.
	 *
	 * @return void
	 */
	public function ajax_apply_coupon_to_cart(): void {
		check_ajax_referer( self::AJAX_NONCE );

		if ( ! is_subclass_of( $this->integration, IntegrationWithCouponInterface::class ) ) {
			wp_send_json_error();
			wp_die();
		}

		$coupon_code = sanitize_text_field( filter_input( INPUT_POST, 'coupon_code' ) );
		$reward_id   = absint( filter_input( INPUT_POST, 'reward_id' ) );

		// Clear dismissed state only if the reward being applied is the one that was dismissed.
		// This respects user intent: if they dismissed Reward A and now apply Reward B,
		// Reward A should remain dismissed and not auto-apply later.
		if ( $this->pending_reward_service->is_reward_dismissed( $reward_id ) ) {
			$this->pending_reward_service->clear_dismissed_reward();
		}

		// If the coupon is already applied, just return successfully.
		if ( $this->integration->is_coupon_applied( $coupon_code ) ) {
			wp_send_json_success( [ 'state' => 'already_applied' ] );
			wp_die();
		}

		// Remove any other RewardsWP coupons FIRST (coupon swap).
		// This ensures eligibility check uses accurate cart state.
		$this->remove_other_rewardswp_coupons( $coupon_code );

		// Check reward-specific eligibility (e.g., minimum spend for Pro features).
		// Must happen AFTER removing old coupons so cart subtotal is accurate.
		$pending_result = $this->pending_reward_service->check_eligibility_and_set_pending(
			$reward_id,
			$coupon_code,
			$this->integration
		);

		if ( null !== $pending_result ) {
			// Either set as pending or eligible to proceed.
			if ( 'pending' === $pending_result['state'] ) {
				wp_send_json_success( $pending_result );
				wp_die();
			}
			// If eligible, continue with the flow.
		}

		/**
		 * Filter to add products to cart before applying coupon.
		 *
		 * Used by Pro reward types that need to add products before coupon application.
		 *
		 * @since 1.0.0
		 * @since 1.2.0 Changed from action to filter to track whether product was actually added.
		 *
		 * @param bool $product_was_added Whether the product was added to cart.
		 * @param int  $reward_id         The reward ID being applied.
		 */
		$product_was_added = apply_filters( 'rewardswp_before_apply_coupon_to_cart', false, $reward_id );

		$result = $this->integration->apply_coupon( $coupon_code );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error(
				[
					'message'           => $result->get_error_message(),
					'product_was_added' => $product_was_added,
					'cart_total'        => method_exists( $this->integration, 'get_cart_total' ) ? $this->integration->get_cart_total() : 0,
				]
			);
			wp_die();
		}

		if ( $result ) {
			// Clear any pending reward for this coupon since it's now applied.
			$this->pending_reward_service->clear_pending_reward();
			wp_send_json_success( [ 'state' => 'applied' ] );
			wp_die();
		}

		wp_send_json_error( [ 'message' => esc_html__( 'Failed to apply coupon.', 'rewardswp' ) ] );

		wp_die();
	}

	/**
	 * Remove other RewardsWP coupons from cart before applying new one.
	 *
	 * This ensures only one reward coupon is applied at a time (coupon swap).
	 *
	 * @since 1.2.0
	 *
	 * @param string $exclude_code Coupon code to exclude from removal.
	 *
	 * @return void
	 */
	private function remove_other_rewardswp_coupons( string $exclude_code ): void {
		if ( ! method_exists( $this->integration, 'get_applied_coupons' ) || ! $this->rewards_service ) {
			return;
		}

		$applied_coupons = $this->integration->get_applied_coupons();

		foreach ( $applied_coupons as $applied_coupon ) {
			if ( $applied_coupon === $exclude_code ) {
				continue;
			}

			// Check if this coupon belongs to a RewardsWP reward.
			$reward = $this->rewards_service->get_reward_by_coupon_code( $applied_coupon );
			if ( $reward ) {
				$this->integration->remove_coupon( $applied_coupon );
			}
		}
	}
}
