<?php
/**
 * Points controller for handling points functionality.
 *
 * @package Awesomemotive\Rewardswp\Controllers
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\Controllers;

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

use Awesomemotive\Rewardswp\Helpers\IconHelper;
use Awesomemotive\Rewardswp\Interfaces\ControllerInterface;
use Awesomemotive\Rewardswp\Interfaces\IntegrationInterface;
use Awesomemotive\Rewardswp\Services\PointsService;
use Awesomemotive\Rewardswp\Services\TrackingService;
use Awesomemotive\Rewardswp\Settings\Options\Points\EarnActionsOption;
use Awesomemotive\Rewardswp\Settings\Options\Points\SpendActionsOption;
use Exception;
use function Awesomemotive\Rewardswp\Helpers\get_member_by;
use function Awesomemotive\Rewardswp\Helpers\get_current_member;
use function Awesomemotive\Rewardswp\Helpers\get_option_value;
use function Awesomemotive\Rewardswp\Helpers\get_option_object;
use function Awesomemotive\Rewardswp\Helpers\count_points_entries;
use function Awesomemotive\Rewardswp\Helpers\format_currency;

/**
 * Class PointsController
 *
 * Handles points-related actions and hooks.
 *
 * @since 1.0.1
 */
class PointsController implements ControllerInterface {

	/**
	 * AJAX nonce for advocate actions.
	 *
	 * @since 1.1.0
	 */
	const AJAX_NONCE = 'rewardswp_advocate_form_nonce';

	/**
	 * Points service instance.
	 *
	 * @since 1.0.1
	 * @var PointsService
	 */
	private PointsService $points_service;

	/**
	 * Tracking service instance.
	 *
	 * @since 1.0.1
	 * @var TrackingService
	 */
	private TrackingService $tracking_service;

	/**
	 * Integration instance.
	 *
	 * @since 1.1.0
	 * @var IntegrationInterface
	 */
	private IntegrationInterface $integration;

	/**
	 * Icon helper instance.
	 *
	 * @since 1.1.0
	 * @var IconHelper
	 */
	private IconHelper $icon_helper;

	/**
	 * Constructor.
	 *
	 * @since 1.0.1
	 *
	 * @param PointsService        $points_service   Points service instance.
	 * @param TrackingService      $tracking_service Tracking service instance.
	 * @param IntegrationInterface $integration      Integration instance.
	 * @param IconHelper           $icon_helper      Icon helper instance.
	 */
	public function __construct(
		PointsService $points_service,
		TrackingService $tracking_service,
		IntegrationInterface $integration,
		IconHelper $icon_helper
	) {
		$this->points_service   = $points_service;
		$this->tracking_service = $tracking_service;
		$this->integration      = $integration;
		$this->icon_helper      = $icon_helper;
	}

	/**
	 * Initialize the controller.
	 *
	 * @since 1.0.1
	 *
	 * @return void
	 */
	public function init(): void {
		add_action( 'user_register', [ $this, 'maybe_award_signup_points' ] );
		add_action( 'rewardswp_award_points_on_signup', [ $this, 'maybe_award_signup_points' ] );

		// AJAX handlers for points redemption (logged-in users only).
		add_action( 'wp_ajax_rewardswp_redeem_points_for_coupon', [ $this, 'ajax_redeem_points_for_coupon' ] );
	}

	/**
	 * Award points for new user signup if conditions are met.
	 *
	 * This method is idempotent - it will only award points once per user,
	 * even if called multiple times (e.g., via both user_register and rewardswp_award_points_on_signup hooks).
	 *
	 * @param int $user_id The ID of the newly registered user.
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.1
	 */
	public function maybe_award_signup_points( int $user_id ): void {
		$earn_actions = get_option_value( EarnActionsOption::class );

		// Check if signup points are enabled and configured.
		if ( empty( $earn_actions['signup']['enabled'] ) || empty( $earn_actions['signup']['points'] ) ) {
			return;
		}

		// Get member by used ID.
		$member = get_member_by( 'user_id', $user_id );

		if ( is_null( $member ) ) {
			return;
		}

		// Check if points were already awarded for this signup to avoid duplicates.
		// This ensures idempotency when the method is called from multiple hooks.
		$existing_points = count_points_entries(
			[
				'member_id'   => $member->id,
				'source_type' => 'signup',
				'source_id'   => $user_id,
			]
		);

		if ( $existing_points > 0 ) {
			return; // Points already awarded for this signup.
		}

		// Create points entry for the advocate.
		$points_entry = [
			'member_id'   => absint( $member->id ),
			'points'      => absint( $earn_actions['signup']['points'] ),
			'type'        => 'earned',
			'source_type' => 'signup',
			'source_id'   => $user_id,
			'status'      => 'approved',
			'note_public' => __( 'Points earned for joining the rewards program', 'rewardswp' ),
		];

		$this->points_service->create_points_entry( $points_entry );
	}

	/**
	 * Handle AJAX request to redeem points for a coupon.
	 *
	 * @since 1.1.0
	 * @return void
	 */
	public function ajax_redeem_points_for_coupon(): void {
		// Security: Check nonce
		check_ajax_referer( self::AJAX_NONCE );

		// Get and sanitize inputs
		$spend_action_type = isset( $_POST['spend_action_type'] ) ? sanitize_text_field( wp_unslash( $_POST['spend_action_type'] ) ) : '';
		$spend_action_id   = isset( $_POST['spend_action_id'] ) ? sanitize_text_field( wp_unslash( $_POST['spend_action_id'] ) ) : '';

		// Validate required inputs
		if ( ! $spend_action_type || ! $spend_action_id ) {
			wp_send_json_error( [ 'message' => __( 'Invalid redemption parameters.', 'rewardswp' ) ], 400 );
		}

		// Get spend actions option object
		$spend_actions_option = get_option_object( SpendActionsOption::class );
		if ( ! $spend_actions_option ) {
			wp_send_json_error( [ 'message' => __( 'Spend actions configuration not found.', 'rewardswp' ) ], 500 );
		}

		// Validate spend action type using helper method
		if ( ! $spend_actions_option->is_valid_action_type( $spend_action_type ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid spend action type.', 'rewardswp' ) ], 400 );
		}

		/*
		 * GRANDFATHERED FEATURE ACCESS (Added Dec 2025):
		 * We intentionally allow configured actions to work even if the site
		 * downgraded their plan. The feature gating only blocks ADDING new
		 * actions in the Admin UI (via upsell overlays), not using already-configured ones.
		 *
		 * This means: if a Plus user configured Free Shipping, then downgraded to Basic,
		 * Free Shipping will continue to work until they remove it from settings.
		 *
		 * The validation that the action is enabled (line ~208) ensures only
		 * configured actions can be redeemed.
		 *
		 * @todo Review this decision in Dec 2026 - evaluate if this grandfathering
		 *       approach is working well or if we should enforce stricter gating.
		 */

		// Get current member
		$member = get_current_member();
		if ( ! $member || ! $member->id ) {
			wp_send_json_error( [ 'message' => __( 'Member not found or not logged in.', 'rewardswp' ) ], 404 );
		}

		// Find the specific action configuration using helper method
		$action_config = $spend_actions_option->find_action_config( $spend_action_id, $spend_action_type );
		if ( ! $action_config || empty( $action_config['enabled'] ) ) {
			wp_send_json_error( [ 'message' => __( 'This redemption option is not available.', 'rewardswp' ) ], 400 );
		}

		// Calculate required points and reward amount using helper methods
		$required_points = $spend_actions_option->calculate_required_points( $action_config );
		$reward_amount   = $spend_actions_option->get_reward_amount( $action_config );

		if ( $required_points <= 0 ) {
			wp_send_json_error( [ 'message' => __( 'Invalid points configuration for this redemption.', 'rewardswp' ) ], 400 );
		}

		// Validate member has sufficient points
		if ( $member->points_balance < $required_points ) {
			wp_send_json_error(
				[
					'message' => __( 'You don\'t have enough points for this redemption.', 'rewardswp' ),
				],
				400
			);
		}

		try {
			// Call PointsService to handle the redemption
			$redemption_result = $this->points_service->redeem_points_for_coupon(
				[
					'member_id'         => $member->id,
					'spend_action_type' => $spend_action_type,
					'spend_action_id'   => $spend_action_id,
					'points_to_deduct'  => $required_points,
					'coupon_data'       => $this->points_service->build_coupon_data( $action_config, $spend_action_type, $reward_amount, $member ),
					'action_config'     => $action_config,
					'integration'       => $this->integration,
				]
			);

			if ( is_wp_error( $redemption_result ) ) {
				// Use 400 for validation errors so frontend can display the message.
				// 500 would trigger "Network error" because response.ok is false.
				wp_send_json_error( [ 'message' => $redemption_result->get_error_message() ], 400 );
			}

			// Get formatted reward label using existing method.
			$formatted_title = $spend_actions_option->get_reward_label( $action_config );

			// Get icon HTML for the reward type.
			$icon_html = '';
			if ( ! empty( $action_config['icon'] ) ) {
				ob_start();
				$this->icon_helper->render_icon( $action_config['icon'], 'size-8' );
				$icon_html = ob_get_clean();
			}

			// Build simplified coupon terms array.
			$coupon_terms = [];

			// 1. Minimum spend or "Applies to entire order" (except for free product).
			$minimum_order_amount = 0;
			if ( 'free_product' === $spend_action_type ) {
				$minimum_order_amount = floatval( $action_config['minimum_order_value'] ?? 0 );
			}

			if ( $minimum_order_amount > 0 ) {
				// Show "$X min spend" which implies it applies to the order.
				// Note: not escaped here — this string is rendered via Alpine x-text, which escapes on output.
				$coupon_terms[] = sprintf(
					/* translators: %s: minimum order amount with currency symbol */
					__( '%s min spend', 'rewardswp' ),
					format_currency( $minimum_order_amount )
				);
			} elseif ( 'free_product' !== $spend_action_type ) {
				// Only show "Applies to entire order" for non-free-product rewards.
				$coupon_terms[] = __( 'Applies to entire order', 'rewardswp' );
			}

			// 2. Expiry.
			$expiry_text = '';
			if ( ! empty( $redemption_result['expiry_date'] ) ) {
				$expiry_text    = sprintf(
					/* translators: %s: expiration date */
					__( 'Expires %s', 'rewardswp' ),
					date_i18n( 'M j, Y', strtotime( $redemption_result['expiry_date'] ) )
				);
				$coupon_terms[] = $expiry_text;
			} else {
				$expiry_text    = __( 'No expiry', 'rewardswp' );
				$coupon_terms[] = $expiry_text;
			}

			// Join with bullet separator.
			$coupon_terms_text = implode( ' • ', $coupon_terms );

			// Pluralize points label. Rendered via Alpine x-text, which escapes on output.
			$points_label = $redemption_result['points_spent'] === 1
			? __( 'point', 'rewardswp' )
			: __( 'points', 'rewardswp' );

			// Extract free_product_id and price for free_product rewards (for smart button text).
			$free_product_id    = null;
			$free_product_price = 0;
			if ( 'free_product' === $spend_action_type ) {
				$products_included = $action_config['products_included'] ?? [];
				if ( is_array( $products_included ) && count( $products_included ) === 1 ) {
					$product         = reset( $products_included );
					$free_product_id = is_array( $product ) ? ( $product['id'] ?? null ) : $product;

					// Get product price for minimum spend calculation.
					if ( $free_product_id && method_exists( $this->integration, 'get_product' ) ) {
						$product_data = $this->integration->get_product( $free_product_id );
						if ( is_array( $product_data ) && isset( $product_data['price'] ) ) {
							$free_product_price = floatval( $product_data['price'] );
						}
					}
				}
			}

			wp_send_json_success(
				[
					'message'              => __( 'Points redeemed successfully!', 'rewardswp' ),
					'coupon_code'          => $redemption_result['coupon_code'],
					'new_balance'          => $redemption_result['new_balance'],
					'points_spent'         => $redemption_result['points_spent'],
					'reward_id'            => $redemption_result['reward_id'] ?? 0,
					'icon_html'            => $icon_html,
					'title'                => $formatted_title,
					'points_label'         => $points_label,
					'coupon_terms'         => $coupon_terms_text,
					'expiry_text'          => $expiry_text,
					'is_applied'           => false, // Never applied immediately after redemption.
					'limit_reached'        => $redemption_result['limit_reached'] ?? false,
					'action_id'            => $redemption_result['action_id'] ?? '',
					'reward_type'          => $spend_action_type,
					'free_product_id'      => $free_product_id,
					'minimum_order_amount' => $minimum_order_amount,
					'free_product_price'   => $free_product_price,
				]
			);

		} catch ( Exception $e ) {
			wp_send_json_error( [ 'message' => __( 'Failed to redeem points. Please try again.', 'rewardswp' ) ], 500 );
		}
	}
}
