<?php
/**
 * Points reward type implementation.
 *
 * @package Awesomemotive\Rewardswp\RewardTypes
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\RewardTypes;

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

use Awesomemotive\Rewardswp\Abstracts\AbstractRewardType;
use Awesomemotive\Rewardswp\Factories\MailerFactory;
use Awesomemotive\Rewardswp\Factories\ViewsFactory;
use Awesomemotive\Rewardswp\Models\Referral;
use Awesomemotive\Rewardswp\Models\Reward;
use Awesomemotive\Rewardswp\Notifications\Emails\AdvocateRewardEmail;
use Awesomemotive\Rewardswp\Services\PointsService;
use Awesomemotive\Rewardswp\Settings\Options\Referrals\RewardsOption;
use Awesomemotive\Rewardswp\Views\Panel\Parts\EmptyRewardsView;
use Awesomemotive\Rewardswp\Views\Panel\RewardTypes\BaseRewardView;
use Exception;
use function Awesomemotive\Rewardswp\Helpers\create_reward;
use function Awesomemotive\Rewardswp\Helpers\format_points_with_label;
use function Awesomemotive\Rewardswp\Helpers\get_date_diff_in_days;
use function Awesomemotive\Rewardswp\Helpers\get_member_by;
use function Awesomemotive\Rewardswp\Helpers\get_option_value;
use function Awesomemotive\Rewardswp\Helpers\get_reward_expiration_date;
use function Awesomemotive\Rewardswp\Helpers\get_user_full_name;
use function Awesomemotive\Rewardswp\Helpers\rewardswp_log_error;

/**
 * Points Reward Type.
 *
 * Reward advocates with points when their referrals are completed.
 *
 * @since 1.1.0
 */
class PointsRewardType extends AbstractRewardType {
	/**
	 * Points service instance.
	 *
	 * @since 1.1.0
	 *
	 * @var PointsService
	 */
	private PointsService $points_service;

	/**
	 * Mailer factory instance.
	 *
	 * @since 1.1.0
	 *
	 * @var MailerFactory
	 */
	private MailerFactory $mailer_factory;

	/**
	 * Views factory instance.
	 *
	 * @since 1.1.0
	 *
	 * @var ViewsFactory
	 */
	private ViewsFactory $views_factory;

	/**
	 * Constructor.
	 *
	 * @since 1.1.0
	 *
	 * @param PointsService  $points_service  Points service instance.
	 * @param MailerFactory  $mailer_factory  Mailer factory instance.
	 * @param ViewsFactory   $views_factory   Views factory instance.
	 */
	public function __construct(
		PointsService $points_service,
		MailerFactory $mailer_factory,
		ViewsFactory $views_factory
	) {
		$this->points_service = $points_service;
		$this->mailer_factory = $mailer_factory;
		$this->views_factory  = $views_factory;
	}
	/**
	 * Get the unique reward type identifier.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_type(): string {
		return 'points';
	}

	/**
	 * Get the title used in admin screens.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_admin_title(): string {
		return __( 'Points', 'rewardswp' );
	}

	/**
	 * Get the advocate-specific title for admin.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_admin_advocate_title(): string {
		return __( 'Reward Advocates with Points', 'rewardswp' );
	}

	/**
	 * Get the friend-specific title for admin.
	 *
	 * Note: Points rewards are only for advocates in Lite version.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_admin_friend_title(): string {
		return __( 'Reward Friends with Points', 'rewardswp' );
	}

	/**
	 * Get the title used in the front-end panel.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_panel_title(): string {
		if ( is_a( $this->reward, Reward::class ) ) {
			// The formatted amount already contains the full readable label (e.g., "500 points").
			return $this->get_reward_formatted_amount();
		}

		return __( 'Points reward', 'rewardswp' );
	}

	/**
	 * Get the subtitle used in the front-end panel.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_panel_subtitle(): string {
		if ( empty( $this->reward ) ) {
			return __( 'Referral reward', 'rewardswp' );
		}

		// A reward with no expiry date (e.g. "never expires" points rewards) has no
		// day count to show; avoid a misleading "expires today" and the null date diff.
		if ( empty( $this->reward->expires_at ) ) {
			return __( 'Referral reward', 'rewardswp' );
		}

		$days_to_expire = get_date_diff_in_days( $this->reward->expires_at );

		return empty( $days_to_expire )
			? __( 'Reward expires today', 'rewardswp' )
			: sprintf(
				/* translators: %s is the number of days to expire */
				_n( 'Reward expires in %s day', 'Reward expires in %s days', $days_to_expire, 'rewardswp' ),
				$days_to_expire
			);
	}

	/**
	 * Get the instructions to be used in the front-end panel.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_panel_instructions(): string {
		return __( 'Your points have been added to your account!', 'rewardswp' );
	}

	/**
	 * Get the icon name. Used in both admin and front-end.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_icon_name(): string {
		return 'reward-points';
	}

	/**
	 * Get the reward formatted amount.
	 *
	 * @since 1.1.0
	 *
	 * @param float  $amount Optional. Amount to format. Default 0 (uses attached reward amount when available).
	 * @param string $format Optional. Format identifier. Currently unused. Default 'default'.
	 *
	 * @return string
	 */
	public function get_reward_formatted_amount( float $amount = 0, string $format = 'default' ): string {
		if ( empty( $amount ) && is_a( $this->reward, Reward::class ) ) {
			$amount = $this->reward->amount;
		}

		if ( empty( $amount ) ) {
			return '';
		}

		switch ( $format ) {
			case 'value_only':
			default:
				return \Awesomemotive\Rewardswp\Helpers\format_points_with_label( absint( $amount ) );
		}
	}

	/**
	 * Get the integrations supported by this reward type.
	 *
	 * @since 1.1.0
	 *
	 * @return array
	 */
	public function available_for(): array {
		return [
			'woocommerce',
			'easy_digital_downloads',
		];
	}

	/**
	 * Get the default values for this reward type.
	 *
	 * @since 1.1.0
	 *
	 * @return array
	 */
	public function get_defaults(): array {
		return [
			'reward_amount'        => 500,
			'minimum_order_amount' => 0,
			'reward_expiration'    => '+3 months',
		];
	}

	/**
	 * Claim reward (friend claiming reward from link).
	 * For points rewards, friends cannot claim - only advocates receive points.
	 *
	 * @since 1.1.0
	 *
	 * @param array $args Arguments for claiming.
	 *
	 * @return Reward|null
	 */
	public function claim_reward( array $args = [] ): ?Reward {
		// Points rewards are only for advocates, not friends.
		// Friends don't claim points directly from a referral link.
		return null;
	}

	/**
	 * Issue a reward.
	 * Creates a reward record when referral conditions are met.
	 *
	 * @since 1.1.0
	 *
	 * @param Referral $referral The referral object.
	 * @param string   $recipient_type Either 'advocate' or 'friend'.
	 * @param array    $args Additional arguments. Can include 'status' to set reward status (default 'issued').
	 *
	 * @return Reward|null
	 */
	public function issue_reward( Referral $referral, string $recipient_type, array $args = [] ): ?Reward {
		// Points rewards are only for advocates in the current implementation.
		if ( 'advocate' !== $recipient_type ) {
			return null;
		}

		try {
			$reward_options = get_option_value( RewardsOption::class );

			// Get status from args, default to 'issued'.
			$reward_status = isset( $args['status'] ) ? sanitize_text_field( $args['status'] ) : 'issued';

			// Only allow 'pending' or 'issued' status.
			if ( ! in_array( $reward_status, [ 'pending', 'issued' ], true ) ) {
				$reward_status = 'issued';
			}

			// Create the advocate reward record.
			$reward = create_reward(
				[
					'referral_id'    => $referral->id,
					'advocate_id'    => $referral->advocate_id,
					'reward_type'    => 'points',
					'recipient_type' => 'advocate',
					'status'         => $reward_status,
					'amount'         => $reward_options['advocate']['reward_amount'] ?? 500,
					'expires_at'     => get_reward_expiration_date( 'advocate' ),
				]
			);

			if ( ! $reward ) {
				return null;
			}

			// Only add points and send email if status is 'issued'.
			// For 'pending' status, these actions will be performed when the reward is activated.
			if ( 'issued' === $reward_status ) {
				// Create points entry for the advocate.
				$points_entry = $this->points_service->create_points_entry(
					[
						'member_id'   => $referral->advocate_id,
						'points'      => $reward->amount,
						'type'        => 'earned',
						'source_type' => 'referral',
						'source_id'   => $referral->id,
						'status'      => 'approved',
						'note_public' => __( 'Points earned from referral', 'rewardswp' ),
					],
					false, // Don't send the generic points notification, we'll send the advocate reward email instead.
					true   // Update member balance.
				);

				if ( is_wp_error( $points_entry ) ) {
					// If points creation failed, we should still have the reward record.
					// Log the error but don't fail the entire process.
					rewardswp_log_error(
						'Failed to create points entry for reward: {message}',
						[
						'message'   => $points_entry->get_error_message(),
						'reward_id' => isset( $reward ) ? $reward->id : 0,
						]
					);
				}

				// Send advocate reward notification email.
				$advocate = get_member_by( 'id', $referral->advocate_id );
				if ( $advocate ) {
					$mailer = $this->mailer_factory->get_mailer( AdvocateRewardEmail::class );

					$days_to_expire = get_date_diff_in_days( $reward->expires_at );

					$friend_name = empty( $referral->referred_user_id )
						? esc_html__( 'A friend', 'rewardswp' )
						: get_user_full_name( $referral->referred_user_id );

					// Notify the advocate with the points reward they just won.
					$mailer
						->set_recipient( $advocate->email )
						->set_data(
							[
								'reward_coupon'          => '', // No coupon for points.
								'reward_amount'          => format_points_with_label( $reward->amount ),
								'friend_name'            => $friend_name,
								'reward_expiration_time' => sprintf(
									/* translators: %s is the number of days to expire */
									_n( '%s day', '%s days', $days_to_expire, 'rewardswp' ),
									$days_to_expire
								),
							]
						)
						->send();
				}
			}

			/**
			 * Fires after a reward is issued to an advocate or friend.
			 *
			 * @since 1.3.0
			 *
			 * @param Reward   $reward         The issued reward.
			 * @param Referral $referral       The triggering referral.
			 * @param string   $recipient_type The recipient type ('advocate' or 'friend').
			 */
			do_action( 'rewardswp_reward_issued', $reward, $referral, $recipient_type );

			return $reward;

		} catch ( Exception $e ) {
			rewardswp_log_error(
				'Failed to issue points reward: {message}',
				[
					'message'    => $e->getMessage(),
					'reward_id'  => isset( $reward ) ? $reward->id : 0,
					'member_id'  => isset( $referral ) ? $referral->advocate_id : 0,
					'referral_id'=> isset( $referral ) ? $referral->id : 0,
				]
			);
			return null;
		}
	}

	/**
	 * Redeem/process a reward.
	 * For points rewards, this marks the reward as redeemed.
	 * Note: Points are already added to the member's balance when the reward is issued.
	 *
	 * @since 1.1.0
	 *
	 * @param Reward $reward The reward to redeem.
	 * @param array  $args Additional arguments.
	 *
	 * @return bool|null
	 */
	public function redeem_reward( Reward $reward, array $args = [] ): ?bool {
		// For points rewards, the points are added immediately when issued.
		// This method is called to mark the reward as redeemed if needed.
		if ( 'issued' !== $reward->status ) {
			return false; // Only process issued rewards.
		}

		// Update the reward status to redeemed.
		$reward->set_as_redeemed();

		return true;
	}

	/**
	 * Prepare reward objects for display in panel view.
	 *
	 * @since 1.1.0
	 *
	 * @param Reward[] $rewards Array of reward objects.
	 * @param array    $args Additional arguments.
	 *
	 * @return array
	 */
	public function prepare_rewards_objects_for_view( array $rewards, array $args = [] ): array {
		return array_filter(
			array_map(
				function ( $reward ) use ( $args ) {
					if ( ! is_a( $reward, Reward::class ) ) {
						return null;
					}

					// We only want the issued ones.
					if ( 'issued' !== $reward->status ) {
						return null;
					}

					$base_reward_view = $this->views_factory->get_view( BaseRewardView::class );

					$this->set_reward( $reward );

					// Points rewards don't have a coupon code like discount rewards.
					// We'll display the points amount and a simple message.
					$points_display = sprintf(
						'<div class="text-center">
							<p class="text-2xl font-bold text-primary mb-2">%s</p>
							<p class="text-gray-600">%s</p>
						</div>',
						esc_html( format_points_with_label( $reward->amount ) ),
						esc_html( $this->get_panel_instructions() )
					);

					return $base_reward_view->get_contents(
						array_merge(
							[
								'icon_name'       => $this->get_icon_name(),
								'reward_title'    => $this->get_panel_title(),
								'reward_subtitle' => $this->get_panel_subtitle(),
								'reward_content'  => $points_display,
							],
							$args
						)
					);
				},
				$rewards
			)
		);
	}

	/**
	 * Get content to display when no rewards are available.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public function get_empty_rewards_content(): string {
		return $this->views_factory->get_view( EmptyRewardsView::class )->get_contents();
	}

	/**
	 * Get the claiming panel content.
	 * For points rewards, there's no claiming panel needed.
	 *
	 * @since 1.1.0
	 *
	 * @param array $args Additional arguments.
	 *
	 * @return string
	 */
	public function get_claim_panel_content( array $args = [] ): string {
		// Points rewards don't have a claiming panel.
		return '';
	}
}
