<?php

namespace App\Services;

use App\Models\InventoryBatch;
use App\Models\Product;
use App\Models\ProductUnit;
use App\Models\Purchase;
use App\Models\Unit;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class PurchaseService
{
    public function __construct(
        private DocumentSequenceService $sequences,
        private VatCalculationService $vat,
        private FinancialPeriodService $periods,
        private InventoryService $inventory,
        private AccountingService $accounting,
        private AuditLogService $audit,
        private BranchAccessService $branches,
    ) {}

    public function createDraft(array $header, array $items, array $payments = []): Purchase
    {
        return DB::transaction(function () use ($header, $items, $payments) {
            $this->branches->assertWarehouse((int) $header['warehouse_id'], $header['branch_id'] ?? null, 'purchases');
            if (!$items) throw new RuntimeException('Add at least one product to the purchase.');
            $year = $this->periods->forDate($header['purchase_date']);
            $number = $this->sequences->getNextNumber('purchase_invoice', $header['branch_id'] ?? null, $year->id)
                ?? throw new RuntimeException('Unable to generate a purchase number.');
            $totals = ['gross' => '0.00', 'discount' => '0.00', 'taxable' => '0.00', 'vat' => '0.00', 'total' => '0.00'];
            $prepared = [];
            foreach ($items as $row) {
                $product = Product::with(['purchaseUnit', 'baseUnit'])->findOrFail($row['product_id']);
                $unitId = (int) ($row['unit_id'] ?? $product->purchase_unit_id ?? $product->base_unit_id);
                $unit = Unit::find($unitId);
                $conversion = ProductUnit::where('product_id', $product->id)->where('unit_id', $unitId)->value('conversion_factor') ?? '1';
                $qty = $this->vat->number($row['quantity'], 4);
                $free = $this->vat->number($row['free_quantity'] ?? 0, 4);
                if (bccomp($qty, '0', 4) !== 1) throw new RuntimeException('Purchase quantities must be greater than zero.');
                $calculated = $this->vat->line($qty, $row['unit_cost'], $row['vat_rate'] ?? $product->vat_rate, 'Exclusive VAT', $row['discount_type'] ?? 'fixed', $row['discount_value'] ?? 0);
                foreach ($totals as $key => $value) $totals[$key] = $this->vat->add($value, $calculated[$key], 2);
                $prepared[] = compact('product', 'unitId', 'unit', 'conversion', 'qty', 'free', 'calculated') + ['row' => $row];
            }
            $paymentType = strtolower($header['payment_type'] ?? 'credit');
            if (!$payments && $paymentType !== 'credit') $payments = [['payment_method' => $paymentType, 'amount' => $totals['total']]];
            $paid = '0.00';
            foreach ($payments as $payment) $paid = $this->vat->add($paid, $payment['amount'], 2);
            if (bccomp($paid, $totals['total'], 2) === 1) throw new RuntimeException('Payment total cannot exceed purchase total.');

            $purchase = Purchase::create([
                'purchase_no' => $number, 'supplier_invoice_no' => $header['supplier_invoice_no'] ?? null,
                'supplier_id' => $header['supplier_id'], 'warehouse_id' => $header['warehouse_id'],
                'branch_id' => $header['branch_id'] ?? null, 'financial_year_id' => $year->id,
                'purchase_date' => $header['purchase_date'], 'payment_type' => $paymentType,
                'reference' => $header['reference'] ?? null, 'po_reference' => $header['po_reference'] ?? null,
                'gross_total' => $totals['gross'], 'discount_total' => $totals['discount'],
                'taxable_total' => $totals['taxable'], 'vat_total' => $totals['vat'],
                'grand_total' => $totals['total'], 'paid_total' => $paid,
                'balance_total' => $this->vat->subtract($totals['total'], $paid),
                'status' => 'draft', 'notes' => $header['notes'] ?? null, 'created_by' => Auth::id(),
            ]);
            foreach ($prepared as $data) {
                $totalQty = $this->vat->add($data['qty'], $data['free'], 4);
                $purchase->items()->create([
                    'product_id' => $data['product']->id, 'unit_id' => $data['unitId'],
                    'product_name_en_snapshot' => $data['product']->name_en,
                    'product_name_ar_snapshot' => $data['product']->name_ar,
                    'unit_snapshot' => $data['unit']?->code, 'conversion_factor' => $data['conversion'],
                    'quantity' => $data['qty'], 'free_quantity' => $data['free'],
                    'base_quantity' => $this->vat->multiply($totalQty, $data['conversion'], 4),
                    'unit_cost' => $data['row']['unit_cost'], 'gross_amount' => $data['calculated']['gross'],
                    'discount_amount' => $data['calculated']['discount'], 'taxable_amount' => $data['calculated']['taxable'],
                    'vat_rate_snapshot' => $data['row']['vat_rate'] ?? $data['product']->vat_rate,
                    'vat_amount' => $data['calculated']['vat'], 'line_total' => $data['calculated']['total'],
                    'batch_number' => $data['row']['batch_number'] ?? null, 'expiry_date' => $data['row']['expiry_date'] ?? null,
                ]);
            }
            foreach ($payments as $payment) if (bccomp((string) $payment['amount'], '0', 2) === 1) {
                $purchase->payments()->create(['payment_method' => strtolower($payment['payment_method']), 'amount' => $payment['amount'], 'reference' => $payment['reference'] ?? null]);
            }
            $this->audit->log('PURCHASE_CREATED', 'purchases', $purchase->id, null, ['purchase_no' => $purchase->purchase_no]);
            return $purchase->load(['items', 'payments']);
        });
    }

    public function finalize(Purchase $purchase): Purchase
    {
        return DB::transaction(function () use ($purchase) {
            $purchase = Purchase::with(['items.product', 'payments'])->lockForUpdate()->findOrFail($purchase->id);
            if ($purchase->status !== 'draft') throw new RuntimeException('Purchase has already been finalized.');
            $this->periods->forDate($purchase->purchase_date);
            foreach ($purchase->items as $item) {
                $baseCost = bccomp((string) $item->base_quantity, '0', 4) === 1
                    ? $this->vat->divide($item->taxable_amount, $item->base_quantity, 4) : '0.0000';
                $ledger = $this->inventory->stockIn([
                    'transaction_date' => $purchase->purchase_date, 'transaction_type' => 'PURCHASE',
                    'reference_type' => 'purchases', 'reference_id' => $purchase->id,
                    'reference_number' => $purchase->purchase_no, 'product_id' => $item->product_id,
                    'warehouse_id' => $purchase->warehouse_id, 'unit_id' => $item->unit_id,
                    'qty_in' => $this->vat->add($item->quantity, $item->free_quantity, 4),
                    'base_qty_in' => $item->base_quantity, 'unit_cost' => $baseCost,
                    'branch_id' => $purchase->branch_id, 'financial_year_id' => $purchase->financial_year_id,
                ]);
                $item->product->update(['last_purchase_cost' => $baseCost]);
                if ($item->batch_number) {
                    $batch = InventoryBatch::where('product_id', $item->product_id)
                        ->where('warehouse_id', $purchase->warehouse_id)
                        ->where('batch_number', $item->batch_number)->lockForUpdate()->first();
                    if ($batch) {
                        $batch->update([
                            'expiry_date' => $item->expiry_date,
                            'quantity' => bcadd((string) $batch->quantity, (string) $item->base_quantity, 4),
                            'cost' => $baseCost,
                            'status' => 'active',
                        ]);
                    } else {
                        $batch = InventoryBatch::create([
                            'product_id' => $item->product_id, 'warehouse_id' => $purchase->warehouse_id,
                            'batch_number' => $item->batch_number, 'expiry_date' => $item->expiry_date,
                            'quantity' => $item->base_quantity, 'cost' => $baseCost, 'status' => 'active',
                        ]);
                    }
                    $ledger->update(['batch_id' => $batch->id]);
                }
            }
            $purchase->update(['status' => 'finalized', 'finalized_by' => Auth::id(), 'finalized_at' => now()]);
            $purchase->load('items', 'payments');
            $this->accounting->postPurchase($purchase);
            $this->audit->log('PURCHASE_FINALIZED', 'purchases', $purchase->id, ['status' => 'draft'], ['status' => 'finalized']);
            return $purchase;
        });
    }
}
