<?php
/**
 * Reward manager service for orchestrating reward creation and redemption.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Services;

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

use Awesomemotive\Rewardswp\Factories\RewardTypesFactory;
use Awesomemotive\Rewardswp\Interfaces\IntegrationInterface;
use Awesomemotive\Rewardswp\Interfaces\RewardTypeInterface;
use Awesomemotive\Rewardswp\Models\Reward;
use Awesomemotive\Rewardswp\RewardTypes\FixedDiscountRewardType;
use Awesomemotive\Rewardswp\Settings\Options\Referrals\RewardsOption;
use Exception;
use function Awesomemotive\Rewardswp\Helpers\get_member_by;
use function Awesomemotive\Rewardswp\Helpers\get_customer_reward;
use function Awesomemotive\Rewardswp\Helpers\get_option_value;
use function Awesomemotive\Rewardswp\Helpers\rewardswp_log_error;

class RewardManagerService {
	/**
	 * The current integration being used by this site.
	 *
	 * @since 1.0.0
	 *
	 * @var IntegrationInterface
	 */
	private IntegrationInterface $integration;

	/**
	 * Stores the generated coupon code.
	 *
	 * @since 1.0.0
	 *
	 * @var string|null
	 */
	private ?string $coupon_code = null;

	/**
	 * Stores the created reward object.
	 *
	 * @since 1.0.0
	 *
	 * @var Reward|null
	 */
	private ?Reward $reward = null;

	/**
	 * Stores any errors that occurred during the process.
	 *
	 * @since 1.0.0
	 *
	 * @var string|null
	 */
	private ?string $error = null;

	/**
	 * The friend's reward options configured for this site.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private array $reward_options;

	/**
	 * The fraud prevention service.
	 *
	 * @since 1.0.0
	 *
	 * @var FraudPreventionService
	 */
	private FraudPreventionService $fraud_prevention_service;

	/**
	 * The RewardTypes factory.
	 *
	 * @since 1.0.0
	 *
	 * @var RewardTypesFactory
	 */
	private RewardTypesFactory $reward_types_factory;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param IntegrationInterface   $integration              The integration instance.
	 * @param FraudPreventionService $fraud_prevention_service The fraud prevention service.
	 * @param RewardTypesFactory     $reward_types_factory     The reward types factory.
	 */
	public function __construct(
		IntegrationInterface $integration,
		FraudPreventionService $fraud_prevention_service,
		RewardTypesFactory $reward_types_factory
	) {
		$this->integration              = $integration;
		$this->fraud_prevention_service = $fraud_prevention_service;
		$this->reward_types_factory     = $reward_types_factory;
		$this->reward_options           = get_option_value( RewardsOption::class )['friend'] ?? [];
	}

	/**
	 * Check if claiming is allowed.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public function can_claim_rewards(): bool {
		$rewards_option = get_option_value( RewardsOption::class );

		if (
			empty( $rewards_option['friend'] ) ||
			empty( $rewards_option['friend']['type'] ) ||
			'unknown' === $rewards_option['friend']['type'] ||
			'points' === $rewards_option['friend']['type'] // Points are only for advocates, not friends.
		) {
			return false;
		}

		return true;
	}

	/**
	 * Claim a reward based on the Friend email and Advocate referral code.
	 *
	 * @since 1.0.0
	 *
	 * @param string $email         The Friend email.
	 * @param string $referral_code An advocate referral code.
	 *
	 * @throws Exception When reward claim fails validation or processing.
	 *
	 * @return Reward|null The reward if successfully created or null in case of failure.
	 */
	public function claim_reward( string $email, string $referral_code ): ?Reward {
		try {
			if ( ! $this->integration->is_active() ) {
				throw new Exception(
					esc_html__( 'This site has no integration active. Please contact the site administrator.', 'rewardswp' )
				);
			}

			if ( $this->fraud_prevention_service->is_self_referral( $email, $referral_code ) ) {
				throw new Exception(
					esc_html__( 'Self-claiming a reward is not allowed.', 'rewardswp' )
				);
			}

			if ( $this->fraud_prevention_service->is_customer( $email ) ) {
				throw new Exception(
					esc_html__( 'It looks like you are already a customer of this site, only new customers can claim rewards.', 'rewardswp' )
				);
			}

			if ( $this->fraud_prevention_service->has_exceeded_referrals_from_user_ip() ) {
				throw new Exception(
					esc_html__( 'You already claimed a reward on this site. Rewards can only be claimed once.', 'rewardswp' )
				);
			}

			$reward = get_customer_reward( $email );

			if ( is_a( $reward, Reward::class ) ) {

				// Reward has been already canceled or issued.
				if ( 'issued' !== $reward->status ) {
					throw new Exception(
						esc_html__( 'You already claimed a reward on this site. Rewards can only be claimed once.', 'rewardswp' )
					);
				}

				// Retrieve the old coupon.
				$this->coupon_code = $reward->get_meta( 'coupon_code' );

				return $reward;
			}

			$advocate = get_member_by( 'referral_code', $referral_code );

			if ( empty( $advocate ) ) {
				throw new Exception(
					esc_html__( 'The referral code is not valid.', 'rewardswp' )
				);
			}

			$reward_type = $this->reward_types_factory->get_reward_type( $this->reward_options['type'] );

			if ( ! is_subclass_of( $reward_type, RewardTypeInterface::class ) ) {
				throw new Exception(
					sprintf(
						/* translators: %s is the reward type */
						esc_html__( 'Incompatible reward type "%s" found', 'rewardswp' ),
						$this->reward_options['type'] ?? 'unknown'
					)
				);
			}

			$reward = $reward_type->claim_reward(
				[
					'email'          => $email,
					'advocate_id'    => $advocate->id,
					'reward_options' => $this->reward_options,
				]
			);

			if ( is_a( $reward, Reward::class ) ) {
				$this->coupon_code = $reward->get_meta( 'coupon_code' );
			}

			return $reward;

		} catch ( Exception $exception ) {
			// Register the last error.
			$this->error = $exception->getMessage();

			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				rewardswp_log_error(
					'Error in claim_reward: {message}',
					[
						'message'   => $exception->getMessage(),
						'member_id' => $member->id ?? 0,
					]
				);
			}

			return null;
		}
	}

	/**
	 * Checks if there was an error during the claim process.
	 *
	 * @since 1.0.0
	 *
	 * @return bool True if there was an error, false otherwise.
	 */
	public function has_error(): bool {
		return ! is_null( $this->error );
	}

	/**
	 * Returns the error message if one occurred.
	 *
	 * @since 1.0.0
	 *
	 * @return string|null The error message or null if no error.
	 */
	public function get_error_message(): ?string {
		return $this->error;
	}

	/**
	 * Get the generated coupon code.
	 *
	 * @since 1.0.0
	 *
	 * @return string|null The coupon code or null if not set.
	 */
	public function get_coupon_code(): ?string {
		return $this->coupon_code;
	}
}
