<?php

namespace App\Services;

use App\Models\Customers\Customer;
use App\Models\Customers\CustomerCreditBalance;
use App\Models\Customers\CustomerCreditLedger;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use RuntimeException;

/**
 * The only path that mutates customer_credit_balances.
 *
 * `credit()`  increases the customer's outstanding balance (they owe us / we issue them credit).
 * `debit()`   decreases it (we draw down their credit pool to settle an invoice).
 * Throws if a debit would push the balance below zero.
 */
class CustomerCreditService
{
    public function credit(Customer $customer, float $amount, ?Model $reference = null, ?string $note = null): CustomerCreditLedger
    {
        if ($amount <= 0) {
            throw new RuntimeException('Credit amount must be positive.');
        }

        return DB::transaction(function () use ($customer, $amount, $reference, $note) {
            $row = CustomerCreditBalance::query()
                ->where('customer_id', $customer->id)
                ->lockForUpdate()
                ->first();

            if (! $row) {
                $row = CustomerCreditBalance::create([
                    'customer_id' => $customer->id,
                    'balance' => 0,
                ]);
            }

            $newBalance = (float) $row->balance + $amount;
            $row->update(['balance' => $newBalance]);

            return CustomerCreditLedger::create([
                'customer_id' => $customer->id,
                'type' => 'credit_in',
                'amount' => $amount,
                'reference_type' => $reference ? $reference::class : null,
                'reference_id' => $reference?->getKey(),
                'balance_after' => $newBalance,
                'note' => $note,
                'created_by' => Auth::id(),
                'created_at' => now(),
            ]);
        });
    }

    public function debit(Customer $customer, float $amount, ?Model $reference = null, ?string $note = null): CustomerCreditLedger
    {
        if ($amount <= 0) {
            throw new RuntimeException('Debit amount must be positive.');
        }

        return DB::transaction(function () use ($customer, $amount, $reference, $note) {
            $row = CustomerCreditBalance::query()
                ->where('customer_id', $customer->id)
                ->lockForUpdate()
                ->first();

            $current = $row ? (float) $row->balance : 0;
            if ($amount > $current + 0.001) {
                throw new RuntimeException("Insufficient credit (have {$current}, requested {$amount}).");
            }

            $newBalance = $current - $amount;
            if ($row) {
                $row->update(['balance' => $newBalance]);
            }

            return CustomerCreditLedger::create([
                'customer_id' => $customer->id,
                'type' => 'credit_out',
                'amount' => $amount,
                'reference_type' => $reference ? $reference::class : null,
                'reference_id' => $reference?->getKey(),
                'balance_after' => $newBalance,
                'note' => $note,
                'created_by' => Auth::id(),
                'created_at' => now(),
            ]);
        });
    }

    public function balance(Customer $customer): float
    {
        return (float) (CustomerCreditBalance::where('customer_id', $customer->id)->value('balance') ?? 0);
    }
}
