<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\CreditNote;
use App\Models\CreditNoteItem;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\InvoiceActivityLog;
use App\Models\InvoiceItem;
use App\Models\Payment;
use App\Models\ServiceTicket;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;

class InvoiceController extends Controller
{
    public function index(Request $request): View
    {
        $search = trim((string) $request->query('search', ''));
        $status = $request->query('status');
        $paymentStatus = $request->query('payment_status');
        $sourceType = $request->query('source_type');
        $density = $request->query('density', 'comfortable');
        if (! in_array($density, ['comfortable', 'compact'], true)) {
            $density = 'comfortable';
        }

        $invoices = Invoice::with(['customer', 'issuer'])
            ->when($search !== '', function ($query) use ($search) {
                $query->where(function ($inner) use ($search) {
                    $inner->where('invoice_number', 'like', "%{$search}%")
                        ->orWhere('verification_code', 'like', "%{$search}%")
                        ->orWhereHas('customer', fn ($customer) => $customer->where('name', 'like', "%{$search}%"));
                });
            })
            ->when(in_array($status, Invoice::STATUSES, true), fn ($query) => $query->where('status', $status))
            ->when(in_array($paymentStatus, Invoice::PAYMENT_STATUSES, true), fn ($query) => $query->where('payment_status', $paymentStatus))
            ->when(in_array($sourceType, Invoice::SOURCES, true), fn ($query) => $query->where('source_type', $sourceType))
            ->latest()
            ->paginate(20)
            ->withQueryString();

        $stats = [
            'draft' => Invoice::where('status', Invoice::STATUS_DRAFT)->count(),
            'review' => Invoice::where('status', Invoice::STATUS_REVIEW)->count(),
            'issued' => Invoice::where('status', Invoice::STATUS_ISSUED)->count(),
            'unpaid' => Invoice::where('payment_status', Invoice::PAYMENT_UNPAID)->count(),
            'paid' => Invoice::where('payment_status', Invoice::PAYMENT_PAID)->count(),
        ];

        return view('admin.invoices.index', compact('invoices', 'search', 'status', 'paymentStatus', 'sourceType', 'stats', 'density'));
    }

    public function create(Request $request): View
    {
        $invoice = new Invoice([
            'source_type' => Invoice::SOURCE_MANUAL,
            'currency' => 'IDR',
            'payment_status' => Invoice::PAYMENT_UNPAID,
            'status' => Invoice::STATUS_DRAFT,
            'invoice_date' => now()->toDateString(),
            'due_date' => now()->toDateString(),
        ]);

        $prefillItems = [[
            'item_type' => 'manual',
            'description' => '',
            'qty' => 1,
            'unit' => 'pcs',
            'unit_price' => 0,
            'discount_amount' => 0,
            'tax_amount' => 0,
        ]];

        $serviceTicket = null;
        $selectedCustomerId = old('customer_id');

        if ($request->filled('service_ticket_id')) {
            $serviceTicket = $this->loadInvoiceableServiceTicket((int) $request->query('service_ticket_id'));
            $selectedCustomerId = $serviceTicket->customer_id;
            $invoice->source_type = Invoice::SOURCE_SERVICE;
            $invoice->source_id = $serviceTicket->id;
            $prefillItems = [[
                'item_type' => $serviceTicket->status === 'Cancelled' ? 'cancellation_fee' : 'service',
                'description' => $serviceTicket->status === 'Cancelled'
                    ? 'Biaya diagnosa / pembatalan servis - ' . $serviceTicket->ticket_code
                    : 'Jasa servis ' . $serviceTicket->brand . ' ' . $serviceTicket->model . ' - ' . $serviceTicket->ticket_code,
                'qty' => 1,
                'unit' => 'jasa',
                'unit_price' => 0,
                'discount_amount' => 0,
                'tax_amount' => 0,
            ]];
            $invoice->public_note = 'Referensi tiket servis: ' . $serviceTicket->ticket_code;
        }

        return view('admin.invoices.form', [
            'invoice' => $invoice,
            'customers' => Customer::where('is_active', true)->orderBy('name')->get(['id', 'name', 'code', 'whatsapp']),
            'serviceTicket' => $serviceTicket,
            'prefillItems' => old('items', $prefillItems),
            'isEdit' => false,
            'selectedCustomerId' => $selectedCustomerId,
        ]);
    }

    public function store(Request $request): RedirectResponse
    {
        $action = $request->input('submit_action', 'draft');
        $isAdmin = auth()->user()?->isAdmin() === true;

        if ($action === 'issue' && ! $isAdmin) {
            return back()->with('error', 'Hanya admin yang dapat menerbitkan invoice.');
        }

        $data = $this->validated($request, $action === 'issue');

        $invoice = DB::transaction(function () use ($request, $data, $action) {
            $sourceTicket = $data['source_type'] === Invoice::SOURCE_SERVICE
                ? $this->loadInvoiceableServiceTicket((int) $data['source_id'])
                : null;

            if ($sourceTicket) {
                $hasExisting = Invoice::where('source_type', Invoice::SOURCE_SERVICE)
                    ->where('source_id', $sourceTicket->id)
                    ->where('status', '!=', Invoice::STATUS_CANCELLED)
                    ->exists();

                if ($hasExisting) {
                    abort(422, 'Tiket servis ini sudah memiliki invoice aktif.');
                }
            }

            $invoice = Invoice::create([
                'customer_id' => $data['customer_id'],
                'source_type' => $data['source_type'],
                'source_id' => $data['source_id'] ?? null,
                'status' => Invoice::STATUS_DRAFT,
                'payment_status' => Invoice::PAYMENT_UNPAID,
                'invoice_date' => $data['invoice_date'],
                'due_date' => $data['due_date'] ?: $data['invoice_date'],
                'currency' => 'IDR',
                'public_note' => $data['public_note'] ?? null,
                'internal_note' => $data['internal_note'] ?? null,
                'created_by' => auth()->id(),
                'updated_by' => auth()->id(),
            ]);

            $this->syncItems($invoice, $data['items']);
            $this->persistSignature($invoice, $request->input('signature_data'), $request->boolean('signature_drawn'));
            $invoice->load(['items', 'payments']);
            $invoice->recalculateTotals();

            if ($action === 'review') {
                $this->submitForReview($invoice);
            }

            if ($action === 'issue') {
                $this->issueInvoice($invoice);
            }

            if ($action === 'issue') {
                $this->logActivity($invoice, 'issued', 'Invoice diterbitkan oleh admin.');
            } elseif ($action === 'review') {
                $this->logActivity($invoice, 'submitted_for_review', 'Draft invoice diajukan untuk review admin.');
            } else {
                $this->logActivity($invoice, 'draft_created', 'Draft invoice dibuat.');
            }

            return $invoice;
        });

        $success = $action === 'issue'
            ? 'Invoice berhasil diterbitkan.'
            : ($action === 'review' ? 'Invoice berhasil diajukan untuk review admin.' : 'Draft invoice berhasil disimpan.');

        return redirect()->route('admin.invoices.show', $invoice)
            ->with('success', $success);
    }

    public function show(Invoice $invoice): View
    {
        $invoice->load(['customer', 'items', 'payments.receiver', 'issuer', 'activities.user', 'creditNotes.items', 'creditNotes.creator']);

        $sourceTicket = $invoice->source_type === Invoice::SOURCE_SERVICE && $invoice->source_id
            ? ServiceTicket::find($invoice->source_id)
            : null;

        $isCancellationInvoice = $sourceTicket && $sourceTicket->status === 'Cancelled';

        return view('admin.invoices.show', [
            'invoice' => $invoice,
            'verifyUrl' => $invoice->verification_code ? route('invoices.verify', $invoice->verification_code) : null,
            'sourceTicket' => $sourceTicket,
            'isCancellationInvoice' => $isCancellationInvoice,
        ]);
    }

    public function edit(Invoice $invoice): View
    {
        abort_if($invoice->status !== Invoice::STATUS_DRAFT, 403, 'Hanya draft yang dapat diedit.');

        $invoice->load('items');
        $serviceTicket = $invoice->source_type === Invoice::SOURCE_SERVICE && $invoice->source_id
            ? ServiceTicket::with('customer')->find($invoice->source_id)
            : null;

        return view('admin.invoices.form', [
            'invoice' => $invoice,
            'customers' => Customer::where('is_active', true)->orderBy('name')->get(['id', 'name', 'code', 'whatsapp']),
            'serviceTicket' => $serviceTicket,
            'prefillItems' => old('items', $invoice->items->map(fn ($item) => [
                'item_type' => $item->item_type,
                'description' => $item->description,
                'qty' => $item->qty,
                'unit' => $item->unit,
                'unit_price' => $item->unit_price,
                'discount_amount' => $item->discount_amount,
                'tax_amount' => $item->tax_amount,
            ])->values()->all()),
            'isEdit' => true,
            'selectedCustomerId' => old('customer_id', $invoice->customer_id),
        ]);
    }

    public function update(Request $request, Invoice $invoice): RedirectResponse
    {
        abort_if($invoice->status !== Invoice::STATUS_DRAFT, 403, 'Hanya draft yang dapat diedit.');

        $action = $request->input('submit_action', 'draft');
        $isAdmin = auth()->user()?->isAdmin() === true;

        if ($action === 'issue' && ! $isAdmin) {
            return back()->with('error', 'Hanya admin yang dapat menerbitkan invoice.');
        }

        $data = $this->validated($request, $action === 'issue');

        DB::transaction(function () use ($request, $invoice, $data, $action) {
            if ($data['source_type'] === Invoice::SOURCE_SERVICE) {
                $this->loadInvoiceableServiceTicket((int) $data['source_id']);
                $hasExisting = Invoice::where('source_type', Invoice::SOURCE_SERVICE)
                    ->where('source_id', $data['source_id'])
                    ->where('id', '!=', $invoice->id)
                    ->where('status', '!=', Invoice::STATUS_CANCELLED)
                    ->exists();

                if ($hasExisting) {
                    abort(422, 'Tiket servis ini sudah memiliki invoice aktif.');
                }
            }

            $invoice->update([
                'customer_id' => $data['customer_id'],
                'source_type' => $data['source_type'],
                'source_id' => $data['source_id'] ?? null,
                'invoice_date' => $data['invoice_date'],
                'due_date' => $data['due_date'] ?: $data['invoice_date'],
                'public_note' => $data['public_note'] ?? null,
                'internal_note' => $data['internal_note'] ?? null,
                'updated_by' => auth()->id(),
            ]);

            $this->syncItems($invoice, $data['items']);
            $this->persistSignature($invoice, $request->input('signature_data'), $request->boolean('signature_drawn'));
            $invoice->load(['items', 'payments']);
            $invoice->recalculateTotals();

            if ($action === 'review') {
                $this->submitForReview($invoice);
            }

            if ($action === 'issue') {
                $this->issueInvoice($invoice);
            }

            if ($action === 'issue') {
                $this->logActivity($invoice, 'issued', 'Draft invoice diperbarui dan diterbitkan oleh admin.');
            } elseif ($action === 'review') {
                $this->logActivity($invoice, 'submitted_for_review', 'Draft invoice diperbarui dan diajukan untuk review admin.');
            } else {
                $this->logActivity($invoice, 'draft_updated', 'Draft invoice diperbarui.');
            }
        });

        $success = $action === 'issue'
            ? 'Invoice berhasil diterbitkan.'
            : ($action === 'review' ? 'Invoice berhasil diajukan untuk review admin.' : 'Draft invoice berhasil diperbarui.');

        return redirect()->route('admin.invoices.show', $invoice)
            ->with('success', $success);
    }

    public function issue(Request $request, Invoice $invoice): RedirectResponse
    {
        if (! auth()->user()?->isAdmin()) {
            return back()->with('error', 'Hanya admin yang dapat menerbitkan invoice.');
        }

        if (! in_array($invoice->status, [Invoice::STATUS_DRAFT, Invoice::STATUS_REVIEW], true)) {
            return back()->with('error', 'Invoice ini tidak dapat diterbitkan dari status saat ini.');
        }

        DB::transaction(function () use ($invoice) {
            $invoice->load(['items', 'payments']);
            $invoice->recalculateTotals();
            $this->issueInvoice($invoice);
            $this->logActivity($invoice, 'issued', 'Invoice diterbitkan dari halaman detail.');
        });

        return back()->with('success', 'Invoice berhasil diterbitkan.');
    }

    public function submitReview(Request $request, Invoice $invoice): RedirectResponse
    {
        if ($invoice->status !== Invoice::STATUS_DRAFT) {
            return back()->with('error', 'Hanya draft yang dapat diajukan review.');
        }

        DB::transaction(function () use ($invoice) {
            $invoice->load(['items', 'payments']);
            $invoice->recalculateTotals();
            $this->submitForReview($invoice);
            $this->logActivity($invoice, 'submitted_for_review', 'Invoice diajukan review dari halaman detail.');
        });

        return back()->with('success', 'Invoice berhasil diajukan untuk review admin.');
    }

    public function createCreditNote(Request $request, Invoice $invoice): RedirectResponse
    {
        if (! auth()->user()?->isAdmin()) {
            return back()->with('error', 'Credit note hanya dapat dibuat oleh admin.');
        }

        if ($invoice->status !== Invoice::STATUS_ISSUED) {
            return back()->with('error', 'Credit note hanya dapat dibuat untuk invoice berstatus issued.');
        }

        $data = $request->validate([
            'credit_amount' => ['required', 'numeric', 'min:1', 'max:' . max((float) $invoice->grand_total, 1)],
            'credit_reason' => ['required', 'string', 'min:5', 'max:1000'],
        ]);

        DB::transaction(function () use ($invoice, $data) {
            $credit = CreditNote::create([
                'credit_note_number' => CreditNote::nextNumber(),
                'invoice_id' => $invoice->id,
                'customer_id' => $invoice->customer_id,
                'credit_date' => now()->toDateString(),
                'grand_total' => $data['credit_amount'],
                'reason' => $data['credit_reason'],
                'created_by' => auth()->id(),
            ]);

            CreditNoteItem::create([
                'credit_note_id' => $credit->id,
                'description' => 'Penyesuaian credit note untuk invoice ' . ($invoice->invoice_number ?: ('Draft #' . $invoice->id)),
                'qty' => 1,
                'unit_price' => $data['credit_amount'],
                'line_total' => $data['credit_amount'],
            ]);

            $invoice->load(['items', 'payments']);
            $invoice->recalculateTotals();
            $invoice->update(['updated_by' => auth()->id()]);

            $this->logActivity(
                $invoice,
                'credit_note_created',
                'Credit note ' . $credit->credit_note_number . ' dibuat sebesar Rp ' . number_format((float) $data['credit_amount'], 0, ',', '.')
            );
        });

        return back()->with('success', 'Credit note berhasil dibuat.');
    }

    public function cancel(Request $request, Invoice $invoice): RedirectResponse
    {
        $data = $request->validate([
            'cancel_reason' => ['required', 'string', 'min:5', 'max:500'],
        ]);

        if ($invoice->status === Invoice::STATUS_CANCELLED) {
            return back()->with('error', 'Invoice ini sudah dibatalkan sebelumnya.');
        }

        if ((float) $invoice->amount_paid > 0) {
            return back()->with('error', 'Invoice yang sudah memiliki pembayaran tidak bisa dibatalkan langsung.');
        }

        $invoice->update([
            'status' => Invoice::STATUS_CANCELLED,
            'cancel_reason' => $data['cancel_reason'],
            'cancelled_by' => auth()->id(),
            'updated_by' => auth()->id(),
        ]);

        $this->logActivity($invoice, 'cancelled', 'Invoice dibatalkan. Alasan: ' . $data['cancel_reason']);

        return back()->with('success', 'Invoice berhasil dibatalkan.');
    }

    public function addPayment(Request $request, Invoice $invoice): RedirectResponse
    {
        if ($invoice->status !== Invoice::STATUS_ISSUED) {
            return back()->with('error', 'Pembayaran hanya bisa dicatat untuk invoice yang sudah diterbitkan.');
        }

        $invoice->load(['payments', 'items']);
        $invoice->recalculateTotals();

        $data = $request->validate([
            'payment_date' => ['required', 'date'],
            'amount' => ['required', 'numeric', 'min:1', 'max:' . max((float) $invoice->balance_due, 1)],
            'method' => ['required', 'in:' . implode(',', Payment::METHODS)],
            'reference_number' => ['nullable', 'string', 'max:100'],
            'note' => ['nullable', 'string', 'max:1000'],
        ]);

        DB::transaction(function () use ($invoice, $data) {
            Payment::create([
                'invoice_id' => $invoice->id,
                'payment_number' => $this->nextPaymentNumber(),
                'payment_date' => $data['payment_date'],
                'amount' => $data['amount'],
                'method' => $data['method'],
                'reference_number' => $data['reference_number'] ?? null,
                'note' => $data['note'] ?? null,
                'received_by' => auth()->id(),
                'created_by' => auth()->id(),
            ]);

            $invoice->load(['payments', 'items']);
            $invoice->recalculateTotals();
            $invoice->update(['updated_by' => auth()->id()]);

            $this->logActivity($invoice, 'payment_added', 'Pembayaran dicatat sebesar Rp ' . number_format((float) $data['amount'], 0, ',', '.'));
        });

        return back()->with('success', 'Pembayaran berhasil dicatat.');
    }

    public function pdf(Invoice $invoice)
    {
        $invoice->load(['customer', 'items', 'payments.receiver', 'issuer']);

        $sourceTicket = $invoice->source_type === Invoice::SOURCE_SERVICE && $invoice->source_id
            ? ServiceTicket::find($invoice->source_id)
            : null;
        $isCancellationInvoice = $sourceTicket && $sourceTicket->status === 'Cancelled';

        $verifyUrl = $invoice->verification_code ? route('invoices.verify', $invoice->verification_code) : null;
        $pdf = Pdf::loadView('admin.invoices.pdf', compact('invoice', 'verifyUrl', 'isCancellationInvoice'))
            ->setPaper('a4', 'portrait');

        $fileName = ($invoice->invoice_number ?: 'invoice-' . $invoice->id) . '.pdf';

        return $pdf->download($fileName);
    }

    private function validated(Request $request, bool $issuing = false): array
    {
        return $request->validate([
            'customer_id' => ['required', 'exists:customers,id'],
            'source_type' => ['required', 'in:' . implode(',', Invoice::SOURCES)],
            'source_id' => ['nullable', 'integer'],
            'invoice_date' => ['required', 'date'],
            'due_date' => ['nullable', 'date'],
            'public_note' => ['nullable', 'string', 'max:2000'],
            'internal_note' => ['nullable', 'string', 'max:2000'],
            'signature_data' => ['nullable', 'string'],
            'signature_drawn' => ['nullable', 'boolean'],
            'items' => ['required', 'array', 'min:1'],
            'items.*.item_type' => ['required', 'in:' . implode(',', Invoice::ITEM_TYPES)],
            'items.*.description' => ['required', 'string', 'max:255'],
            'items.*.qty' => ['required', 'numeric', 'min:0.01'],
            'items.*.unit' => ['nullable', 'string', 'max:30'],
            'items.*.unit_price' => ['required', 'numeric', 'min:0'],
            'items.*.discount_amount' => ['nullable', 'numeric', 'min:0'],
            'items.*.tax_amount' => ['nullable', 'numeric', 'min:0'],
        ]);
    }

    private function syncItems(Invoice $invoice, array $items): void
    {
        $invoice->items()->delete();

        foreach (array_values($items) as $index => $item) {
            $qty = (float) $item['qty'];
            $unitPrice = (float) $item['unit_price'];
            $discount = (float) ($item['discount_amount'] ?? 0);
            $tax = (float) ($item['tax_amount'] ?? 0);

            InvoiceItem::create([
                'invoice_id' => $invoice->id,
                'item_type' => $item['item_type'],
                'description' => trim($item['description']),
                'qty' => $qty,
                'unit' => $item['unit'] ?? null,
                'unit_price' => $unitPrice,
                'discount_amount' => $discount,
                'tax_amount' => $tax,
                'line_total' => max(($qty * $unitPrice) - $discount + $tax, 0),
                'sort_order' => $index,
            ]);
        }
    }

    private function issueInvoice(Invoice $invoice): void
    {
        if (! $invoice->signature_path) {
            abort(422, 'Tanda tangan penerbit wajib diisi sebelum invoice diterbitkan.');
        }

        $invoice->update([
            'invoice_number' => $invoice->invoice_number ?: Invoice::nextInvoiceNumber(),
            'verification_code' => $invoice->verification_code ?: Invoice::generateVerificationCode(),
            'status' => Invoice::STATUS_ISSUED,
            'invoice_date' => $invoice->invoice_date ?: now()->toDateString(),
            'issued_at' => now(),
            'review_approved_at' => now(),
            'review_approved_by' => auth()->id(),
            'issued_by' => auth()->id(),
            'updated_by' => auth()->id(),
        ]);
    }

    private function submitForReview(Invoice $invoice): void
    {
        $invoice->update([
            'status' => Invoice::STATUS_REVIEW,
            'review_submitted_at' => now(),
            'review_submitted_by' => auth()->id(),
            'updated_by' => auth()->id(),
        ]);
    }

    private function persistSignature(Invoice $invoice, ?string $base64, bool $signatureDrawn = false): void
    {
        if (! $base64 || ! $signatureDrawn) {
            return;
        }

        $data = preg_replace('/^data:image\/\w+;base64,/', '', $base64);
        $binary = base64_decode((string) $data, true);

        if ($binary === false) {
            abort(422, 'Format tanda tangan tidak valid.');
        }

        $path = 'invoices/' . ($invoice->id ?: 'draft') . '/signature_' . now()->format('YmdHis') . '.png';
        Storage::disk('public')->put($path, $binary);

        $invoice->update(['signature_path' => $path]);
    }

    private function loadInvoiceableServiceTicket(int $ticketId): ServiceTicket
    {
        $ticket = ServiceTicket::with('customer')->findOrFail($ticketId);

        abort_unless(in_array($ticket->status, ['Ready', 'Cancelled'], true), 422, 'Invoice service hanya bisa dibuat dari tiket Ready atau Cancelled.');

        return $ticket;
    }

    private function logActivity(Invoice $invoice, string $action, string $description): void
    {
        InvoiceActivityLog::create([
            'invoice_id' => $invoice->id,
            'user_id' => auth()->id(),
            'action' => $action,
            'description' => $description,
        ]);
    }

    private function nextPaymentNumber(): string
    {
        $prefix = 'PAY-' . now()->format('Ym') . '-';
        $last = Payment::whereNotNull('payment_number')
            ->where('payment_number', 'like', $prefix . '%')
            ->orderByDesc('payment_number')
            ->value('payment_number');

        $sequence = $last ? ((int) substr($last, -5)) + 1 : 1;

        return $prefix . str_pad((string) $sequence, 5, '0', STR_PAD_LEFT);
    }
}