<?php

namespace App\Services;

use App\Models\Supplier;
use App\Models\SupplierInvoice;
use App\Models\SupplierInvoiceItem;
use App\Models\SupplierPayment;
use App\Models\SupplierCreditNote;
use App\Models\SupplierLedger;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class SupplierService
{
    // =============================================
    // SUPPLIERS
    // =============================================

    public function getAll(array $filters = [])
    {
        $query = Supplier::query();

        if (!empty($filters['tenant_id'])) {
            $query->where('tenant_id', $filters['tenant_id']);
        }
        if (isset($filters['is_active'])) {
            $query->where('is_active', $filters['is_active']);
        }
        if (!empty($filters['search'])) {
            $query->where(function ($q) use ($filters) {
                $q->where('name', 'like', "%{$filters['search']}%")
                  ->orWhere('company_name', 'like', "%{$filters['search']}%")
                  ->orWhere('contact_person', 'like', "%{$filters['search']}%")
                  ->orWhere('phone', 'like', "%{$filters['search']}%");
            });
        }

        return $query->withCount(['invoices', 'payments'])->get();
    }

    public function find(int $id): ?Supplier
    {
        return Supplier::with(['invoices' => function ($q) {
            $q->latest()->limit(10);
        }, 'payments' => function ($q) {
            $q->latest()->limit(10);
        }])->find($id);
    }

    public function create(array $data): Supplier
    {
        return DB::transaction(function () use ($data) {
            $supplier = Supplier::create($data);

            // Record opening balance in ledger if provided
            if (!empty($data['opening_balance']) && $data['opening_balance'] > 0) {
                $this->addLedgerEntry(
                    $supplier->id,
                    $data['branch_id'] ?? null,
                    SupplierLedger::TYPE_OPENING_BALANCE,
                    null,
                    null,
                    'Opening balance',
                    $data['opening_balance'],
                    0,
                    now()->toDateString()
                );

                $supplier->update(['current_credit_balance' => $data['opening_balance']]);
            }

            return $supplier;
        });
    }

    public function update(int $id, array $data): ?Supplier
    {
        $supplier = Supplier::find($id);
        if (!$supplier) return null;

        $supplier->update($data);
        return $supplier->fresh();
    }

    public function delete(int $id): bool
    {
        $supplier = Supplier::find($id);
        if (!$supplier) return false;

        // Prevent delete if there's an outstanding balance
        if ($supplier->current_credit_balance > 0) {
            throw new \Exception('Cannot delete supplier with outstanding credit balance of ' . $supplier->current_credit_balance);
        }

        return (bool) $supplier->delete();
    }

    // =============================================
    // INVOICES
    // =============================================

    public function getInvoices(int $supplierId, array $filters = [])
    {
        $query = SupplierInvoice::with(['items.ingredient', 'items.product', 'branch', 'user'])
            ->where('supplier_id', $supplierId);

        if (!empty($filters['branch_id'])) {
            $query->where('branch_id', $filters['branch_id']);
        }
        if (!empty($filters['status'])) {
            $query->where('status', $filters['status']);
        }
        if (!empty($filters['payment_type'])) {
            $query->where('payment_type', $filters['payment_type']);
        }
        if (!empty($filters['start_date'])) {
            $query->where('invoice_date', '>=', $filters['start_date']);
        }
        if (!empty($filters['end_date'])) {
            $query->where('invoice_date', '<=', $filters['end_date']);
        }

        return $query->latest('invoice_date')->get();
    }

    public function getAllInvoices(array $filters = [])
    {
        $query = SupplierInvoice::with(['supplier', 'branch', 'user'])
            ->when(!empty($filters['branch_id']), fn($q) => $q->where('branch_id', $filters['branch_id']))
            ->when(!empty($filters['status']), fn($q) => $q->where('status', $filters['status']))
            ->when(!empty($filters['start_date']), fn($q) => $q->where('invoice_date', '>=', $filters['start_date']))
            ->when(!empty($filters['end_date']), fn($q) => $q->where('invoice_date', '<=', $filters['end_date']));

        return $query->latest('invoice_date')->get();
    }

    public function createInvoice(array $data): SupplierInvoice
    {
        return DB::transaction(function () use ($data) {
            $supplier = Supplier::findOrFail($data['supplier_id']);

            // Auto-generate invoice number if not provided
            if (empty($data['invoice_number'])) {
                $data['invoice_number'] = SupplierInvoice::generateInvoiceNumber();
            }

            // Calculate due date from supplier's payment terms
            if (empty($data['due_date'])) {
                $data['due_date'] = now()->addDays($supplier->payment_terms_days)->toDateString();
            }

            // Calculate totals
            $subtotal        = collect($data['items'])->sum(fn($i) => $i['quantity'] * $i['unit_price']);
            $discountAmount  = $data['discount_amount'] ?? 0;
            $taxAmount       = $data['tax_amount'] ?? 0;
            $totalAmount     = $subtotal - $discountAmount + $taxAmount;

            $data['subtotal']          = $subtotal;
            $data['total_amount']      = $totalAmount;
            $data['remaining_amount']  = $totalAmount;
            $data['paid_amount']       = 0;
            $data['status']            = SupplierInvoice::STATUS_UNPAID;

            // Cash purchases are immediately paid
            if (($data['payment_type'] ?? 'credit') === SupplierInvoice::TYPE_CASH) {
                $data['paid_amount']      = $totalAmount;
                $data['remaining_amount'] = 0;
                $data['status']           = SupplierInvoice::STATUS_PAID;
            }

            $invoice = SupplierInvoice::create($data);

            // Create line items
            foreach ($data['items'] as $item) {
                $item['supplier_invoice_id'] = $invoice->id;
                $item['total_price']         = $item['quantity'] * $item['unit_price'];
                SupplierInvoiceItem::create($item);
            }

            // Only credit purchases affect the credit balance
            if ($invoice->payment_type === SupplierInvoice::TYPE_CREDIT) {
                $this->incrementSupplierBalance($supplier, $totalAmount);
                $this->addLedgerEntry(
                    $supplier->id,
                    $invoice->branch_id,
                    SupplierLedger::TYPE_INVOICE,
                    SupplierInvoice::class,
                    $invoice->id,
                    "Invoice #{$invoice->invoice_number}",
                    $totalAmount,
                    0,
                    $invoice->invoice_date->toDateString()
                );
            }

            return $invoice->load(['items.ingredient', 'items.product', 'supplier']);
        });
    }

    public function findInvoice(int $id): ?SupplierInvoice
    {
        return SupplierInvoice::with(['items.ingredient', 'items.product', 'supplier', 'branch', 'user'])->find($id);
    }

    public function markOverdueInvoices(): int
    {
        return SupplierInvoice::overdue()->update(['status' => SupplierInvoice::STATUS_OVERDUE]);
    }

    // =============================================
    // PAYMENTS
    // =============================================

    public function recordPayment(array $data): SupplierPayment
    {
        return DB::transaction(function () use ($data) {
            $supplier = Supplier::findOrFail($data['supplier_id']);
            $amount   = (float) $data['amount'];

            $payment = SupplierPayment::create($data);

            // Reduce supplier's credit balance
            $this->decrementSupplierBalance($supplier, $amount);

            // Add ledger entry
            $this->addLedgerEntry(
                $supplier->id,
                $data['branch_id'],
                SupplierLedger::TYPE_PAYMENT,
                SupplierPayment::class,
                $payment->id,
                "Payment - {$data['payment_method']}" . (!empty($data['reference_number']) ? " (#{$data['reference_number']})" : ''),
                0,
                $amount,
                $data['payment_date']
            );

            // Apply payment to oldest unpaid invoices (FIFO)
            $this->applyPaymentToInvoices($supplier->id, $data['branch_id'] ?? null, $amount);

            return $payment->load(['supplier', 'branch', 'user']);
        });
    }

    public function getPayments(int $supplierId, array $filters = [])
    {
        return SupplierPayment::with(['branch', 'user'])
            ->where('supplier_id', $supplierId)
            ->when(!empty($filters['branch_id']), fn($q) => $q->where('branch_id', $filters['branch_id']))
            ->when(!empty($filters['start_date']), fn($q) => $q->where('payment_date', '>=', $filters['start_date']))
            ->when(!empty($filters['end_date']), fn($q) => $q->where('payment_date', '<=', $filters['end_date']))
            ->latest('payment_date')
            ->get();
    }

    // =============================================
    // CREDIT NOTES
    // =============================================

    public function createCreditNote(array $data): SupplierCreditNote
    {
        return DB::transaction(function () use ($data) {
            $supplier = Supplier::findOrFail($data['supplier_id']);
            $amount   = (float) $data['amount'];

            if (empty($data['credit_note_number'])) {
                $data['credit_note_number'] = SupplierCreditNote::generateCreditNoteNumber();
            }

            $creditNote = SupplierCreditNote::create($data);

            // Credit notes reduce what we owe the supplier
            $this->decrementSupplierBalance($supplier, $amount);

            // If linked to an invoice, apply to that invoice
            if (!empty($data['supplier_invoice_id'])) {
                $invoice = SupplierInvoice::find($data['supplier_invoice_id']);
                $invoice?->applyCreditNote($amount);
            }

            $this->addLedgerEntry(
                $supplier->id,
                $data['branch_id'],
                SupplierLedger::TYPE_CREDIT_NOTE,
                SupplierCreditNote::class,
                $creditNote->id,
                "Credit Note #{$creditNote->credit_note_number} - {$creditNote->reason}",
                0,
                $amount,
                $data['date']
            );

            return $creditNote->load(['supplier', 'branch', 'user', 'invoice']);
        });
    }

    // =============================================
    // LEDGER / STATEMENT
    // =============================================

    public function getStatement(int $supplierId, array $filters = [])
    {
        $query = SupplierLedger::where('supplier_id', $supplierId)
            ->orderBy('transaction_date')
            ->orderBy('id');

        if (!empty($filters['branch_id'])) {
            $query->where('branch_id', $filters['branch_id']);
        }
        if (!empty($filters['start_date'])) {
            $query->where('transaction_date', '>=', $filters['start_date']);
        }
        if (!empty($filters['end_date'])) {
            $query->where('transaction_date', '<=', $filters['end_date']);
        }

        $entries     = $query->get();
        $supplier    = Supplier::find($supplierId);
        $totalDebit  = $entries->sum('debit');
        $totalCredit = $entries->sum('credit');

        return [
            'supplier'      => $supplier,
            'entries'       => $entries,
            'total_debit'   => $totalDebit,
            'total_credit'  => $totalCredit,
            'closing_balance' => $supplier?->current_credit_balance ?? 0,
        ];
    }

    // =============================================
    // DASHBOARD SUMMARY
    // =============================================

    public function getDashboardSummary(?int $branchId = null): array
    {
        $invoiceQuery = SupplierInvoice::query()
            ->when($branchId, fn($q) => $q->where('branch_id', $branchId));

        // Mark overdue first
        $this->markOverdueInvoices();

        return [
            'total_suppliers'         => Supplier::active()->count(),
            'total_credit_outstanding'=> Supplier::sum('current_credit_balance'),
            'overdue_amount'          => (clone $invoiceQuery)->where('status', 'overdue')->sum('remaining_amount'),
            'overdue_suppliers_count' => Supplier::whereHas('invoices', fn($q) => $q->where('status', 'overdue'))->count(),
            'invoices_this_month'     => (clone $invoiceQuery)->whereMonth('invoice_date', now()->month)->sum('total_amount'),
            'payments_this_month'     => SupplierPayment::query()
                ->when($branchId, fn($q) => $q->where('branch_id', $branchId))
                ->whereMonth('payment_date', now()->month)
                ->sum('amount'),
            'suppliers_over_limit'    => Supplier::overLimit()->count(),
            'aging' => [
                'current'    => (clone $invoiceQuery)->whereIn('status', ['unpaid', 'partial'])->where('due_date', '>=', now())->sum('remaining_amount'),
                'days_30'    => (clone $invoiceQuery)->where('status', 'overdue')->where('due_date', '>=', now()->subDays(30))->where('due_date', '<', now())->sum('remaining_amount'),
                'days_60'    => (clone $invoiceQuery)->where('status', 'overdue')->where('due_date', '>=', now()->subDays(60))->where('due_date', '<', now()->subDays(30))->sum('remaining_amount'),
                'days_90plus'=> (clone $invoiceQuery)->where('status', 'overdue')->where('due_date', '<', now()->subDays(60))->sum('remaining_amount'),
            ],
        ];
    }

    // =============================================
    // PRIVATE HELPERS
    // =============================================

    private function incrementSupplierBalance(Supplier $supplier, float $amount): void
    {
        $supplier->increment('current_credit_balance', $amount);
    }

    private function decrementSupplierBalance(Supplier $supplier, float $amount): void
    {
        $newBalance = max(0, (float) $supplier->current_credit_balance - $amount);
        $supplier->update(['current_credit_balance' => $newBalance]);
    }

    private function addLedgerEntry(
        int $supplierId,
        ?int $branchId,
        string $type,
        ?string $referenceType,
        ?int $referenceId,
        string $description,
        float $debit,
        float $credit,
        string $date
    ): SupplierLedger {
        // Calculate running balance
        $lastEntry = SupplierLedger::where('supplier_id', $supplierId)
            ->orderByDesc('transaction_date')
            ->orderByDesc('id')
            ->first();

        $previousBalance = $lastEntry ? (float) $lastEntry->balance : 0;
        $newBalance      = $previousBalance + $debit - $credit;

        return SupplierLedger::create([
            'supplier_id'      => $supplierId,
            'branch_id'        => $branchId,
            'transaction_type' => $type,
            'reference_type'   => $referenceType,
            'reference_id'     => $referenceId,
            'description'      => $description,
            'debit'            => $debit,
            'credit'           => $credit,
            'balance'          => max(0, $newBalance),
            'transaction_date' => $date,
        ]);
    }

    private function applyPaymentToInvoices(int $supplierId, ?int $branchId, float $paymentAmount): void
    {
        $unpaidInvoices = SupplierInvoice::where('supplier_id', $supplierId)
            ->whereIn('status', [SupplierInvoice::STATUS_UNPAID, SupplierInvoice::STATUS_PARTIAL, SupplierInvoice::STATUS_OVERDUE])
            ->when($branchId, fn($q) => $q->where('branch_id', $branchId))
            ->orderBy('due_date') // FIFO - oldest due date first
            ->get();

        $remaining = $paymentAmount;

        foreach ($unpaidInvoices as $invoice) {
            if ($remaining <= 0) break;

            $apply    = min($remaining, (float) $invoice->remaining_amount);
            $invoice->applyPayment($apply);
            $remaining -= $apply;
        }
    }
}
