<?php
/**
 * Feature gating service for managing feature access based on plan tiers.
 *
 * @package Awesomemotive\Rewardswp\Services
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\Services;

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

use function Awesomemotive\Rewardswp\Helpers\get_rewardswp_plan_tier;
use function Awesomemotive\Rewardswp\Helpers\get_rewardswp_plan_tier_names;
use function Awesomemotive\Rewardswp\Helpers\is_license_valid;

/**
 * Feature gating service class.
 *
 * Handles feature access validation, caching, and plan-based feature gating.
 *
 * @since 1.0.0
 */
final class FeatureGatingService {

	/**
	 * Cache key for consolidated feature gates.
	 *
	 * @since 1.0.0
	 */
	const CACHE_KEY = 'rewardswp_feature_gates';

	/**
	 * Cache expiration time in seconds.
	 *
	 * @since 1.0.0
	 */
	const CACHE_EXPIRATION = 900; // 15 minutes.

	/**
	 * Lock key for cache updates.
	 *
	 * @since 1.0.0
	 */
	const LOCK_KEY = 'rewardswp_feature_gates_lock';

	/**
	 * Lock timeout in seconds.
	 *
	 * @since 1.0.0
	 */
	const LOCK_TIMEOUT = 5;

	/**
	 * Get the feature access matrix defining which plans can access which features.
	 *
	 * This method returns the feature matrix that defines which plans have access
	 * to specific features. In the future, this could be loaded from an API.
	 *
	 * @since 1.0.0
	 *
	 * @return array Feature access matrix.
	 */
	public function get_feature_matrix(): array {
		return [
			'basic_color_controls'          => [ 'lite', 'basic', 'plus', 'pro' ],
			'gradient_controls'             => [ 'plus', 'pro' ],
			'advanced_color_tools'          => [ 'basic', 'plus', 'pro' ],
			'priority_support'              => [ 'basic', 'plus', 'pro' ],
			'rewards_widget'                => [ 'lite', 'basic', 'plus', 'pro' ],
			'open_panel_from_menu'          => [ 'lite', 'basic', 'plus', 'pro' ],
			'loyalty_program_points'        => [ 'lite', 'basic', 'plus', 'pro' ],
			'product_points_banner'         => [ 'lite', 'basic', 'plus', 'pro' ],

			// Points.
			'points_branding'               => [ 'basic', 'plus', 'pro' ],
			'points_expiry'                 => [ 'basic', 'plus', 'pro' ],

			// Referrals.
			'referrals_program'             => [ 'basic', 'plus', 'pro' ],
			'referral_reward_expiration'    => [ 'plus', 'pro' ],

			// Widget color settings.
			'brand_foreground_mode'         => [ 'basic', 'plus', 'pro' ],

			// Earn points actions.
			'earn_points_actions'           => [ 'lite', 'basic', 'plus', 'pro' ],
			'earn_purchase'                 => [ 'lite', 'basic', 'plus', 'pro' ],
			'earn_signup'                   => [ 'lite', 'basic', 'plus', 'pro' ],
			'earn_first_order'              => [ 'lite', 'basic', 'plus', 'pro' ],
			'earn_referral'                 => [ 'lite', 'basic', 'plus', 'pro' ],
			'earn_product_review'           => [ 'lite', 'basic', 'plus', 'pro' ],
			'earn_birthday'                 => [ 'pro' ],

			// Redeem points actions.
			'redeem_points_actions'         => [ 'lite', 'basic', 'plus', 'pro' ],
			'redeem_fixed_discount'         => [ 'lite', 'basic', 'plus', 'pro' ],
			'redeem_percentage_discount'    => [ 'basic', 'plus', 'pro' ],
			'redeem_free_shipping'          => [ 'plus', 'pro' ],
			'redeem_free_product'           => [ 'pro' ],
			'redeem_options_limit'          => [ 'basic', 'plus', 'pro' ],

			// Advocate rewards.
			'advocate_reward_fixed'         => [ 'lite', 'basic', 'plus', 'pro' ],
			'advocate_reward_percentage'    => [ 'lite', 'basic', 'plus', 'pro' ],
			'advocate_reward_free_shipping' => [ 'plus', 'pro' ],
			'advocate_reward_free_product'  => [ 'pro' ],
			'advocate_reward_points'        => [ 'lite', 'basic', 'plus', 'pro' ],

			// Friend rewards.
			'friend_reward'                 => [ 'basic', 'plus', 'pro' ],

			'unique_coupon_types'           => [ 'lite', 'basic', 'plus', 'pro' ],

			// Emails.
			'email_banner'                  => [ 'basic', 'plus', 'pro' ],
			'points_expiry_email'           => [ 'basic', 'plus', 'pro' ],
			'friend_reward_email'           => [ 'basic', 'plus', 'pro' ],
			'birthday_reward_email'         => [ 'pro' ],

			// Misc.
			'reward_expiration'             => [ 'basic', 'plus', 'pro' ],
			'unlimited_members'             => [ 'lite', 'basic', 'plus', 'pro' ],
			'unlimited_rewards'             => [ 'lite', 'basic', 'plus', 'pro' ],
			'unlimited_referrals'           => [ 'lite', 'basic', 'plus', 'pro' ],

			// Widget.
			'panel_animations'              => [ 'pro' ],

			// Reporting.
			'exports'                       => [ 'basic', 'plus', 'pro' ],
			'orders_using_rewards_chart'    => [ 'basic', 'plus', 'pro' ],
			'first_time_redeemers_kpi'      => [ 'basic', 'plus', 'pro' ],
			'orders_using_rewards_kpi'      => [ 'basic', 'plus', 'pro' ],
			'dashboard_deltas'              => [ 'basic', 'plus', 'pro' ],

			// Anti-Spam.
			'antispam_honeypot'             => [ 'lite', 'basic', 'plus', 'pro' ],
		];
	}

	/**
	 * Check if the current user can access a specific feature.
	 *
	 * This method validates feature access based on the user's plan tier
	 * and caches the result for performance.
	 *
	 * @since 1.0.0
	 *
	 * @param string $feature The feature to check access for.
	 * @return bool Whether the user can access the feature.
	 */
	public function can_access( string $feature ): bool {
		$feature_matrix = $this->get_feature_matrix();

		// Check if feature exists in our matrix.
		if ( ! $this->validate_feature_exists( $feature ) ) {
			return false;
		}

		// Get consolidated feature gates cache.
		$feature_gates_cache = $this->get_cache();

		// If cache exists and contains this feature, return cached result.
		if ( false !== $feature_gates_cache && isset( $feature_gates_cache[ $feature ] ) ) {
			return (bool) $feature_gates_cache[ $feature ];
		}

		// Check if DevTools override is active (ignore license check if override exists).
		$has_devtools_override = $this->has_devtools_override();

		// Get current plan tier.
		$current_plan = get_rewardswp_plan_tier();

		// For paid versions (non-lite), verify license is valid unless DevTools override is active.
		if ( 'lite' !== $current_plan && ! $has_devtools_override ) {
			if ( ! is_license_valid() ) {
				// License is not valid, deny access.
				// Acquire lock before updating cache to prevent race conditions.
				if ( $this->acquire_lock() ) {
					$current_cache = $this->get_cache();
					if ( false === $current_cache ) {
						$current_cache = [];
					}
					$current_cache[ $feature ] = false;
					$this->set_cache( $current_cache );
					$this->release_lock();
				}
				return false;
			}
		}

		// Check if the current plan has access to this feature.
		$has_access = in_array( $current_plan, $feature_matrix[ $feature ], true );

		// Acquire lock before updating cache to prevent race conditions.
		if ( $this->acquire_lock() ) {
			// Re-read cache after acquiring lock to get latest state.
			$current_cache = $this->get_cache();
			if ( false === $current_cache ) {
				$current_cache = [];
			}

			// Update cache with our calculated result.
			$current_cache[ $feature ] = $has_access;
			$this->set_cache( $current_cache );

			$this->release_lock();
		}

		return $has_access;
	}

	/**
	 * Get available features for the current plan.
	 *
	 * @since 1.0.0
	 *
	 * @return array Array of available feature names.
	 */
	public function get_available_features(): array {
		$current_plan       = get_rewardswp_plan_tier();
		$feature_matrix     = $this->get_feature_matrix();
		$available_features = [];

		foreach ( $feature_matrix as $feature => $allowed_plans ) {
			if ( in_array( $current_plan, $allowed_plans, true ) ) {
				$available_features[] = $feature;
			}
		}

		return $available_features;
	}

	/**
	 * Get the required plans for a feature.
	 *
	 * Returns an array of plan names that have access to the specified feature.
	 * Excludes 'lite' from the result since Lite is the free version.
	 *
	 * @since 1.1.0
	 *
	 * @param string $feature The feature slug to check.
	 * @return array Array of plan names (e.g., ['Plus', 'Pro']).
	 */
	public function get_required_plans( string $feature ): array {
		$feature_matrix = $this->get_feature_matrix();

		// Check if feature exists in our matrix.
		if ( ! isset( $feature_matrix[ $feature ] ) ) {
			return [];
		}

		$tier_names     = get_rewardswp_plan_tier_names();
		$allowed_plans  = $feature_matrix[ $feature ];
		$required_plans = [];

		foreach ( $allowed_plans as $plan_slug ) {
			// Skip 'lite' since it's the free version and not relevant for upgrade prompts.
			if ( 'lite' === $plan_slug ) {
				continue;
			}

			if ( isset( $tier_names[ $plan_slug ] ) ) {
				$required_plans[] = $tier_names[ $plan_slug ];
			}
		}

		return $required_plans;
	}

	/**
	 * Get the minimum required plan for a feature.
	 *
	 * Returns the lowest tier plan name that has access to the feature.
	 *
	 * @since 1.1.0
	 *
	 * @param string $feature The feature slug to check.
	 * @return string The minimum required plan name (e.g., 'Plus'), or 'Pro' as fallback.
	 */
	public function get_minimum_required_plan( string $feature ): string {
		$required_plans = $this->get_required_plans( $feature );

		if ( empty( $required_plans ) ) {
			return 'Pro';
		}

		// Plan priority order (lowest to highest).
		$plan_priority = [ 'Basic', 'Plus', 'Pro' ];

		foreach ( $plan_priority as $plan ) {
			if ( in_array( $plan, $required_plans, true ) ) {
				return $plan;
			}
		}

		return 'Pro';
	}

	/**
	 * Clear all feature access cache.
	 *
	 * This should be called when license status changes.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function clear_cache(): void {
		delete_transient( self::CACHE_KEY );
	}

	/**
	 * Acquire a lock for updating feature gates cache.
	 *
	 * Uses transient-based locking to prevent race conditions when updating
	 * the consolidated feature gates cache. Uses wp_cache_add() when available
	 * for atomic lock acquisition, falls back to transient-based locking.
	 *
	 * @since 1.0.0
	 *
	 * @param int $timeout Lock timeout in seconds. Default 5 seconds.
	 * @return bool True if lock was acquired, false otherwise.
	 */
	private function acquire_lock( int $timeout = self::LOCK_TIMEOUT ): bool {
		$lock_id = wp_generate_password( 32, false );

		// Try to use wp_cache_add() first (atomic operation if object cache is available).
		if ( wp_cache_add( self::LOCK_KEY, $lock_id, '', $timeout ) ) {
			return true;
		}

		// Fallback to transient-based locking with retry logic.
		$max_attempts = 3;
		$attempt      = 0;

		while ( $attempt < $max_attempts ) {
			// Check if lock exists.
			$existing_lock = get_transient( self::LOCK_KEY );
			if ( false === $existing_lock ) {
				// Lock doesn't exist, try to acquire it.
				set_transient( self::LOCK_KEY, $lock_id, $timeout );
				// Verify we got the lock (another process might have acquired it).
				$verify_lock = get_transient( self::LOCK_KEY );
				if ( $verify_lock === $lock_id ) {
					return true;
				}
			}

			// Lock is held by another process, wait briefly and retry.
			$attempt++;
			if ( $attempt < $max_attempts ) {
				usleep( 100000 * $attempt ); // Exponential backoff: 100ms, 200ms, 300ms.
			}
		}

		return false;
	}

	/**
	 * Release the lock for updating feature gates cache.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	private function release_lock(): void {
		wp_cache_delete( self::LOCK_KEY );
		delete_transient( self::LOCK_KEY );
	}

	/**
	 * Get the consolidated feature gates cache.
	 *
	 * @since 1.0.0
	 *
	 * @return array|false The cached feature gates array or false if not cached.
	 */
	private function get_cache() {
		return get_transient( self::CACHE_KEY );
	}

	/**
	 * Set the consolidated feature gates cache.
	 *
	 * @since 1.0.0
	 *
	 * @param array $cache The feature gates array to cache.
	 * @return void
	 */
	private function set_cache( array $cache ): void {
		set_transient( self::CACHE_KEY, $cache, self::CACHE_EXPIRATION );
	}

	/**
	 * Check if DevTools override is active.
	 *
	 * @since 1.0.0
	 *
	 * @return bool True if DevTools override is active, false otherwise.
	 */
	private function has_devtools_override(): bool {
		if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
			return false;
		}

		$devtools_override = get_option( 'rewardswp_devtools_plan_override', '' );
		return ! empty( $devtools_override ) && in_array( $devtools_override, [ 'lite', 'basic', 'plus', 'pro' ], true );
	}

	/**
	 * Validate if a feature exists in the feature matrix.
	 *
	 * @since 1.0.0
	 *
	 * @param string $feature The feature to validate.
	 * @return bool True if feature exists, false otherwise.
	 */
	private function validate_feature_exists( string $feature ): bool {
		$feature_matrix = $this->get_feature_matrix();
		return isset( $feature_matrix[ $feature ] );
	}
}
