<?php
/**
 * Reward helper functions.
 *
 * @package Awesomemotive\Rewardswp\Helpers
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\Helpers;

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

use Awesomemotive\Rewardswp\Database\Tables\RewardsTable;
use Awesomemotive\Rewardswp\Factories\RewardTypesFactory;
use Awesomemotive\Rewardswp\Models\Reward;
use Awesomemotive\Rewardswp\Settings\Options\Points\SpendActionsOption;
use function Awesomemotive\Rewardswp\Helpers\get_option_object;
use Awesomemotive\Rewardswp\Services\ReferralsService;
use Awesomemotive\Rewardswp\Services\RewardsService;
use Awesomemotive\Rewardswp\Services\TrackingService;
use Exception;
use WP_Error;
use WP_User;

/**
 * Create a new reward.
 *
 * @param array $args The arguments for creating the reward.
 *
 * @return Reward|WP_Error The created reward object or a WP_Error on failure.
 */
function create_reward( array $args ) {
	$args = wp_parse_args(
		$args,
		[
			'referral_id'    => null,
			'advocate_id'    => 0,
			'reward_type'    => '',
			'recipient_type' => '',
			'amount'         => 0,
			'status'         => 'pending',
			'created_at'     => get_current_time(),
			'applied_at'     => '',
			'expires_at'     => '',
		]
	);

	try {
		// Create a new reward object.
		$reward = am_rewardswp()->get_container()->make( Reward::class );

		// Populate the object.
		$reward->hydrate( $args );

		// Attempt to create the reward.
		$reward->create();

		return $reward;
	} catch ( Exception $e ) {
		return new WP_Error( 'general_error', $e->getMessage() );
	}
}

/**
 * Return a reward by id.
 *
 * @since 1.0.0
 *
 * @param int $reward_id The reward ID.
 *
 * @return Reward|null
 */
function get_reward( int $reward_id ): ?Reward {
	try {
		$referral = am_rewardswp()->get_container()->make( Reward::class );

		$referral->load( $reward_id );

		return $referral;
	} catch ( Exception $e ) {
		return null;
	}
}

/**
 * Retrieve rewards based on the given parameters and pagination options.
 *
 * @since 1.0.0
 *
 * @param array $query_args An associative array of conditions to filter rewards.
 *                          See {@see RewardsTable::get_columns()} for available parameters.
 * @param int   $per_page   Optional. The number of items per page for pagination. Default to 10.
 * @param int   $page       Optional. The page number for pagination. Default to 1.
 *
 * @return Reward[] Array of Reward objects.
 */
function get_rewards( array $query_args = [], int $per_page = 10, int $page = 1 ): array {
	try {
		$rewards_service = am_rewardswp()->get_container()->make( RewardsService::class );
		return $rewards_service->get_rewards( $query_args, $per_page, $page );
	} catch ( Exception $e ) {
		return [];
	}
}

/**
 * Retrieve rewards count based on the given parameters and pagination options.
 *
 * @since 1.0.0
 *
 * @param array $query_args An associative array of conditions to filter rewards.
 *                          See {@see RewardsTable::get_columns()} for available parameters.
 *
 * @return int.
 */
function count_rewards( array $query_args = [] ): ?int {
	try {
		$rewards_service = am_rewardswp()->get_container()->make( RewardsService::class );
		return $rewards_service->count_rewards( $query_args );
	} catch ( Exception $e ) {
		return null;
	}
}

/**
 * Attempt to retrieve the reward for a given customer.
 *
 * Searches ALL referrals for this email (across different visits/sessions)
 * and returns any existing friend reward, to prevent duplicate claims.
 *
 * @since 1.0.0
 *
 * @param string $email The customer email.
 *
 * @return Reward|null The reward object, null otherwise.
 */
function get_customer_reward( string $email ): ?Reward {
	try {
		$referral_service = am_rewardswp()->get_container()->get( ReferralsService::class );

		$referrals = $referral_service->get_referrals(
			[
				'referred_email' => sanitize_email( $email ),
			],
			99999 // Get all referrals for this email, not just the first page.
		);

		if ( empty( $referrals ) ) {
			return null; // This is a new customer, never referred before.
		}

		$rewards_service = am_rewardswp()->get_container()->get( RewardsService::class );

		// Search for friend rewards across ALL referrals for this email.
		// This prevents duplicates when the same person visits multiple times.
		foreach ( $referrals as $referral ) {
			$rewards = $rewards_service->get_rewards(
				[
					'referral_id'    => $referral->id,
					'recipient_type' => 'friend',
				],
				1 // We only need to find one existing reward.
			);

			if ( ! empty( $rewards ) ) {
				// Found an existing reward for this customer.
				return $rewards[0];
			}
		}

		return null; // Customer has been referred but never claimed a reward.
	} catch ( Exception $e ) {
		return null;
	}
}

/**
 * Attempt to create a reward for a given customer.
 *
 * @since 1.0.0
 *
 * @param string $email         The customer email.
 * @param int    $advocate_id   The advocate that referred the customer.
 * @param string $reward_type   The reward type.
 * @param float  $reward_amount The reward amount.
 *
 * @return Reward|null The new reward object or null otherwise.
 */
function create_customer_reward(
	string $email,
	int $advocate_id,
	string $reward_type,
	float $reward_amount
): ?Reward {
	try {
		$user = get_user_by( 'email', $email );

		$tracking_service = am_rewardswp()->get_container()->get( TrackingService::class );

		$referral = create_referral(
			[
				'advocate_id'      => $advocate_id,
				'event_id'         => $tracking_service->get_cookie( $tracking_service::VISIT_ID_COOKIE_NAME ),
				'referred_user_id' => is_a( $user, WP_User::class ) ? $user->ID : null,
				'referred_email'   => $email,
			]
		);

		if ( is_wp_error( $referral ) ) {
			throw new Exception( $referral->get_error_message() );
		}

		$reward = create_reward(
			[
				'referral_id'    => $referral->id,
				// It starts unbound from the advocate. It will be bounded if the friend becomes advocate.
				'advocate_id'    => 0,
				'recipient_type' => 'friend',
				'reward_type'    => $reward_type,
				'status'         => 'issued',
				'amount'         => $reward_amount,
			]
		);

		if ( is_wp_error( $reward ) ) {
			return null;
		}

		return $reward;
	} catch ( Exception $e ) {
		return null;
	}
}

/**
 * Retrieves rewards associated with a specific advocate.
 *
 * @param int   $advocate_id The ID of the advocate.
 * @param array $args        Optional. Additional arguments to filter rewards. Default empty array.
 * @param int   $per_page    Optional. Number of rewards per page. Default 99999.
 * @param int   $page        Optional. The page number to retrieve. Default 1.
 *
 * @return Reward[] An array of rewards matching the given parameters.
 */
function get_advocate_rewards( int $advocate_id, array $args = [], int $per_page = 99999, int $page = 1 ): array {
	try {
		$rewards_service = am_rewardswp()->get_container()->get( RewardsService::class );

		return $rewards_service->get_advocate_rewards(
			$advocate_id,
			$args,
			$per_page,
			$page
		);
	} catch ( Exception $e ) {
		return [];
	}
}

/**
 * Retrieve an array with the rewards divided by group.
 *
 * @since 1.0.0
 *
 * @param Reward[] $rewards Array of reward objects.
 *
 * @return array An array with the rewards divided by group.
 */
function get_grouped_rewards( array $rewards ): array {
	$grouped_rewards = [];

	foreach ( $rewards as $reward ) {

		if ( ! is_a( $reward, Reward::class ) ) {
			continue;
		}

		$reward_type = $reward->reward_type ?? 'unknown';

		if ( ! isset( $grouped_rewards[ $reward_type ] ) ) {
			$grouped_rewards[ $reward_type ] = [];
		}

		$grouped_rewards[ $reward_type ][] = $reward;
	}

	return $grouped_rewards;
}

/**
 * Retrieve the reward expiration date according to a recipient.
 *
 * @since 1.0.0
 *
 * @param string      $recipient The recipient type ('advocate' or 'friend').
 * @param string|null $start_date Optional start date for expiration calculation.
 *
 * @return string|null The expiration date in 'Y-m-d H:i:s' format or null.
 *
 * @throws Exception
 */
function get_reward_expiration_date( string $recipient, ?string $start_date = null ): ?string {
	try {
		$expiration_period = am_rewardswp()->get_container()
			->get( \Awesomemotive\Rewardswp\Services\RewardsService::class )
			->get_reward_expiration( $recipient );

		if ( empty( $expiration_period ) || 'never' === $expiration_period ) {
			return null;
		}

		return add_time_to_date( $expiration_period, $start_date );
	} catch ( Exception $e ) {
		return null;
	}
}

/**
 * Retrieves the formatted amount for the given reward.
 *
 * @since 1.0.0
 *
 * @param Reward $reward The reward object for which the formatted amount is to be retrieved.
 * @param string $format Optional. Format identifier passed to the reward type implementation. Default 'default'.
 * @return string|null The formatted reward amount, or null if an exception occurs.
 */
function get_reward_formatted_amount( Reward $reward, string $format = 'default' ): ?string {
	try {
		$reward_type = am_rewardswp()->get_container()->get( RewardTypesFactory::class )->get_reward_type( $reward->reward_type );
		$reward_type->set_reward( $reward );

		return $reward_type->get_reward_formatted_amount( 0, $format );
	} catch ( Exception $exception ) {
		return null;
	}
}

/**
 * Get display title for a Points Spend Action.
 *
 * Prefers the custom title set in settings; falls back to the reward formatted
 * amount provided by the RewardType implementation.
 *
 * @since 1.1.0
 *
 * @param array $action_config The spend action configuration.
 * @return string Display title.
 */
function get_spend_action_display_title( array $action_config ): string {
	// 1) Custom title from settings
	$custom_title = isset( $action_config['custom_title'] ) ? trim( (string) $action_config['custom_title'] ) : '';
	if ( '' !== $custom_title ) {
		return sanitize_text_field( $custom_title );
	}

	// 1.5) Free product: include product name when exactly one product is configured
	$type = $action_config['type'] ?? '';
	if ( 'free_product' === $type && ! empty( $action_config['products_included'] ) && is_array( $action_config['products_included'] ) ) {
		$products = array_values( $action_config['products_included'] );
		if ( count( $products ) === 1 ) {
			$product_name = isset( $products[0]['name'] ) ? trim( (string) $products[0]['name'] ) : '';
			if ( '' !== $product_name ) {
				return sprintf(
					/* translators: %s: Free product name. */
					esc_html__( 'Free %s', 'rewardswp' ),
					sanitize_text_field( $product_name )
				);
			}
		}
	}

	// 2) RewardType formatted amount as fallback
	try {
		/**
		 * Spend actions option instance.
		 *
		 * @var SpendActionsOption $spend_option
		 */
		$spend_option = get_option_object( SpendActionsOption::class );

		$discount_amount   = absint( $action_config['coupon_amount'] ?? 1 );
		$discount_percent  = absint( $action_config['coupon_amount'] ?? 1 );
		$calculated_amount = $spend_option->get_reward_amount( $action_config, $discount_amount, $discount_percent );

		/**
		 * Reward types factory instance.
		 *
		 * @var RewardTypesFactory $factory
		 */
		$factory  = am_rewardswp()->get_container()->get( RewardTypesFactory::class );
		$type_obj = $factory->get_reward_type( $action_config['type'] ?? '' );
		if ( $type_obj && method_exists( $type_obj, 'get_reward_formatted_amount' ) ) {
			return $type_obj->get_reward_formatted_amount( (float) $calculated_amount );
		}
	} catch ( \Exception $e ) {
		// Fall through to empty string
	}

	return '';
}

/**
 * Get display title for a referral reward configuration.
 *
 * Handles all reward types including free_product with proper formatting.
 * For free_product rewards, returns "Free {product_name}" using products_included.
 *
 * @since 1.2.0
 *
 * @param array $reward_config Reward configuration array with 'type', 'reward_amount', 'products_included', etc.
 * @return string Display title (e.g., "$10 off", "free Software eBook").
 */
function get_referral_reward_display_title( array $reward_config ): string {
	$type   = $reward_config['type'] ?? '';
	$amount = (float) ( $reward_config['reward_amount'] ?? 0 );

	// For free_product rewards, use product name from products_included.
	if ( 'free_product' === $type ) {
		// Priority: reward_description (custom name) > products_included name.
		$product_name = '';
		if ( ! empty( $reward_config['reward_description'] ) ) {
			$product_name = sanitize_text_field( $reward_config['reward_description'] );
		} elseif ( ! empty( $reward_config['products_included'][0]['name'] ) ) {
			$product_name = sanitize_text_field( $reward_config['products_included'][0]['name'] );
		}

		if ( ! empty( $product_name ) ) {
			// If there's a value limit, show "$X off Product".
			if ( $amount > 0 ) {
				return sprintf(
					/* translators: %1$s: currency amount, %2$s: product name */
					esc_html__( '%1$s off %2$s', 'rewardswp' ),
					esc_html( format_currency( $amount ) ),
					esc_html( $product_name )
				);
			}
		return sprintf(
			/* translators: %s: product name */
			esc_html__( 'free %s', 'rewardswp' ),
			esc_html( $product_name )
		);
		}

		// Fallback for free product without a name.
		if ( $amount > 0 ) {
			return sprintf(
				/* translators: %s: currency amount */
				esc_html__( '%s off product', 'rewardswp' ),
				esc_html( format_currency( $amount ) )
			);
		}
		return esc_html__( 'free product', 'rewardswp' );
	}

	// For other reward types, use the existing logic.
	return get_reward_display_title_from_values(
		$reward_config['name'] ?? '',
		$type,
		$amount
	);
}

/**
 * Resolve a display title given raw values (used by emails or other flows).
 *
 * @since 1.1.0
 *
 * @param string|null $custom_title Optional custom title. If non-empty, returned as-is (sanitized).
 * @param string      $reward_type  Reward type slug.
 * @param float       $reward_amount Reward amount used by the type.
 * @return string Display title.
 */
function get_reward_display_title_from_values( ?string $custom_title, string $reward_type, float $reward_amount ): string {
	$custom_title = trim( (string) $custom_title );
	if ( '' !== $custom_title ) {
		return sanitize_text_field( $custom_title );
	}

	try {
		/**
		 * Reward types factory instance.
		 *
		 * @var RewardTypesFactory $factory
		 */
		$factory = am_rewardswp()->get_container()->get( RewardTypesFactory::class );
		$type    = $factory->get_reward_type( $reward_type );
		if ( $type && method_exists( $type, 'get_reward_formatted_amount' ) ) {
			return $type->get_reward_formatted_amount( $reward_amount );
		}
	} catch ( \Exception $e ) {
		// ignore
	}

	return '';
}

/**
 * Build a reward config array from an existing Reward model.
 *
 * Produces a structure compatible with the admin config format used by
 * get_referral_reward_display_title() and the widget's rendering pipeline.
 * Used when displaying a previously claimed reward whose config may have
 * changed since it was issued.
 *
 * @since 1.3.1
 *
 * @param Reward $reward The reward model.
 * @return array Config-compatible array with type, reward_amount, products_included, etc.
 */
function get_reward_config_from_model( Reward $reward ): array {
	$config = [
		'type'               => $reward->reward_type,
		'reward_amount'      => $reward->amount,
		'reward_description' => $reward->get_meta( 'reward_description' ) ?? '',
		'products_included'  => [],
	];

	// For free_product rewards, include product info from stored metadata.
	$free_product_name = $reward->get_meta( 'free_product_name' );
	if ( ! empty( $free_product_name ) ) {
		$config['products_included'] = [
			[
				'id'   => $reward->get_meta( 'free_product_id' ) ?? '',
				'name' => $free_product_name,
			],
		];
	}

	return $config;
}

/**
 * Delete all rewards and their associated metadata for a given member ID.
 *
 * @since 1.1.0
 *
 * @param int $member_id The ID of the member (advocate).
 * @return bool True if all deletions were successful or if no rewards/metadata existed, false on any error.
 */
function delete_rewards_by_member_id( int $member_id ): bool {
	try {
		/**
		 * Rewards service instance.
		 *
		 * @var RewardsService $rewards_service
		 */
		$rewards_service = am_rewardswp()->get_container()->get( RewardsService::class );
		return $rewards_service->delete_by_member_id( $member_id );
	} catch ( Exception $e ) {
		// Optionally log the error: error_log( $e->getMessage() );
		return false;
	}
}
