<?php

namespace App\Http\Controllers\Concerns;

use App\Models\Customers\Customer;
use App\Models\Lookups\PaymentMethod;
use App\Models\Returns\CreditNote;
use App\Services\CustomerCreditService;
use Illuminate\Database\Eloquent\Collection;

/**
 * Shared credit-note redemption used by every checkout (POS, Sales Order,
 * Invoice) so they behave identically.
 *
 * **A credit note is a way of PAYING, not a discount.** Applying one settles
 * part of what is owed; it does not reduce what the sale was worth. This used
 * to fold the redeemed amount into the invoice's `discount`, which shrank
 * net_total and made a settled receivable look like money the shop had given
 * away — wrong on the invoice, wrong in every revenue report downstream, and
 * invisible as a tender. Credit is now booked through PaymentService as a
 * `credit` payment split, exactly like cash or a card.
 *
 * Rules:
 *   - A note is a store of value drawn down over time: it can be spent across
 *     several invoices until used up, and keeps its own remaining balance
 *     ({@see CreditNote::remainingAmount()}). There is no "claim it in full or
 *     not at all", and no reissuing the unused part as a fresh note.
 *   - Customer credit is the single pool of available credit; issued notes feed
 *     it. Redemption is capped by what the sale needs, what the selected notes
 *     still hold, AND what the pool holds — so a customer can never over-draw
 *     and an invoice can never be paid past its balance.
 *   - The caller may ask for a specific amount (pay Rs. 2,000 of a Rs. 5,000
 *     note); it is clamped to that same cap.
 */
trait AppliesCheckoutCredit
{
    /**
     * Work out how much credit can be applied, and how it splits across the
     * selected notes.
     *
     * @param  array<int,int>  $creditNoteIds
     * @param  float|null  $requestedAmount  an explicit amount to apply (e.g. a user-typed
     *                                       part-payment); null applies the maximum allowed
     * @return array{
     *     notes: Collection<int,CreditNote>,
     *     available: float,
     *     spendable: float,
     *     applied: float,
     *     splits: array<int,array{method: string, amount: float, credit_note_id: int}>
     * }
     */
    protected function resolveCheckoutCredit(
        ?int $customerId,
        array $creditNoteIds,
        float $amountOwedBeforeCredit,
        CustomerCreditService $credit,
        ?float $requestedAmount = null,
    ): array {
        $empty = [
            'notes' => new Collection,
            'available' => 0.0,
            'spendable' => 0.0,
            'applied' => 0.0,
            'splits' => [],
        ];

        if (! $customerId || empty($creditNoteIds)) {
            return $empty;
        }

        // Ordered by id so the allocation below is deterministic: the same
        // selection always draws the same notes down in the same order.
        $notes = CreditNote::query()
            ->whereIn('id', $creditNoteIds)
            ->where('customer_id', $customerId)
            ->spendable()
            ->orderBy('id')
            ->get();

        if ($notes->isEmpty()) {
            return $empty;
        }

        // What the chosen notes still hold between them.
        $spendable = round($notes->sum(fn (CreditNote $note): float => $note->remainingAmount()), 2);

        $available = $credit->balance(Customer::findOrFail($customerId));

        $cap = round(min($spendable, max(0, $amountOwedBeforeCredit), $available), 2);

        $applied = $requestedAmount === null
            ? $cap
            : round(max(0, min($requestedAmount, $cap)), 2);

        return [
            'notes' => $notes,
            'available' => $available,
            'spendable' => $spendable,
            'applied' => $applied,
            'splits' => $this->allocateCreditSplits($notes, $applied),
        ];
    }

    /**
     * Spread the amount being applied across the notes, filling each up to its
     * remaining balance before moving to the next.
     *
     * One split per note (rather than one lump sum) is what lets PaymentService
     * draw each note down by exactly what it contributed, so
     * `applied_amount` on every note stays a true record of where it went.
     *
     * @param  Collection<int,CreditNote>  $notes
     * @return array<int,array{method: string, amount: float, credit_note_id: int}>
     */
    protected function allocateCreditSplits(Collection $notes, float $applied): array
    {
        $splits = [];
        $left = round($applied, 2);

        foreach ($notes as $note) {
            if ($left <= 0.001) {
                break;
            }

            $take = round(min($note->remainingAmount(), $left), 2);

            if ($take <= 0.001) {
                continue;
            }

            $splits[] = [
                'method' => PaymentMethod::CREDIT,
                'amount' => $take,
                'credit_note_id' => (int) $note->getKey(),
            ];

            $left = round($left - $take, 2);
        }

        return $splits;
    }
}
