<?php

namespace App\Services;

use App\Models\Lookups\LedgerEntryType;
use App\Models\Lookups\Status;
use App\Models\Purchasing\Supplier;
use App\Models\Purchasing\SupplierLedger;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use RuntimeException;

/**
 * The only path that mutates suppliers.outstanding_balance.
 *
 * `debit()`   increases what we owe the supplier (a GRN was received on credit).
 * `credit()`  decreases it (a payment was made to the supplier).
 */
class SupplierService
{
    public function debit(Supplier $supplier, float $amount, ?Model $reference = null, ?string $note = null): SupplierLedger
    {
        if ($amount <= 0) {
            throw new RuntimeException('Debit amount must be positive.');
        }

        return DB::transaction(function () use ($supplier, $amount, $reference, $note) {
            $row = Supplier::query()->whereKey($supplier->id)->lockForUpdate()->first();
            $newBalance = (float) $row->outstanding_balance + $amount;
            $row->update(['outstanding_balance' => $newBalance]);

            return SupplierLedger::create([
                'supplier_id' => $supplier->id,
                'ledger_entry_type_id' => LedgerEntryType::idOf(LedgerEntryType::DEBIT),
                'amount' => $amount,
                'reference_type' => $reference ? $reference::class : null,
                'reference_id' => $reference?->getKey(),
                'reference_number' => $reference?->getAttribute('grn_number') ?? $reference?->getAttribute('po_number') ?? $reference?->getAttribute('return_grn_number'),
                'balance_after' => $newBalance,
                'note' => $note,
                'created_by' => Auth::id(),
                'created_at' => now(),
            ]);
        });
    }

    public function credit(Supplier $supplier, float $amount, ?Model $reference = null, ?string $note = null): SupplierLedger
    {
        if ($amount <= 0) {
            throw new RuntimeException('Credit amount must be positive.');
        }

        return DB::transaction(function () use ($supplier, $amount, $reference, $note) {
            $row = Supplier::query()->whereKey($supplier->id)->lockForUpdate()->first();
            $newBalance = max(0.0, (float) $row->outstanding_balance - $amount);
            $row->update(['outstanding_balance' => $newBalance]);

            return SupplierLedger::create([
                'supplier_id' => $supplier->id,
                'ledger_entry_type_id' => LedgerEntryType::idOf(LedgerEntryType::CREDIT),
                '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 isOverLimit(Supplier $supplier, float $additionalAmount): bool
    {
        if ((float) $supplier->credit_limit <= 0) {
            return false;
        }

        return (float) $supplier->outstanding_balance + $additionalAmount > (float) $supplier->credit_limit;
    }

    public function blacklist(Supplier $supplier, string $reason): void
    {
        $supplier->update([
            'is_blacklisted' => true,
            'blacklisted_reason' => $reason,
            'blacklisted_at' => now(),
            'blacklisted_by' => Auth::id(),
            'status_id' => Status::idOf(Status::CTX_SUPPLIER, Status::SUPPLIER_BLACKLISTED),
        ]);

        AuditLog::record('supplier.blacklisted', $supplier, ['reason' => $reason]);
    }

    public function unblacklist(Supplier $supplier): void
    {
        $supplier->update([
            'is_blacklisted' => false,
            'blacklisted_reason' => null,
            'blacklisted_at' => null,
            'blacklisted_by' => null,
            'status_id' => Status::idOf(Status::CTX_SUPPLIER, Status::SUPPLIER_ACTIVE),
        ]);

        AuditLog::record('supplier.unblacklisted', $supplier);
    }
}
