<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Admin\Concerns\CalculatesGstDocumentTotals;
use App\Helpers\GstHelper;
use App\Helpers\ItemCatalogHelper;
use App\Services\InvoiceDocumentPresenter;
use App\Services\InvoicePdfGenerator;
use App\Helpers\UnitHelper;
use App\Models\Company;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Item;
use App\Models\TaxRate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\StreamedResponse;

class InvoiceController extends AdminController
{
    use CalculatesGstDocumentTotals;

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

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

        $query = Invoice::with('customer')
            ->forCompany($companyId)
            ->when($filters['client_name'] ?? null, fn ($q, $v) => $q->whereHas('customer', fn ($c) => $c->where('name', 'like', "%{$v}%")))
            ->when($filters['invoice_number'] ?? null, fn ($q, $v) => $q->where('invoice_number', 'like', "%{$v}%"))
            ->when($filters['status'] ?? null, fn ($q, $v) => $v !== 'all' ? $q->where('status', $v) : $q)
            ->when($filters['type'] ?? null, fn ($q, $v) => $v !== 'all' ? $q->where('type', $v) : $q)
            ->when($filters['city'] ?? null, fn ($q, $v) => $v !== 'all' ? $q->whereHas('customer', fn ($c) => $c->where('city', $v)) : $q)
            ->when($filters['issued_from'] ?? null, fn ($q, $v) => $q->whereDate('invoice_date', '>=', $v))
            ->when($filters['issued_to'] ?? null, fn ($q, $v) => $q->whereDate('invoice_date', '<=', $v))
            ->when($filters['due_from'] ?? null, fn ($q, $v) => $q->whereDate('due_date', '>=', $v))
            ->when($filters['due_to'] ?? null, fn ($q, $v) => $q->whereDate('due_date', '<=', $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('invoice_number', 'like', "%{$v}%")
                        ->orWhereHas('customer', fn ($c) => $c->where('name', 'like', "%{$v}%"));
                });
            });

        $invoices = $query->latest('invoice_date')->latest('id')->get();

        if (($filters['status'] ?? '') === 'overdue') {
            $invoices = $invoices->filter(fn ($inv) => $inv->display_status === 'overdue')->values();
        }

        $cities = Customer::where('company_id', $companyId)
            ->whereNotNull('city')->where('city', '!=', '')
            ->distinct()->orderBy('city')->pluck('city');

        $hasFilters = collect($filters)->filter(fn ($v) => $v && $v !== 'all')->isNotEmpty();

        $invoiceId = $request->query('invoice');
        $selected = $invoiceId ? $invoices->firstWhere('id', (int) $invoiceId) : null;

        if ($invoiceId && ! $selected) {
            $selected = Invoice::with('customer')->forCompany($companyId)->find($invoiceId);
            if ($selected) {
                $invoices = $invoices->push($selected)
                    ->sortByDesc(fn ($inv) => $inv->invoice_date->format('Y-m-d').'-'.$inv->id)
                    ->values();
            }
        }

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

        return view('admin.invoices.index', array_merge([
            'invoices' => $invoices,
            'filters' => $filters,
            'cities' => $cities,
            'hasFilters' => $hasFilters,
            'invoiceTypes' => collect(config('invoice.types'))
                ->only(config('invoice.primary_types'))
                ->all(),
            'createTypes' => config('invoice.create_types'),
            'statuses' => config('invoice.statuses'),
            'features' => config('invoice.features'),
            'selected' => $selected,
            'panelOpen' => (bool) ($invoiceId && $selected),
        ], $panelData));
    }

    public function create(Request $request)
    {
        $creatable = config('invoice.primary_types', ['invoice', 'retail_invoice', 'tax_invoice', 'excise_invoice']);
        $type = $request->query('type', 'invoice');
        if (! in_array($type, $creatable, true)) {
            $type = 'invoice';
        }

        return view('admin.invoices.create', $this->formData(null, $type));
    }

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

        $invoice = null;
        DB::transaction(function () use ($data, $processed, $calc, $request, &$invoice) {
            $invoice = Invoice::create(array_merge($data, $calc, [
                'status' => $request->input('save_action') === 'draft' ? 'draft' : 'sent',
            ]));
            $this->syncLineItems($invoice, $processed);
        });

        if (in_array($request->input('save_action'), ['save', 'preview'], true)) {
            return redirect()->route('admin.invoices.index', ['invoice' => $invoice->id])
                ->with('success', config('invoice.types.'.$data['type']).' created successfully.');
        }

        return redirect()->route('admin.invoices.index')
            ->with('success', config('invoice.types.'.$data['type']).' saved as draft.');
    }

    public function edit(Invoice $invoice)
    {
        $this->authorizeInvoice($invoice);
        $invoice->load('items');

        return view('admin.invoices.edit', $this->formData($invoice, $invoice->type));
    }

    public function update(Request $request, Invoice $invoice)
    {
        $this->authorizeInvoice($invoice);

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

        DB::transaction(function () use ($invoice, $data, $processed, $calc, $request) {
            $invoice->update(array_merge($data, $calc, [
                'status' => $request->input('save_action') === 'draft' ? 'draft' : ($invoice->status === 'draft' ? 'sent' : $invoice->status),
            ]));
            $invoice->items()->delete();
            $this->syncLineItems($invoice, $processed);
        });

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

        return redirect()->route('admin.invoices.index')
            ->with('success', 'Invoice saved as draft.');
    }

    public function show(Invoice $invoice)
    {
        return redirect()->route('admin.invoices.index', ['invoice' => $invoice->id]);
    }

    public function print(Invoice $invoice, Request $request)
    {
        $this->authorizeInvoice($invoice);
        $copyKey = $request->query('copy', 'original');

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

    public function shareLink(Invoice $invoice)
    {
        $this->authorizeInvoice($invoice);

        if (in_array($invoice->status, ['draft', 'cancelled'], true)) {
            return response()->json([
                'message' => 'Publish this invoice before sharing a public link.',
            ], 422);
        }

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

    public function downloadPdf(Invoice $invoice, Request $request, InvoicePdfGenerator $pdfGenerator)
    {
        $data = $this->invoiceDisplayData($invoice);
        $copyKey = $request->query('copy', 'original');
        $copies = $data['documentCopies'];
        $copyLabel = $copies[$copyKey] ?? $copies['original'] ?? 'Original Copy';

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

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

    /**
     * @return array<string, mixed>
     */
    private function invoiceDisplayData(Invoice $invoice): array
    {
        $this->authorizeInvoice($invoice);

        return $this->documentPresenter->displayData($invoice);
    }

    public function export(Request $request): StreamedResponse
    {
        $invoices = Invoice::with('customer')
            ->forCompany($this->companyId())
            ->latest('invoice_date')
            ->get();

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

        return response()->streamDownload(function () use ($invoices) {
            $out = fopen('php://output', 'w');
            fputcsv($out, [
                'Issue Date', 'Doc No', 'Status', 'Client', 'Due Date', 'Tax',
                'Amount', 'Payment Date', 'Balance', 'Dr/Cr', 'Type',
            ]);
            foreach ($invoices as $inv) {
                fputcsv($out, [
                    $inv->invoice_date->format('Y-m-d'),
                    $inv->invoice_number,
                    $inv->display_status,
                    $inv->customer->name ?? '',
                    $inv->due_date?->format('Y-m-d') ?? '',
                    $inv->total_tax,
                    $inv->grand_total,
                    $inv->payment_date?->format('Y-m-d') ?? '',
                    $inv->balance,
                    $inv->dr_cr,
                    $inv->typeLabel(),
                ]);
            }
            fclose($out);
        }, $filename, ['Content-Type' => 'text/csv']);
    }

    public function destroy(Invoice $invoice)
    {
        $this->authorizeInvoice($invoice);
        $invoice->delete();

        return redirect()->route('admin.invoices.index')->with('success', 'Invoice deleted successfully.');
    }

    private function formData(?Invoice $invoice, string $type): array
    {
        $companyId = $this->companyId();
        $company = Company::find($companyId);
        $count = Invoice::where('company_id', $companyId)->count();
        $defaultSeq = $company->invoice_start_number + $count + ($invoice ? 0 : 1);

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

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

        return [
            'invoice' => $invoice,
            'customers' => $customers,
            'gstEnabled' => GstHelper::usesRegularGst($company),
            'indianStates' => config('company.indian_states', []),
            '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'),
            ],
            'catalogItems' => $catalogItems,
            'taxRates' => $taxRates,
            'invoiceType' => $type,
            'typeLabel' => config('invoice.types.'.$type),
            'createTypes' => config('invoice.create_types'), // only 4 creatable types
            'paymentModes' => config('invoice.payment_modes'),
            'advancePaymentMethods' => config('invoice.advance_payment_methods'),
            'paymentTerms' => config('invoice.payment_terms'),
            'salesPersons' => config('invoice.sales_persons'),
            'shippingModes' => config('invoice.shipping_modes'),
            'discountAllTypes' => config('invoice.discount_all_types'),
            'company' => $company,
            'defaultPrefix' => $invoice?->invoice_prefix ?? $company->invoice_prefix,
            'defaultSeq' => $invoice?->invoice_seq ?? $defaultSeq,
            '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' => $invoice
                ? $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()
                : [['item_name' => '', 'description' => '', 'unit' => 'NOS', 'quantity' => 1, 'rate' => 0, 'discount_percent' => 0, 'tax_rate' => 0, 'hsn_sac' => '']],
        ];
    }

    private function validateInvoice(Request $request, ?Invoice $invoice = null): array
    {
        $validTypes = array_keys(config('invoice.types'));

        $data = $request->validate([
            'customer_id' => ['required', 'exists:customers,id'],
            'invoice_prefix' => ['required', 'string', 'max:20'],
            'invoice_seq' => ['required', 'integer', 'min:1'],
            'invoice_date' => ['required', 'date'],
            'due_date' => ['nullable', 'date'],
            'po_no' => ['nullable', 'string', 'max:50'],
            'po_date' => ['nullable', 'date'],
            'payment_mode' => ['nullable', 'string', 'max:20'],
            'payment_terms' => ['nullable', 'string', 'max:50'],
            'sales_person' => ['nullable', 'string', 'max:100'],
            'excise_percent' => ['nullable', 'numeric', 'min:0', 'max:100'],
            'type' => ['required', 'in:'.implode(',', $validTypes)],
            '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'],
            'advance_payment_method' => ['nullable', 'string', 'max:50'],
            'advance_payment_date' => ['nullable', 'date'],
            'advance_payment_reference' => ['nullable', 'string', 'max:100'],
            'advance_payment_memo' => ['nullable', 'string'],
            'transport_eway' => ['nullable', 'string'],
            'place_of_supply' => ['nullable', 'string', 'max:100'],
            'tax_on' => ['nullable', 'in:bill_to,ship_to'],
        ]);

        $data['company_id'] = $this->companyId();
        $data['invoice_number'] = $data['invoice_prefix'].'-'.str_pad((string) $data['invoice_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_transport_eway'] = $request->boolean('has_transport_eway');

        if (! $data['has_shipping']) {
            $data['shipping_charges'] = 0;
        }
        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';
            if ($data['discount_all_type'] === 'percent') {
                $data['discount_all_percent'] = (float) ($data['discount_all_value'] ?? 0);
            } else {
                $data['discount_all_percent'] = 0;
            }
        }
        if (! $data['has_shipping']) {
            $data['shipping_mode'] = null;
        }
        if (! $data['has_custom_amount']) {
            $data['custom_amount'] = 0;
        }
        if (! $data['has_advance_payment']) {
            $data['advance_payment'] = 0;
            $data['advance_payment_method'] = null;
            $data['advance_payment_date'] = null;
            $data['advance_payment_reference'] = null;
            $data['advance_payment_memo'] = null;
        }
        if ($data['type'] !== 'excise_invoice') {
            $data['excise_percent'] = 0;
        }

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

        $exists = Invoice::where('company_id', $this->companyId())
            ->where('invoice_number', $data['invoice_number'])
            ->when($invoice, fn ($q) => $q->where('id', '!=', $invoice->id))
            ->exists();

        if ($exists) {
            throw \Illuminate\Validation\ValidationException::withMessages([
                'invoice_seq' => ['This invoice 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);
        }

        $exciseAmount = 0;
        if ($data['type'] === 'excise_invoice' && ($data['excise_percent'] ?? 0) > 0) {
            $exciseAmount = $subtotal * ((float) $data['excise_percent'] / 100);
        }

        $totalTax = $gst['total_tax'];
        $grand = $subtotal
            + $totalTax
            + $exciseAmount
            + (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));

        return array_merge([
            'subtotal' => round($subtotal, 2),
            'taxable_amount' => round($subtotal, 2),
            'discount_amount' => round($discountAll + (float) ($data['discount_on_total'] ?? 0), 2),
            'total_tax' => $totalTax,
            'excise_amount' => round($exciseAmount, 2),
            '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'],
            'grand_total' => $grand,
            'paid_amount' => 0,
            'items' => $processedItems,
        ]);
    }

    private function syncLineItems(Invoice $invoice, array $items): void
    {
        foreach ($items as $row) {
            InvoiceItem::create([
                'invoice_id' => $invoice->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' => round((float) ($row['discount'] ?? 0), 2),
                'discount_percent' => (float) ($row['discount_percent'] ?? 0),
                '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),
            ]);
        }
    }

    private function authorizeInvoice(Invoice $invoice): void
    {
        if ($invoice->company_id !== $this->companyId()) {
            abort(403);
        }
    }
}
