<?php

declare(strict_types=1);

namespace Australcode\Rewards\Service;

use Australcode\Rewards\Domain\Redemption\RedemptionQuote;
use Australcode\Rewards\Domain\Settings\SettingsRepository;
use Australcode\Rewards\Domain\Wallet\CustomerBlock;
use Australcode\Rewards\Domain\Wallet\TransactionType;

defined( 'ABSPATH' ) || exit;

/**
 * Redemption rules + ledger operations.
 *
 * Conversion: redemption_rate points buy 1 currency unit of discount
 * (amount = points / rate). A quote clamps the requested points to the balance,
 * the cart subtotal and the max_redemption_percent cap. The actual debit/refund
 * goes through WalletService so the ledger stays the single source of truth.
 */
final class RedemptionService {

	public function __construct(
		private readonly WalletService $wallet,
		private readonly SettingsRepository $settings,
		private readonly CustomerBlock $blocks,
	) {
	}

	public function points_to_amount( int $points ): float {
		$rate = $this->settings->get()->redemption_rate;
		if ( $rate < 1 || $points <= 0 ) {
			return 0.0;
		}
		// Floor to 2 decimals so the discount never exceeds the points' value.
		return floor( $points / $rate * 100 ) / 100;
	}

	public function max_redeemable_points( int $user_id, float $cart_subtotal ): int {
		$settings = $this->settings->get();
		$balance  = $this->wallet->get_balance( $user_id )->points_balance;
		if ( $balance <= 0 || $cart_subtotal <= 0 ) {
			return 0;
		}
		$max_discount = $cart_subtotal * ( (float) $settings->max_redemption_percent / 100.0 );
		$max_by_cart  = (int) floor( $max_discount * $settings->redemption_rate );
		return max( 0, min( $balance, $max_by_cart ) );
	}

	public function quote( int $user_id, int $points, float $cart_subtotal ): RedemptionQuote {
		$settings = $this->settings->get();
		if ( ! $settings->redemption_enabled ) {
			return RedemptionQuote::invalid( 'redemption_disabled' );
		}
		// A blocked customer cannot redeem (B6). Checked before any clamping so the
		// reason is unambiguous to the caller (REST / cart).
		if ( $this->blocks->is_blocked( $user_id ) ) {
			return RedemptionQuote::invalid( 'user_blocked' );
		}
		if ( $points < $settings->min_points_to_redeem ) {
			return RedemptionQuote::invalid( 'below_minimum' );
		}

		$points = min( $points, $this->max_redeemable_points( $user_id, $cart_subtotal ) );
		if ( $points <= 0 ) {
			return RedemptionQuote::invalid( 'nothing_to_redeem' );
		}

		$amount = $this->points_to_amount( $points );
		if ( $amount <= 0.0 ) {
			return RedemptionQuote::invalid( 'zero_amount' );
		}

		return RedemptionQuote::valid( $points, $amount );
	}

	/**
	 * Debits points for a redemption (negative ledger entry of type Redeem).
	 */
	public function redeem( int $user_id, int $points, ?int $order_id, string $reason = '' ): void {
		if ( $points <= 0 ) {
			return;
		}
		$this->wallet->credit(
			$user_id,
			-$points,
			TransactionType::Redeem,
			/* translators: %d: order number. */
			'' !== $reason ? $reason : sprintf( 'Redeemed on order #%d', (int) $order_id ),
			$order_id
		);
	}

	/**
	 * Returns previously redeemed points to the customer (e.g. on order
	 * cancel/refund). Recorded as a positive Adjust entry.
	 */
	public function refund_redemption( int $user_id, int $points, ?int $order_id ): void {
		if ( $points <= 0 ) {
			return;
		}
		$this->wallet->credit(
			$user_id,
			$points,
			TransactionType::Adjust,
			/* translators: %d: order number. */
			sprintf( 'Redemption refunded from order #%d', (int) $order_id ),
			$order_id
		);
	}
}
