<?php
namespace App\Http\Controllers;
use App\Models as M;
use App\Services\GhanaTaxService;
use App\Services\SequenceService;
use App\Services\StockService;
use App\Support\CrudRegistry;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class DeliveryNoteController extends Controller {
    use Concerns\FxDoc;
    public function index(Request $r) { return app(CrudController::class)->index($r, 'delivery-notes'); }
    public function show(int $id) { return app(CrudController::class)->show('delivery-notes', $id); }
    public function create(Request $r) {
        $this->need('sales.create');
        $d = new M\DeliveryNote(['dn_date' => date('Y-m-d')]);
        $order = $r->filled('from_order') ? M\SalesOrder::with('lines.product')->find($r->from_order) : null;
        if ($order) { $d->sales_order_id = $order->id; $d->customer_id = $order->customer_id; $d->quarry_id = $order->quarry_id; $this->fxFill($d, $order); }
        $data = $this->formData($d);
        $data['order'] = $order;
        return view('docs.delivery.form', $data);
    }
    public function store(Request $r) {
        $this->need('sales.create');
        $d = $this->validated($r);
        $dn = DB::transaction(function () use ($d) {
            $dn = M\DeliveryNote::create($d['head'] + ['dn_no' => SequenceService::next('delivery_note'),
                'gate_pass_no' => SequenceService::next('gate_pass'), 'status' => 'dispatched', 'dispatched_by' => Auth::user()->name]);
            $this->saveLines($dn, $d['lines']);
            if ($dn->weighbridge_ticket_id) M\WeighbridgeTicket::where('id', $dn->weighbridge_ticket_id)->update(['delivery_note_id' => $dn->id]);
            if ($dn->sales_order_id) SalesOrderController::refreshDispatchStatus(M\SalesOrder::find($dn->sales_order_id));
            return $dn;
        });
        return redirect()->route('delivery-notes.show', $dn)->with('success', 'Dispatched '.$dn->dn_no.' — gate pass '.$dn->gate_pass_no.'.');
    }
    public function destroy(int $id) {
        $this->need('sales.delete');
        $dn = M\DeliveryNote::with('lines')->findOrFail($id);
        abort_unless($dn->status === 'draft', 403, 'Dispatched notes cannot be deleted. Raise a credit note instead.');
        $dn->delete();
        return redirect()->route('delivery-notes.index')->with('success', 'Delivery note deleted.');
    }
    public function invoice(int $id) {
        $this->need('sales.create');
        $dn = M\DeliveryNote::with('lines')->findOrFail($id);
        abort_unless($dn->status === 'dispatched', 403, 'Only dispatched notes can be invoiced.');
        return redirect()->route('sales-invoices.create', ['from_delivery' => $dn->id]);
    }
    public function print(Request $r, int $id) {
        $this->need('sales.view');
        $doc = M\DeliveryNote::with('customer', 'lines.product', 'ticket', 'quarry')->findOrFail($id);
        if ($r->get('pdf')) return \App\Services\PdfService::download('docs.delivery.print', ['d' => $doc], 'DN-'.($doc->dn_no ?? $id).'.pdf');
        return view('docs.delivery.print', ['d' => $doc]);
    }
    protected function validated(Request $r): array {
        $head = $r->validate(['customer_id' => 'required|exists:customers,id', 'dn_date' => 'required|date', 'sales_order_id' => 'nullable|exists:sales_orders,id',
            'transporter_id' => 'nullable|exists:transporters,id', 'vehicle_no' => 'nullable|max:30', 'driver_name' => 'nullable|max:100',
            'quarry_id' => 'nullable|exists:quarries,id', 'weighbridge_ticket_id' => 'nullable|exists:weighbridge_tickets,id',
            'lines' => 'required|array|min:1', 'lines.*.product_id' => 'required|exists:products,id', 'lines.*.qty_tonnes' => 'required|numeric|min:0.001',
            'lines.*.rate' => 'required|numeric|min:0', 'lines.*.stockpile_id' => 'nullable|exists:stockpiles,id'] + $this->fxRules());
        $lines = $head['lines']; unset($head['lines']);
        return ['head' => $head, 'lines' => $lines];
    }
    protected function saveLines(M\DeliveryNote $dn, array $lines): void {
        $sub = 0;
        foreach ($lines as $l) {
            $amt = round($l['qty_tonnes'] * $l['rate'], 2);
            $pileId = $l['stockpile_id'] ?? null;
            if (!$pileId && $dn->quarry_id) $pileId = StockService::stockpileFor($dn->quarry_id, $l['product_id'])->id;
            $dn->lines()->create(['product_id' => $l['product_id'], 'stockpile_id' => $pileId, 'qty_tonnes' => $l['qty_tonnes'], 'rate' => $l['rate'], 'amount' => $amt]);
            if ($pileId) StockService::moveStockpile($pileId, $dn->dn_date->format('Y-m-d'), 'dispatch-out', 0, $l['qty_tonnes'], 'delivery_note', $dn->id, 'Dispatch '.$dn->dn_no);
            if ($dn->sales_order_id) {
                $ol = M\SalesOrderLine::where('order_id', $dn->sales_order_id)->where('product_id', $l['product_id'])->first();
                if ($ol) $ol->increment('qty_dispatched', $l['qty_tonnes']);
            }
            $sub += $amt;
        }
        $tax = GhanaTaxService::salesTax($sub);
        $dn->update(['subtotal' => round($sub, 2), 'tax_amount' => $tax['total_tax'], 'grand_total' => $tax['grand']]);
    }
    protected function formData(M\DeliveryNote $d): array {
        return ['fx' => \App\Services\CurrencyService::active(), 'record' => $d, 'isNew' => !$d->exists, 'cfg' => CrudRegistry::get('delivery-notes'), 'key' => 'delivery-notes',
            'docNo' => SequenceService::peek('delivery_note'),
            'customers' => M\Customer::where('is_active', true)->orderBy('name')->get(), 'quarries' => M\Quarry::orderBy('name')->get(),
            'products' => M\Product::where('is_active', true)->where('is_sellable', true)->orderBy('name')->get(),
            'transporters' => M\Transporter::orderBy('name')->get(), 'piles' => M\Stockpile::with('product')->orderBy('code')->get(),
            'orders' => M\SalesOrder::whereIn('status', ['confirmed', 'partially-dispatched'])->orderByDesc('id')->limit(200)->get(),
            'tickets' => M\WeighbridgeTicket::where('status', 'closed')->whereNull('delivery_note_id')->orderByDesc('id')->limit(200)->get()];
    }
}
