<?php

namespace App\Services;

use App\Models\Lookups\DiscountType;
use App\Models\Promotions\CartDiscount;
use Illuminate\Support\Collection;

/**
 * The cart-value promotional engine ("Cart Discounts" / spend-and-save).
 *
 * An order-level rule: when the checkout's cart value clears a rule's
 * threshold, that rule's discount is applied to the whole cart — not to
 * individual lines. This service is the single evaluation point: every
 * consumer — the POS payload, the checkout credit estimate, the held-sale
 * summary, and the authoritative evaluation in InvoiceService::create() —
 * resolves "which rule is live right now and what does it take off" through
 * these methods, so the UI can never show a discount the engine would not
 * also apply.
 */
class CartDiscountService
{
    /**
     * All rules currently live: switched on AND inside their validity
     * window. NULL starts_at = no lower bound, NULL ends_at = no upper bound
     * (the same convention as ProductBundle / Deal).
     *
     * @return Collection<int, CartDiscount>
     */
    public function activeRules(): Collection
    {
        return CartDiscount::query()
            ->with('discountType')
            ->where('is_active', true)
            ->where(fn ($q) => $q->whereNull('starts_at')->orWhere('starts_at', '<=', now()))
            ->where(fn ($q) => $q->whereNull('ends_at')->orWhere('ends_at', '>=', now()))
            ->orderByDesc('min_cart_value')
            ->orderBy('id')
            ->get();
    }

    /**
     * The rule that applies to a cart of the given value, if any.
     *
     * Spending-and-saving semantics: when several rules are live the highest
     * threshold the cart clears wins (a Rs. 12,000 cart should earn the
     * "Spend 10,000" reward, not the "Spend 5,000" one), with ties broken by
     * lowest id — deterministic, documented, never a coin flip.
     */
    public function bestRule(float $cartValue): ?CartDiscount
    {
        foreach ($this->activeRules() as $rule) {
            if ($cartValue >= (float) $rule->min_cart_value) {
                return $rule;
            }
        }

        return null;
    }

    /**
     * Amount an active rule takes off a cart of the given value.
     *
     * A percentage is taken against the cart value; a fixed discount is a
     * flat cart-level amount (NOT per-unit — unlike Deals there is no
     * quantity to multiply by). The result is clamped to the cart value and
     * to the rule's optional max_discount_amount ceiling, rounded to 2 dp.
     */
    public function computeDiscount(CartDiscount $rule, float $cartValue): float
    {
        if ($cartValue <= 0 || (float) $rule->discount_value <= 0) {
            return 0.0;
        }

        $discount = $rule->discountType?->code === DiscountType::PERCENTAGE
            ? $cartValue * ((float) $rule->discount_value / 100)
            : (float) $rule->discount_value;

        $discount = min(max($discount, 0), $cartValue);

        if ($rule->max_discount_amount !== null && (float) $rule->max_discount_amount > 0) {
            $discount = min($discount, (float) $rule->max_discount_amount);
        }

        return round($discount, 2);
    }
}
