<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Admin\Concerns\CalculatesGstDocumentTotals;
use App\Helpers\CompanySettings;
use App\Helpers\DebitNoteDocumentSettings;
use App\Helpers\GstHelper;
use App\Helpers\ItemCatalogHelper;
use App\Helpers\UnitHelper;
use App\Models\Company;
use App\Models\DebitNote;
use App\Models\DebitNoteItem;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Item;
use App\Models\PurchaseBill;
use App\Models\TaxRate;
use App\Models\Vendor;
use App\Services\DebitNoteDocumentPresenter;
use App\Services\DebitNotePdfGenerator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\StreamedResponse;

class DebitNoteController extends AdminController
{
    use CalculatesGstDocumentTotals;

    public function __construct(
        private readonly DebitNoteDocumentPresenter $documentPresenter,
    ) {}

    public function index(Request $request)
    {
        $companyId = $this->companyId();
        $filters = $request->only([
            'q', 'client_name', 'debit_note_number', 'status', 'city',
            'issued_from', 'issued_to', 'amount_min', 'amount_max', 'item_name',
        ]);

        $query = DebitNote::with(['customer', 'vendor', 'invoice', 'purchaseBill'])
            ->forCompany($companyId)
            ->when($filters['client_name'] ?? null, function ($q, $v) {
                $q->where(function ($sub) use ($v) {
                    $sub->whereHas('customer', fn ($c) => $c->where('name', 'like', "%{$v}%"))
                        ->orWhereHas('vendor', fn ($c) => $c->where('name', 'like', "%{$v}%"));
                });
            })
            ->when($filters['debit_note_number'] ?? null, fn ($q, $v) => $q->where('debit_note_number', 'like', "%{$v}%"))
            ->when($filters['status'] ?? null, fn ($q, $v) => $v !== 'all' ? $q->where('status', $v) : $q)
            ->when($filters['city'] ?? null, function ($q, $v) {
                $q->where(function ($sub) use ($v) {
                    $sub->whereHas('customer', fn ($c) => $c->where('city', 'like', "%{$v}%"))
                        ->orWhereHas('vendor', fn ($c) => $c->where('city', 'like', "%{$v}%"));
                });
            })
            ->when($filters['issued_from'] ?? null, fn ($q, $v) => $q->whereDate('debit_note_date', '>=', $v))
            ->when($filters['issued_to'] ?? null, fn ($q, $v) => $q->whereDate('debit_note_date', '<=', $v))
            ->when($filters['amount_min'] ?? null, fn ($q, $v) => $q->where('grand_total', '>=', $v))
            ->when($filters['amount_max'] ?? null, fn ($q, $v) => $q->where('grand_total', '<=', $v))
            ->when($filters['item_name'] ?? null, fn ($q, $v) => $q->whereHas('items', fn ($i) => $i->where('item_name', 'like', "%{$v}%")))
            ->when($filters['q'] ?? null, function ($q, $v) {
                $q->where(function ($sub) use ($v) {
                    $sub->where('debit_note_number', 'like', "%{$v}%")
                        ->orWhereHas('customer', fn ($c) => $c->where('name', 'like', "%{$v}%"))
                        ->orWhereHas('vendor', fn ($c) => $c->where('name', 'like', "%{$v}%"));
                });
            });

        $debitNotes = $query->latest('debit_note_date')->latest('id')->get();
        $hasFilters = collect($filters)->filter(fn ($v) => $v && $v !== 'all')->isNotEmpty();

        $noteId = $request->query('debit_note');
        $selected = $noteId ? $debitNotes->firstWhere('id', (int) $noteId) : null;

        if ($noteId && ! $selected) {
            $selected = DebitNote::with(['customer', 'vendor', 'invoice', 'purchaseBill'])->forCompany($companyId)->find($noteId);
            if ($selected) {
                $debitNotes = $debitNotes->push($selected)
                    ->sortByDesc(fn ($n) => $n->debit_note_date->format('Y-m-d').'-'.$n->id)
                    ->values();
            }
        }

        $panelData = $selected ? $this->documentPresenter->displayData($selected) : [];

        return view('admin.debit-notes.index', array_merge([
            'debitNotes' => $debitNotes,
            'filters' => $filters,
            'hasFilters' => $hasFilters,
            'statuses' => config('debit_note.statuses'),
            'features' => config('debit_note.features'),
            'selected' => $selected,
            'panelOpen' => (bool) ($noteId && $selected),
        ], $panelData));
    }

    public function create(Request $request)
    {
        $data = $this->formData(null, $request);

        if ($data['customers']->isEmpty() && $data['vendors']->isEmpty()) {
            return view('admin.debit-notes.create', array_merge($data, ['emptyParties' => true]));
        }

        return view('admin.debit-notes.create', $data);
    }

    public function store(Request $request)
    {
        $data = $this->validateDebitNote($request);
        $items = $this->validateLineItems($request);
        $calc = $this->calculateTotals($data, $items);
        $processed = $calc['items'];
        unset($calc['items']);

        $status = $request->input('save_action') === 'draft' ? 'draft' : 'unpaid';
        $calc['amount_due'] = $status === 'unpaid' ? $calc['grand_total'] : 0;
        $calc['paid_amount'] = 0;

        $debitNote = null;
        DB::transaction(function () use ($data, $processed, $calc, $status, &$debitNote) {
            $debitNote = DebitNote::create(array_merge($data, $calc, ['status' => $status]));
            $this->syncLineItems($debitNote, $processed);
        });

        if (in_array($request->input('save_action'), ['save', 'preview'], true)) {
            return redirect()->route('admin.debit-notes.index', ['debit_note' => $debitNote->id])
                ->with('success', 'Debit note created successfully.');
        }

        return redirect()->route('admin.debit-notes.index')
            ->with('success', 'Debit note saved as draft.');
    }

    public function edit(DebitNote $debit_note)
    {
        $this->authorizeNote($debit_note);
        $debit_note->load(['items', 'invoice', 'purchaseBill']);

        return view('admin.debit-notes.edit', $this->formData($debit_note, request()));
    }

    public function update(Request $request, DebitNote $debit_note)
    {
        $this->authorizeNote($debit_note);

        $data = $this->validateDebitNote($request, $debit_note);
        $items = $this->validateLineItems($request);
        $calc = $this->calculateTotals($data, $items);
        $processed = $calc['items'];
        unset($calc['items']);

        $status = $request->input('save_action') === 'draft'
            ? 'draft'
            : ($debit_note->status === 'draft' ? 'unpaid' : $debit_note->status);

        if (in_array($status, ['void', 'paid'], true)) {
            $calc['amount_due'] = 0;
            $calc['paid_amount'] = $status === 'paid' ? $calc['grand_total'] : $debit_note->paid_amount;
        } elseif ($status === 'unpaid') {
            $calc['amount_due'] = $calc['grand_total'];
            $calc['paid_amount'] = 0;
        } else {
            $calc['amount_due'] = $debit_note->amount_due;
            $calc['paid_amount'] = $debit_note->paid_amount;
        }

        DB::transaction(function () use ($debit_note, $data, $processed, $calc, $status) {
            $debit_note->update(array_merge($data, $calc, ['status' => $status]));
            $debit_note->items()->delete();
            $this->syncLineItems($debit_note, $processed);
        });

        if (in_array($request->input('save_action'), ['save', 'preview'], true)) {
            return redirect()->route('admin.debit-notes.index', ['debit_note' => $debit_note->id])
                ->with('success', 'Debit note updated successfully.');
        }

        return redirect()->route('admin.debit-notes.index')
            ->with('success', 'Debit note saved as draft.');
    }

    public function show(DebitNote $debit_note)
    {
        return redirect()->route('admin.debit-notes.index', ['debit_note' => $debit_note->id]);
    }

    public function print(DebitNote $debit_note, Request $request)
    {
        $this->authorizeNote($debit_note);
        $copyKey = $request->query('copy', 'original');

        return view('admin.debit-notes.print-pdf', [
            'debitNote' => $debit_note,
            'copyKey' => $copyKey,
            'autoprint' => $request->boolean('autoprint', true),
            'backUrl' => route('admin.debit-notes.index', ['debit_note' => $debit_note->id]),
            'pdfUrl' => route('admin.debit-notes.pdf', [
                'debit_note' => $debit_note,
                'copy' => $copyKey,
                'inline' => 1,
            ]),
        ]);
    }

    public function downloadPdf(DebitNote $debit_note, Request $request, DebitNotePdfGenerator $pdfGenerator)
    {
        $this->authorizeNote($debit_note);
        $data = $this->documentPresenter->displayData($debit_note);
        $copyKey = $request->query('copy', 'original');
        $copies = $data['documentCopies'];
        $copyLabel = $copies[$copyKey] ?? $copies['original'] ?? 'Original Copy';

        if ($request->boolean('inline')) {
            return $pdfGenerator->stream($debit_note, $data, $copyLabel);
        }

        return $pdfGenerator->download($debit_note, $data, $copyLabel)
            ->withHeaders([
                'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
                'Pragma' => 'no-cache',
            ]);
    }

    public function shareLink(DebitNote $debit_note)
    {
        $this->authorizeNote($debit_note);

        if ($debit_note->status === 'draft') {
            return response()->json([
                'message' => 'Issue this debit note before sharing a public link.',
            ], 422);
        }

        return response()->json([
            'url' => $debit_note->publicShareUrl(),
            'visibility' => 'PUBLIC',
        ]);
    }

    public function invoicesJson(Request $request): JsonResponse
    {
        $request->validate(['customer_id' => ['required', 'exists:customers,id']]);

        $invoices = Invoice::forCompany($this->companyId())
            ->where('customer_id', $request->customer_id)
            ->whereNotIn('status', ['cancelled', 'draft'])
            ->latest('invoice_date')
            ->latest('id')
            ->limit(100)
            ->get(['id', 'invoice_number', 'invoice_date', 'grand_total', 'place_of_supply', 'tax_on']);

        return response()->json($invoices->map(fn ($inv) => [
            'id' => $inv->id,
            'invoice_number' => $inv->invoice_number,
            'invoice_date' => $inv->invoice_date?->format('Y-m-d'),
            'grand_total' => (float) $inv->grand_total,
            'place_of_supply' => $inv->place_of_supply,
            'tax_on' => $inv->tax_on,
        ]));
    }

    public function purchaseBillsJson(Request $request): JsonResponse
    {
        $request->validate(['vendor_id' => ['required', 'exists:vendors,id']]);

        $bills = PurchaseBill::where('company_id', $this->companyId())
            ->where('vendor_id', $request->vendor_id)
            ->whereNotIn('status', ['cancelled', 'draft'])
            ->latest('bill_date')
            ->latest('id')
            ->limit(100)
            ->get(['id', 'bill_number', 'bill_date', 'grand_total']);

        return response()->json($bills->map(fn ($bill) => [
            'id' => $bill->id,
            'bill_number' => $bill->bill_number,
            'bill_date' => $bill->bill_date?->format('Y-m-d'),
            'grand_total' => (float) $bill->grand_total,
        ]));
    }

    public function export(Request $request): StreamedResponse
    {
        $companyId = $this->companyId();
        $filters = $request->only([
            'q', 'client_name', 'debit_note_number', 'status', 'city',
            'issued_from', 'issued_to', 'amount_min', 'amount_max', 'item_name',
        ]);

        $notes = DebitNote::with('customer')
            ->forCompany($companyId)
            ->when($filters['client_name'] ?? null, fn ($q, $v) => $q->whereHas('customer', fn ($c) => $c->where('name', 'like', "%{$v}%")))
            ->when($filters['debit_note_number'] ?? null, fn ($q, $v) => $q->where('debit_note_number', 'like', "%{$v}%"))
            ->when($filters['status'] ?? null, fn ($q, $v) => $v !== 'all' ? $q->where('status', $v) : $q)
            ->when($filters['issued_from'] ?? null, fn ($q, $v) => $q->whereDate('debit_note_date', '>=', $v))
            ->when($filters['issued_to'] ?? null, fn ($q, $v) => $q->whereDate('debit_note_date', '<=', $v))
            ->when($filters['q'] ?? null, function ($q, $v) {
                $q->where(function ($sub) use ($v) {
                    $sub->where('debit_note_number', 'like', "%{$v}%")
                        ->orWhereHas('customer', fn ($c) => $c->where('name', 'like', "%{$v}%"));
                });
            })
            ->latest('debit_note_date')
            ->latest('id')
            ->get();

        $filename = 'debit-notes-'.date('Y-m-d').'.csv';

        return response()->streamDownload(function () use ($notes) {
            $out = fopen('php://output', 'w');
            fputcsv($out, ['Date', 'Doc No', 'Status', 'Client/Vendor', 'Amount', 'Amount Due']);
            foreach ($notes as $note) {
                fputcsv($out, [
                    $note->debit_note_date->format('Y-m-d'),
                    $note->debit_note_number,
                    $note->statusMeta()['label'],
                    $note->partyName(),
                    $note->grand_total,
                    $note->amount_due,
                ]);
            }
            fclose($out);
        }, $filename, ['Content-Type' => 'text/csv']);
    }

    public function destroy(DebitNote $debit_note)
    {
        $this->authorizeNote($debit_note);
        $debit_note->delete();

        return redirect()->route('admin.debit-notes.index')->with('success', 'Debit note deleted successfully.');
    }

    private function authorizeNote(DebitNote $debitNote): void
    {
        if ($debitNote->company_id !== $this->companyId()) {
            abort(403);
        }
    }

    private function formData(?DebitNote $debitNote, Request $request): array
    {
        $companyId = $this->companyId();
        $count = DebitNote::where('company_id', $companyId)->count();
        $defaultSeq = $count + ($debitNote ? 0 : 1);
        $defaultPrefix = config('debit_note.prefixes.default', 'DN');

        $lineItems = [['item_name' => '', 'description' => '', 'unit' => 'NOS', 'quantity' => 1, 'rate' => 0, 'discount_percent' => 0, 'tax_rate' => 0, 'hsn_sac' => '']];

        if ($request->query('copy_from_invoice')) {
            $invoice = Invoice::with('items')->forCompany($companyId)->find($request->query('copy_from_invoice'));
            if ($invoice) {
                $lineItems = $invoice->items->map(fn ($li) => [
                    'item_id' => $li->item_id,
                    'item_name' => $li->item_name,
                    'description' => $li->description,
                    'unit' => $li->unit,
                    'quantity' => (float) $li->quantity,
                    'rate' => (float) $li->rate,
                    'discount_percent' => (float) $li->discount_percent,
                    'tax_rate' => (float) $li->tax_rate,
                    'hsn_sac' => $li->hsn_sac,
                ])->values()->all();
                if (! $debitNote) {
                    $debitNote = new DebitNote([
                        'entity_type' => 'client',
                        'customer_id' => $invoice->customer_id,
                        'invoice_id' => $invoice->id,
                        'related_document_number' => $invoice->invoice_number,
                        'related_document_date' => $invoice->invoice_date,
                        'place_of_supply' => $invoice->place_of_supply,
                        'tax_on' => $invoice->tax_on,
                    ]);
                }
            }
        }

        $catalogItems = Item::with('taxRate')
            ->where('company_id', $companyId)->where('is_active', true)
            ->orderBy('name')->get(['id', 'name', 'description', 'unit', 'sale_rate', 'hsn_sac', 'tax_rate_id']);

        $taxRates = TaxRate::where('company_id', $companyId)->active()->selectable()->orderBy('rate')->get();
        $company = Company::find($companyId);
        $customers = Customer::where('company_id', $companyId)->where('is_active', true)->orderBy('name')->get();
        $vendors = Vendor::where('company_id', $companyId)->where('is_active', true)->orderBy('name')->get();

        return [
            'debitNote' => $debitNote,
            'entityType' => old('entity_type', $debitNote?->entity_type ?? 'client'),
            'company' => $company,
            'gstEnabled' => GstHelper::usesRegularGst($company),
            'indianStates' => config('company.indian_states', []),
            'reasons' => config('debit_note.reasons'),
            'clientsJson' => $customers->map(fn ($c) => $c->toPickerArray())->values(),
            'clientPickerOptions' => [
                'gstTreatments' => config('customer.gst_treatments'),
                'currencies' => config('customer.currencies'),
                'countries' => config('customer.countries'),
                'indianStates' => config('customer.indian_states'),
            ],
            'typeLabel' => 'Debit Note',
            'docLabels' => [
                'title' => DebitNoteDocumentSettings::title($companyId),
                'number' => 'Debit Note no',
                'date' => 'Date',
            ],
            'customers' => $customers,
            'vendors' => $vendors,
            'vendorsJson' => $vendors->map(fn ($v) => $v->toPickerArray())->values(),
            'purchaseBillsJsonUrl' => route('admin.debit-notes.purchase-bills-json'),
            'catalogItems' => $catalogItems,
            'taxRates' => $taxRates,
            'shippingModes' => config('invoice.shipping_modes'),
            'discountAllTypes' => config('invoice.discount_all_types'),
            'defaultPrefix' => $debitNote?->debit_note_prefix ?? $defaultPrefix,
            'defaultSeq' => $debitNote?->debit_note_seq ?? $defaultSeq,
            'invoicesJsonUrl' => route('admin.debit-notes.invoices-json'),
            'itemsJson' => ItemCatalogHelper::jsonForForm($catalogItems, 'sale_rate'),
            'unitsCatalog' => UnitHelper::allForCompany($companyId),
            'unitsJson' => UnitHelper::jsonForCompany($companyId),
            'taxRatesJson' => $taxRates->map(fn ($t) => [
                'id' => $t->id,
                'name' => $t->name,
                'rate' => (float) $t->rate,
            ])->values(),
            'lineItems' => $debitNote && $debitNote->exists
                ? $debitNote->items->map(fn ($li) => [
                    'item_id' => $li->item_id,
                    'item_name' => $li->item_name,
                    'description' => $li->description,
                    'unit' => $li->unit,
                    'quantity' => (float) $li->quantity,
                    'rate' => (float) $li->rate,
                    'discount_percent' => (float) $li->discount_percent,
                    'tax_rate' => (float) $li->tax_rate,
                    'hsn_sac' => $li->hsn_sac,
                ])->values()->all()
                : $lineItems,
        ];
    }

    private function validateDebitNote(Request $request, ?DebitNote $debitNote = null): array
    {
        $reasonKeys = implode(',', array_keys(config('debit_note.reasons', [])));
        $entityType = $request->input('entity_type', 'client');

        $rules = [
            'entity_type' => ['required', 'in:client,vendor'],
            'debit_note_prefix' => ['required', 'string', 'max:20'],
            'debit_note_seq' => ['required', 'integer', 'min:1'],
            'debit_note_date' => ['required', 'date'],
            'related_document_date' => ['nullable', 'date'],
            'reason' => ['required', 'string', 'in:'.$reasonKeys],
            'terms' => ['nullable', 'string'],
            'private_notes' => ['nullable', 'string'],
            'notes' => ['nullable', 'string'],
            'shipping_charges' => ['nullable', 'numeric', 'min:0'],
            'shipping_mode' => ['nullable', 'string', 'max:30'],
            'discount_on_total' => ['nullable', 'numeric', 'min:0'],
            'discount_all_type' => ['nullable', 'in:percent,fixed'],
            'discount_all_value' => ['nullable', 'numeric', 'min:0'],
            'custom_amount' => ['nullable', 'numeric', 'min:0'],
            'custom_amount_label' => ['nullable', 'string', 'max:100'],
            'advance_payment' => ['nullable', 'numeric', 'min:0'],
            'place_of_supply' => ['nullable', 'string', 'max:100'],
            'tax_on' => ['nullable', 'in:bill_to,ship_to'],
            'total_quantity_unit' => ['nullable', 'string', 'max:20'],
            'total_quantity' => ['nullable', 'numeric', 'min:0'],
        ];

        if ($entityType === 'vendor') {
            $rules['vendor_id'] = ['required', 'exists:vendors,id'];
            $rules['purchase_bill_id'] = ['nullable', 'exists:purchase_bills,id'];
        } else {
            $rules['customer_id'] = ['required', 'exists:customers,id'];
            $rules['invoice_id'] = ['nullable', 'exists:invoices,id'];
        }

        $data = $request->validate($rules);
        $data['entity_type'] = $entityType;

        $data['company_id'] = $this->companyId();
        $data['debit_note_number'] = $data['debit_note_prefix'].'-'.str_pad((string) $data['debit_note_seq'], 4, '0', STR_PAD_LEFT);
        $data['has_shipping'] = $request->boolean('has_shipping');
        $data['has_discount_total'] = $request->boolean('has_discount_total');
        $data['has_discount_all'] = $request->boolean('has_discount_all');
        $data['has_custom_amount'] = $request->boolean('has_custom_amount');
        $data['has_advance_payment'] = $request->boolean('has_advance_payment');
        $data['has_total_quantity'] = $request->boolean('has_total_quantity');
        $data['show_cess'] = $request->boolean('show_cess');

        if ($entityType === 'client') {
            $data['vendor_id'] = null;
            $data['purchase_bill_id'] = null;
            if ($data['invoice_id'] ?? null) {
                $invoice = Invoice::forCompany($this->companyId())->find($data['invoice_id']);
                if ($invoice) {
                    $data['related_document_number'] = $invoice->invoice_number;
                    $data['related_document_date'] = $invoice->invoice_date;
                }
            } else {
                $data['invoice_id'] = null;
                $data['related_document_number'] = $request->input('related_document_number');
            }
        } else {
            $data['customer_id'] = null;
            $data['invoice_id'] = null;
            if ($data['purchase_bill_id'] ?? null) {
                $bill = PurchaseBill::where('company_id', $this->companyId())->find($data['purchase_bill_id']);
                if ($bill) {
                    $data['related_document_number'] = $bill->bill_number;
                    $data['related_document_date'] = $bill->bill_date;
                }
            } else {
                $data['purchase_bill_id'] = null;
                $data['related_document_number'] = $request->input('related_document_number');
            }
        }

        if (! $data['has_shipping']) {
            $data['shipping_charges'] = 0;
            $data['shipping_mode'] = null;
        }
        if (! $data['has_discount_total']) {
            $data['discount_on_total'] = 0;
        }
        if (! $data['has_discount_all']) {
            $data['discount_all_value'] = 0;
            $data['discount_all_percent'] = 0;
        } else {
            $data['discount_all_type'] = $data['discount_all_type'] ?? 'percent';
            $data['discount_all_percent'] = ($data['discount_all_type'] === 'percent')
                ? (float) ($data['discount_all_value'] ?? 0)
                : 0;
        }
        if (! $data['has_custom_amount']) {
            $data['custom_amount'] = 0;
        }
        if (! $data['has_advance_payment']) {
            $data['advance_payment'] = 0;
        }
        if (! $data['has_total_quantity']) {
            $data['total_quantity'] = null;
            $data['total_quantity_unit'] = null;
        }

        $data['debit_note_seq'] = (int) $data['debit_note_seq'];

        $exists = DebitNote::where('company_id', $this->companyId())
            ->where('debit_note_number', $data['debit_note_number'])
            ->when($debitNote, fn ($q) => $q->where('id', '!=', $debitNote->id))
            ->exists();

        if ($exists) {
            throw \Illuminate\Validation\ValidationException::withMessages([
                'debit_note_seq' => ['This document number already exists.'],
            ]);
        }

        return $data;
    }

    private function validateLineItems(Request $request): array
    {
        $request->validate([
            'line_items' => ['required', 'array', 'min:1'],
            'line_items.*.item_name' => ['required', 'string', 'max:255'],
            'line_items.*.quantity' => ['required', 'numeric', 'min:0.001'],
            'line_items.*.rate' => ['required', 'numeric', 'min:0'],
            'line_items.*.discount_percent' => ['nullable', 'numeric', 'min:0', 'max:100'],
            'line_items.*.tax_rate' => ['nullable', 'numeric', 'min:0', 'max:100'],
            'line_items.*.hsn_sac' => ['nullable', 'string', 'max:20'],
        ]);

        return collect($request->input('line_items', []))
            ->filter(fn ($row) => ! empty($row['item_name']))
            ->values()
            ->all();
    }

    private function calculateTotals(array $data, array $items): array
    {
        $data['company_id'] = $data['company_id'] ?? $this->companyId();
        $lineResult = $this->processGstLineItems($data, $items);
        $processedItems = $lineResult['processed'];
        $subtotal = $lineResult['subtotal'];
        $gst = $lineResult['gst'];

        $discountAll = 0;
        if ($data['has_discount_all']) {
            $discVal = (float) ($data['discount_all_value'] ?? 0);
            $discountAll = ($data['discount_all_type'] ?? 'percent') === 'fixed'
                ? $discVal
                : $subtotal * ($discVal / 100);
        }

        $totalTax = $gst['total_tax'];
        $grand = $subtotal
            + $totalTax
            + (float) ($data['shipping_charges'] ?? 0)
            + (float) ($data['custom_amount'] ?? 0)
            - (float) ($data['discount_on_total'] ?? 0)
            - $discountAll
            - (float) ($data['advance_payment'] ?? 0);

        $grand = max(0, round($grand, 2));
        $roundOff = 0;

        if (CompanySettings::bool('round_off_amount', $data['company_id'])) {
            $rounded = round($grand);
            $roundOff = round($rounded - $grand, 2);
            $grand = $rounded;
        }

        return [
            'subtotal' => round($subtotal, 2),
            'taxable_amount' => round($subtotal, 2),
            'discount_amount' => round($discountAll + (float) ($data['discount_on_total'] ?? 0), 2),
            'total_tax' => $totalTax,
            'cgst_amount' => $gst['cgst_amount'],
            'sgst_amount' => $gst['sgst_amount'],
            'igst_amount' => $gst['igst_amount'],
            'place_of_supply' => $lineResult['place_of_supply'],
            'is_igst' => $lineResult['is_igst'],
            'tax_on' => $lineResult['tax_on'],
            'round_off' => $roundOff,
            'grand_total' => $grand,
            'items' => $processedItems,
        ];
    }

    private function syncLineItems(DebitNote $debitNote, array $items): void
    {
        foreach ($items as $row) {
            DebitNoteItem::create([
                'debit_note_id' => $debitNote->id,
                'item_id' => $row['item_id'] ?? null,
                'item_name' => $row['item_name'],
                'description' => $row['description'] ?? null,
                'hsn_sac' => $row['hsn_sac'] ?? null,
                'unit' => $row['unit'] ?? 'NOS',
                'quantity' => (float) $row['quantity'],
                'rate' => (float) $row['rate'],
                'discount_percent' => (float) ($row['discount_percent'] ?? 0),
                'discount' => round((float) ($row['discount'] ?? 0), 2),
                'tax_rate' => (float) ($row['tax_rate'] ?? 0),
                'cgst_rate' => (float) ($row['cgst_rate'] ?? 0),
                'sgst_rate' => (float) ($row['sgst_rate'] ?? 0),
                'igst_rate' => (float) ($row['igst_rate'] ?? 0),
                'tax_amount' => round((float) ($row['tax_amount'] ?? 0), 2),
                'cgst_amount' => round((float) ($row['cgst_amount'] ?? 0), 2),
                'sgst_amount' => round((float) ($row['sgst_amount'] ?? 0), 2),
                'igst_amount' => round((float) ($row['igst_amount'] ?? 0), 2),
                'amount' => round((float) ($row['amount'] ?? 0), 2),
            ]);
        }
    }
}
