<?php

namespace App\Controllers\Admin;

use App\Controllers\BaseController;

use App\Models\OrderModel;
use App\Models\OrderItemModel;
use App\Models\ProductModel;
use App\Models\PaymentModel;
use App\Models\PaymentMethodModel;
use App\Models\CompanySettingModel;
use App\Services\MonthlyPoScheduler;

class OrderController extends BaseController
{
    protected OrderModel $orderModel;
    protected OrderItemModel $itemModel;
    protected ProductModel $productModel;
    protected PaymentModel $paymentModel;
    protected PaymentMethodModel $paymentMethodModel;
    protected CompanySettingModel $settingModel;
    protected MonthlyPoScheduler $monthlyPoScheduler;

    public function __construct()
    {
        helper(['form', 'url']);

        $this->orderModel         = new OrderModel();
        $this->itemModel          = new OrderItemModel();
        $this->productModel       = new ProductModel();
        $this->paymentModel       = new PaymentModel();
        $this->paymentMethodModel = new PaymentMethodModel();
        $this->settingModel       = new CompanySettingModel();
        $this->monthlyPoScheduler = new MonthlyPoScheduler();
    }

    //------------------------------
    // INDEX
    //------------------------------

public function index()
{
    $search = $this->request->getGet('search');

    // 1. Ambil data dengan Pagination & Search
    $query = $this->orderModel->orderBy('id_order', 'DESC');
    
    if (!empty($search)) {
        $query = $query->groupStart()
                       ->like('order_number', $search)
                       ->orLike('customer_name', $search)
                       ->groupEnd();
    }

    // 2. Siapkan data untuk View
    $data = [
        'title'        => 'Daftar Order',
        'orders'       => $query->paginate(10), // 10 order per halaman
        'pager'        => $this->orderModel->pager,
        'search'       => $search,
        
        // Statistik (Gunakan instance baru agar tidak terpengaruh filter pencarian)
        'totalOrder'   => $this->orderModel->countAllResults(),
        'totalUnpaid'  => $this->orderModel->where('payment_status', 'unpaid')->countAllResults(),
        'totalPartial' => $this->orderModel->where('payment_status', 'partial')->countAllResults(),
        'totalPaid'    => $this->orderModel->where('payment_status', 'paid')->countAllResults()
    ];

    return view('admin/orders/index', $data);
}
    //------------------------------
    // CREATE
    //------------------------------
    public function create()
    {
        $setting = $this->settingModel->first();

        $paymentMethods = $this->paymentMethodModel->active();

        if (empty($paymentMethods)) {
            $this->paymentMethodModel->insertBatch([
                [
                    'nama_metode' => 'Transfer',
                    'kode'        => 'transfer',
                    'keterangan' => 'Transfer bank atau virtual account',
                    'is_active'  => 1
                ],
                [
                    'nama_metode' => 'QRIS',
                    'kode'        => 'qris',
                    'keterangan' => 'Pembayaran melalui QRIS',
                    'is_active'  => 1
                ],
                [
                    'nama_metode' => 'Cash',
                    'kode'        => 'cash',
                    'keterangan' => 'Bayar tunai saat pengambilan',
                    'is_active'  => 1
                ]
            ]);

            $paymentMethods = $this->paymentMethodModel->active();
        }

        $monthlyPoLimit = (int) ($setting['monthly_po_limit'] ?? 0);
        $data = [
            'title' => 'Order Baru',

            'order_no' => $this->orderModel->generateOrderNumber(),

            'products' => $this->productModel
                ->where('status', 'active')
                ->orderBy('nama_product', 'ASC')
                ->findAll(),

            'paymentMethods' => $paymentMethods,

            'monthlyPoLimit' => $monthlyPoLimit,
            'monthlyPoUsed' => $this->poQuantityForMonth(date('Y-m-01')),

            'defaultTargetDate' => date('Y-m-d')
        ];

        return view('admin/orders/create', $data);
    }

    //------------------------------
    // STORE
    //------------------------------
    public function store()
    {
        if (!$this->validate([
            'customer_name' => 'required',
            'total'         => 'required|decimal'
        ])) {
            return redirect()->back()
                ->withInput()
                ->with('errors', $this->validator->getErrors());
        }

        $db = \Config\Database::connect();
        $db->transBegin();

        try {

            $orderNumber     = $this->request->getPost('order_number') ?: $this->orderModel->generateOrderNumber();
            $total           = (float)$this->request->getPost('total');
            $paid            = (float)$this->request->getPost('jumlah_bayar');
            $orderType       = $this->request->getPost('order_type') ?: 'ready';
            $targetDate      = $this->request->getPost('target_date');
            $paymentMethodId = $this->request->getPost('id_payment_method');
            $items           = self::normalizeOrderItems($this->request->getPost());
            $products        = array_column($items, 'id_product');

            log_message('debug', 'OrderStore payload: ' . json_encode([
                'order_number' => $orderNumber,
                'customer_name' => $this->request->getPost('customer_name'),
                'total' => $total,
                'paid' => $paid,
                'order_type' => $orderType,
                'products' => $products,
                'items' => $items,
            ]));

            if ($paid > 0 && empty($paymentMethodId)) {
                return redirect()->back()
                    ->withInput()
                    ->with('error', 'Pilih metode pembayaran saat memasukkan jumlah bayar.');
            }

            if (empty($items)) {
                return redirect()->back()->withInput()->with('error', 'Pilih minimal satu produk sebelum menyimpan order.');
            }

            foreach ($items as $item) {
                if ($item['is_custom'] && $item['product_name'] === '') {
                    return redirect()->back()->withInput()->with('error', 'Nama produk custom wajib diisi.');
                }
            }

            // Tipe order ditentukan oleh produk yang dipilih, bukan hanya radio
            // button dari browser. Satu produk PO membuat seluruh order menjadi PO.
            $selectedProducts = [];
            foreach ($items as $item) {
                if ($item['is_custom']) {
                    continue;
                }

                $product = $this->productModel->find($item['id_product']);
                if (! $product) {
                    throw new \RuntimeException('Produk yang dipilih tidak ditemukan.');
                }

                $selectedProducts[(string) $item['id_product']] = $product;
            }

            $readyStockError = self::readyStockAvailabilityError($items, $selectedProducts);
            if ($readyStockError !== null) {
                throw new \RuntimeException($readyStockError);
            }

            $automaticallyChangedToPo = $orderType !== 'po'
                && self::requiresPreOrder($items, $selectedProducts);

            if ($automaticallyChangedToPo) {
                $orderType = 'po';
            }

            if ($orderType === 'po' && count($products) > 5 && ! $this->request->getPost('po_bulk_confirmed')) {
                return redirect()->back()->withInput()->with('error', 'PO berisi lebih dari 5 produk. Konfirmasi penjadwalan produksi diperlukan.');
            }

            // Total yang disimpan harus berasal dari item yang benar-benar diterima,
            // bukan hanya nilai hidden input dari browser.
            $total = array_sum(array_column($items, 'subtotal'));
            if ($total <= 0) {
                return redirect()->back()->withInput()->with('error', 'Total order harus lebih besar dari 0.');
            }

            if ($paid > $total) {
                return redirect()->back()->withInput()->with('error', 'Jumlah pembayaran tidak boleh melebihi total order.');
            }

            $paymentMethod = $paymentMethodId ? $this->paymentMethodModel->find($paymentMethodId) : null;
            if ($paid > 0 && (! $paymentMethod || ! $paymentMethod['is_active'])) {
                return redirect()->back()->withInput()->with('error', 'Metode pembayaran tidak valid atau tidak aktif.');
            }

            /*
            |--------------------------------------------------------------------------
            | READY STOCK HARUS LUNAS
            |--------------------------------------------------------------------------
            */

            if ($orderType == 'ready' && $paid < $total) {

                return redirect()->back()
                    ->withInput()
                    ->with('error', 'Produk Ready Stock wajib dibayar lunas.');

            }

            /*
            |--------------------------------------------------------------------------
            | LIMIT PO PER BULAN
            |--------------------------------------------------------------------------
            */

            $monthlyPoLimit = 0;
            if ($orderType === 'po') {
                $setting = $this->settingModel->first();
                $monthlyPoLimit = (int) ($setting['monthly_po_limit'] ?? 0);
            }

            /*
            |--------------------------------------------------------------------------
            | HITUNG STATUS PEMBAYARAN
            |--------------------------------------------------------------------------
            */

            $remaining = $total - $paid;

            if ($remaining < 0) {
                $remaining = 0;
            }

            if ($paid <= 0) {

                $paymentStatus = 'unpaid';

            } elseif ($paid < $total) {

                $paymentStatus = 'partial';

            } else {

                $paymentStatus = 'paid';

            }

            /*
            |--------------------------------------------------------------------------
            | INSERT ORDER
            |--------------------------------------------------------------------------
            */

            $orderData = [
                'order_number'   => $orderNumber,
                'customer_name'  => $this->request->getPost('customer_name'),
                'customer_phone' => $this->request->getPost('customer_phone'),
                'customer_email' => $this->request->getPost('customer_email'),
                'order_type'     => $orderType,
                'total'          => $total,
                'paid_amount'      => $paid,
                'remaining_amount' => $remaining,
                'payment_status'   => $paymentStatus,
                'payment_method' => $paid > 0 ? $paymentMethod['kode'] : 'cash',
                'order_status'   => $paid > 0 ? 'production' : 'waiting_payment',
                'notes'          => $this->request->getPost('notes'),
                'target_date'    => $targetDate,
            ];

            if ($db->fieldExists('production_status', 'orders')) {
                $orderData['production_status'] = $orderType === 'po' ? 'waiting' : 'completed';
            }

            $orderId = $this->orderModel->insert($orderData, true);
            if (! $orderId) {
                throw new \RuntimeException('Gagal menyimpan data order: ' . implode(' ', $this->orderModel->errors()));
            }

            /*
            |--------------------------------------------------------------------------
            | INSERT ORDER ITEM
            |--------------------------------------------------------------------------
            */

            if (!empty($items)) {

                foreach ($items as $item) {

                    $productId = $item['id_product'];
                    $qty       = $item['qty'];
                    $price     = $item['price'];
                    $subtotal  = $item['subtotal'];

                    /*
                    |------------------------------------------
                    | PRODUK CUSTOM
                    |------------------------------------------
                    */

                    if ($item['is_custom']) {

                        if (! $this->itemModel->insert([

                            'id_order'     => $orderId,

                            // Produk custom tidak memiliki baris pada tabel products.
                            // Kolom ini nullable agar riwayat detail custom tetap tersimpan.
                            'id_product'   => null,

                            'product_name' => $item['product_name'],

                            'description'  => $item['description'] ?: null,

                            'qty'          => $qty,

                            'price'        => $price,

                            'subtotal'     => $subtotal

                        ])) {
                            throw new \RuntimeException('Gagal menyimpan item custom: ' . implode(' ', $this->itemModel->errors()));
                        }

                    } else {

                        $product = $selectedProducts[(string) $productId] ?? null;

                        if (!$product) {
                            throw new \RuntimeException('Produk yang dipilih tidak ditemukan.');
                        }

                        // Kurangi persediaan hanya untuk produk ready stock. Kondisi
                        // stok >= qty membuat pembaruan ini atomik sehingga stok tidak
                        // menjadi negatif jika ada order lain masuk bersamaan.
                        if (self::fulfillmentType($product) === 'ready') {
                            $stockUpdated = $db->table('products')
                                ->set('stok', 'stok - ' . (int) $qty, false)
                                ->where('id_product', (int) $productId)
                                ->where('stok >=', (int) $qty)
                                ->update();

                            if (! $stockUpdated || $db->affectedRows() !== 1) {
                                throw new \RuntimeException(sprintf(
                                    'Stok produk ready "%s" berubah atau tidak mencukupi saat order diproses.',
                                    $product['nama_product']
                                ));
                            }
                        }

                        if (! $this->itemModel->insert([

                            'id_order'     => $orderId,

                            'id_product'   => $productId,

                            'product_name' => $product['nama_product'],

                            'description'  => $product['description'],

                            'qty'          => $qty,

                            'price'        => $price,

                            'subtotal'     => $subtotal

                        ])) {
                            throw new \RuntimeException('Gagal menyimpan item order: ' . implode(' ', $this->itemModel->errors()));
                        }

                    }

                }

            }

            // Satu tagihan tetap satu order. Hanya pengerjaan PO-nya yang
            // dibagi per bulan ketika kapasitas bulan tujuan sudah penuh.
            $schedule = [];
            if ($orderType === 'po') {
                $schedule = $this->monthlyPoScheduler->replaceOrderSchedule(
                    (int) $orderId,
                    array_sum(array_column($items, 'qty')),
                    $monthlyPoLimit,
                    $targetDate
                );

                // Repack all unstarted PO batches. Capacity released by an
                // earlier edit is reused before an order is split again.
                $rebalancedSchedules = $this->monthlyPoScheduler->rebalance($monthlyPoLimit);
                $schedule = $rebalancedSchedules[(int) $orderId] ?? $this->monthlyPoScheduler->getOrderSchedule((int) $orderId);

                $targetDate = $schedule[0]['scheduled_month'] ?? $targetDate;
                if (! $this->orderModel->update($orderId, ['target_date' => $targetDate])) {
                    throw new \RuntimeException('Gagal memperbarui target jadwal PO.');
                }
            }

            /*
            |--------------------------------------------------------------------------
            | COMMIT ORDER FIRST
            |--------------------------------------------------------------------------
            */

            $db->transCommit();

            if ($paid > 0 && !empty($paymentMethodId)) {
                $paymentController = new PaymentController();
                $paymentController->recordPayment($orderId, $paid, (int) $paymentMethodId);
            }

            $successMessage = $automaticallyChangedToPo
                ? 'Order berhasil dibuat sebagai Pre-Order karena keranjang berisi produk PO.'
                : 'Order berhasil dibuat.';

            if (count($schedule) > 1) {
                $successMessage .= ' Produksi dibagi ke ' . count($schedule) . ' bulan sesuai kapasitas bulanan.';
            } elseif ($schedule !== [] && $schedule[0]['scheduled_month'] !== date('Y-m-01', strtotime($this->request->getPost('target_date') ?: 'today'))) {
                $successMessage .= ' Produksi dijadwalkan ke bulan berikutnya karena kapasitas bulan pilihan sudah penuh.';
            }

            return redirect()
                ->to('/admin/orders/show/' . $orderId)
                ->with('success', $successMessage);

        } catch (\Throwable $e) {

            $db->transRollback();

            return redirect()->back()
                ->withInput()
                ->with('error', $e->getMessage());

        }
    }

    /**
     * Normalizes either the flat form payload used by the admin form or a
     * nested items payload into one consistent item structure.
     */
    public static function normalizeOrderItems(array $payload): array
    {
        $rawItems = [];

        if (isset($payload['items']) && is_array($payload['items'])) {
            $rawItems = $payload['items'];
        } else {
            $products = $payload['product'] ?? [];
            $products = is_array($products) ? $products : [$products];
            $names = (array) ($payload['product_name'] ?? []);
            $qtys = (array) ($payload['qty'] ?? []);
            $prices = (array) ($payload['price'] ?? []);
            $descriptions = (array) ($payload['description'] ?? []);
            $customFlags = (array) ($payload['is_custom'] ?? []);

            foreach ($products as $index => $productId) {
                $rawItems[] = [
                    'id_product'   => $productId,
                    'product_name' => $names[$index] ?? '',
                    'qty'          => $qtys[$index] ?? 1,
                    'price'        => $prices[$index] ?? 0,
                    'description'  => $descriptions[$index] ?? '',
                    'is_custom'    => $customFlags[$index] ?? false,
                ];
            }
        }

        $items = [];
        foreach ($rawItems as $rawItem) {
            if (! is_array($rawItem)) {
                continue;
            }

            $productId = trim((string) ($rawItem['id_product'] ?? $rawItem['product'] ?? ''));
            $isCustom = filter_var($rawItem['is_custom'] ?? false, FILTER_VALIDATE_BOOLEAN)
                || $productId === ''
                || str_contains(strtolower($productId), 'custom');
            $qty = max(1, (int) ($rawItem['qty'] ?? 1));
            $price = max(0, (float) ($rawItem['price'] ?? 0));

            $items[] = [
                'id_product'   => $productId,
                'product_name' => trim((string) ($rawItem['product_name'] ?? '')),
                'description'  => trim((string) ($rawItem['description'] ?? '')),
                'qty'          => $qty,
                'price'        => $price,
                'subtotal'     => $qty * $price,
                'is_custom'    => $isCustom,
            ];
        }

        return $items;
    }

    /** Returns true when at least one selected master product is a PO product. */
    public static function requiresPreOrder(array $items, array $productsById): bool
    {
        foreach ($items as $item) {
            if (($item['is_custom'] ?? false) === true) {
                continue;
            }

            $productId = (string) ($item['id_product'] ?? '');
            if (self::fulfillmentType($productsById[$productId] ?? []) === 'po') {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns an error when the requested ready-stock quantity exceeds stock.
     * Keeping this on the server prevents browser-side validation from being
     * bypassed and also totals duplicate product rows before comparing stock.
     */
    public static function readyStockAvailabilityError(array $items, array $productsById): ?string
    {
        $requestedByProduct = [];

        foreach ($items as $item) {
            if (($item['is_custom'] ?? false) === true) {
                continue;
            }

            $productId = (string) ($item['id_product'] ?? '');
            $product = $productsById[$productId] ?? null;
            if (! $product || self::fulfillmentType($product) === 'po') {
                continue;
            }

            $requestedByProduct[$productId] = ($requestedByProduct[$productId] ?? 0)
                + max(1, (int) ($item['qty'] ?? 1));
        }

        foreach ($requestedByProduct as $productId => $requestedQty) {
            $product = $productsById[$productId];
            $availableQty = max(0, (int) ($product['stok'] ?? 0));
            $productName = (string) ($product['nama_product'] ?? 'Produk');

            if ($availableQty === 0) {
                return sprintf('Stok produk ready "%s" sedang kosong. Perbarui stok atau gunakan produk PO.', $productName);
            }

            if ($requestedQty > $availableQty) {
                return sprintf(
                    'Stok produk ready "%s" tidak mencukupi. Tersedia %d, diminta %d.',
                    $productName,
                    $availableQty,
                    $requestedQty
                );
            }
        }

        return null;
    }

    private static function fulfillmentType(array $product): string
    {
        return strtolower((string) ($product['stock_type'] ?? 'ready')) === 'po'
            ? 'po'
            : 'ready';
    }

    private function poQuantityForMonth(string $monthStart): int
    {
        return $this->monthlyPoScheduler->quantityForMonth($monthStart);
    }

    //------------------------------
    // SHOW
    //------------------------------
    public function show($id = null)
    {
        if (empty($id)) {
            $latestOrder = $this->orderModel
                ->orderBy('id_order', 'DESC')
                ->first();

            if (!$latestOrder) {
                return redirect()->to('/admin/orders')->with('error', 'Belum ada order untuk ditampilkan.');
            }

            $id = $latestOrder['id_order'];
        }

        $order = $this->orderModel->find($id);

        if (!$order) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Order tidak ditemukan.');
        }

        $data = [

            'title' => 'Detail Order',

            'order' => $order,

            // Item Order
            'items' => $this->itemModel->getItems($id),

            // Riwayat Pembayaran
            'payments' => $this->paymentModel
                ->select('
                    payments.*,
                    payment_methods.nama_metode
                ')
                ->join(
                    'payment_methods',
                    'payment_methods.id_payment_method = payments.id_payment_method',
                    'left'
                )
                ->where('payments.id_order', $id)
                ->orderBy('payments.tanggal_bayar', 'ASC')
                ->findAll(),

            // Total Pembayaran
            'totalPaid' => $this->paymentModel
                ->selectSum('jumlah_bayar')
                ->where('id_order', $id)
                ->where('status', 'verified')
                ->first()['jumlah_bayar'] ?? 0,

            'invoice' => (new \App\Services\InvoiceService())->findByOrder((int) $id),
            'productionSchedules' => $this->monthlyPoScheduler->getOrderSchedule((int) $id)

        ];

        return view('admin/orders/show', $data);
    }

    //------------------------------
    // TEST EMAIL
    //------------------------------
    public function testEmail()
    {
        try {
            $to = session()->get('email') ?: 'test@example.com';
            $email = \Config\Services::email();
            $email->setTo($to);
            $email->setSubject('Test Email Order');
            $email->setMessage('Ini adalah email uji dari sistem order.');
            $email->send();

            return redirect()->back()->with('success', 'Email uji berhasil dikirim.');
        } catch (\Throwable $e) {
            return redirect()->back()->with('error', 'Gagal mengirim email uji: ' . $e->getMessage());
        }
    }

    //------------------------------
    // UPDATE PAYMENT
    //------------------------------
    public function updatePayment($id)
    {
        $order = $this->orderModel->find($id);

        if (!$order) {
            return redirect()->back()->with('error', 'Order tidak ditemukan.');
        }

        $amount = (float) $this->request->getPost('jumlah_bayar');
        $methodId = (int) $this->request->getPost('id_payment_method');

        if ($amount <= 0) {
            return redirect()->back()->withInput()->with('error', 'Jumlah pembayaran harus lebih dari nol.');
        }

        $method = $this->paymentMethodModel->find($methodId);

        if (!$method || !($method['is_active'] ?? false)) {
            return redirect()->back()->withInput()->with('error', 'Metode pembayaran tidak valid atau tidak aktif.');
        }

        $remainingAmount = (float) ($order['remaining_amount'] ?? $order['total'] - ($order['paid_amount'] ?? 0));
        if ($amount > $remainingAmount) {
            return redirect()->back()->withInput()->with('error', 'Jumlah pembayaran tidak boleh melebihi sisa tagihan.');
        }

        $db = \Config\Database::connect();
        $db->transBegin();

        try {
            $this->paymentModel->insert([
                'id_order'          => $id,
                'id_payment_method' => $methodId,
                'tanggal_bayar'     => date('Y-m-d H:i:s'),
                'jumlah_bayar'      => $amount,
                'status'            => 'verified'
            ]);

            $paid = (float) ($this->paymentModel->selectSum('jumlah_bayar')->where('id_order', $id)->where('status', 'verified')->first()['jumlah_bayar'] ?? 0);
            $remaining = max(0, (float) $order['total'] - $paid);
            $paymentStatus = $paid <= 0 ? 'unpaid' : ($paid < $order['total'] ? 'partial' : 'paid');

            $this->orderModel->update($id, [
                'paid_amount'      => $paid,
                'remaining_amount' => $remaining,
                'payment_status'   => $paymentStatus,
                'payment_method'   => $method['kode'] ?? 'cash'
            ]);

            $db->transCommit();

            return redirect()->to('/admin/orders/show/' . $id)->with('success', 'Pembayaran berhasil ditambahkan.');
        } catch (\Throwable $e) {
            $db->transRollback();
            return redirect()->back()->withInput()->with('error', $e->getMessage());
        }
    }

    //------------------------------
    // DELETE
    //------------------------------
    public function delete($id)
    {
        $order = $this->orderModel->find($id);

        if (!$order) {

            return redirect()
                ->to('/admin/orders')
                ->with('error', 'Order tidak ditemukan.');

        }

        $db = \Config\Database::connect();

        $db->transBegin();

        try {

            // Hapus semua pembayaran
            $this->paymentModel
                ->where('id_order', $id)
                ->delete();

            // Hapus semua item order
            $this->itemModel
                ->where('id_order', $id)
                ->delete();

            // Hapus order
            $this->orderModel
                ->delete($id);

            $db->transCommit();

            return redirect()
                ->to('/admin/orders')
                ->with('success', 'Order berhasil dihapus.');

        } catch (\Exception $e) {

            $db->transRollback();

            return redirect()
                ->back()
                ->with('error', $e->getMessage());

        }
    }

    //------------------------------
    // EDIT
    //------------------------------
    public function edit($id)
    {
        $order = $this->orderModel->find($id);

        if (!$order) {

            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(
                'Order tidak ditemukan.'
            );

        }

        $data = [

            'title' => 'Edit Order',

            // Data Order
            'order' => $order,

            // Detail Item
            'items' => $this->itemModel->getItems($id),

            // Produk Aktif
            'products' => $this->productModel
                ->where('status', 'active')
                ->orderBy('nama_product', 'ASC')
                ->findAll(),

            // Metode Pembayaran
            'paymentMethods' => $this->paymentMethodModel
                ->active(),

            'productionSchedules' => $this->monthlyPoScheduler->getOrderSchedule((int) $id),
            'monthlyPoLimit' => (int) (($this->settingModel->first()['monthly_po_limit'] ?? 0))

        ];

        return view('admin/orders/edit', $data);
    }

    //------------------------------
    // UPDATE
    //------------------------------
    public function update($id)
    {
        $order = $this->orderModel->find($id);

        if (!$order) {

            return redirect()
                ->back()
                ->with('error', 'Order tidak ditemukan.');

        }

        if (!$this->validate([
            'customer_name' => 'required'
        ])) {

            return redirect()
                ->back()
                ->withInput()
                ->with('errors', $this->validator->getErrors());

        }

        $products = $this->request->getPost('product');
        $qtys = $this->request->getPost('qty');
        $prices = $this->request->getPost('price');

        if (empty($products) || empty($qtys) || empty($prices)) {
            return redirect()
                ->back()
                ->withInput()
                ->with('error', 'Order harus memiliki setidaknya satu item.');
        }

        $items = self::normalizeOrderItems($this->request->getPost());
        $selectedProducts = [];
        foreach ($items as $item) {
            if ($item['is_custom']) {
                continue;
            }

            $product = $this->productModel->find($item['id_product']);
            if (! $product) {
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('error', 'Produk yang dipilih tidak ditemukan.');
            }

            $selectedProducts[(string) $item['id_product']] = $product;
        }

        $orderType = $this->request->getPost('order_type') ?: 'ready';
        $automaticallyChangedToPo = $orderType !== 'po'
            && self::requiresPreOrder($items, $selectedProducts);

        if ($automaticallyChangedToPo) {
            $orderType = 'po';
        }

        $total = array_sum(array_column($items, 'subtotal'));

        if ($total <= 0) {
            return redirect()
                ->back()
                ->withInput()
                ->with('error', 'Total order harus lebih besar dari 0.');
        }

        $db = \Config\Database::connect();

        $orderStatus = (string) $this->request->getPost('order_status');
        if (!in_array($orderStatus, ['waiting_payment', 'production', 'completed', 'cancelled'], true)) {
            $orderStatus = (string) ($order['order_status'] ?? 'waiting_payment');
        }

        $requestedTargetDate = $this->request->getPost('target_date') ?: ($order['target_date'] ?? date('Y-m-d'));
        $monthlyPoLimit = (int) (($this->settingModel->first()['monthly_po_limit'] ?? 0));

        $db->transBegin();

        try {

            $paid = (float) $order['paid_amount'];

            $remaining = $total - $paid;

            if ($remaining < 0) {
                $remaining = 0;
            }

            if ($paid <= 0) {

                $paymentStatus = 'unpaid';

            } elseif ($paid < $total) {

                $paymentStatus = 'partial';

            } else {

                $paymentStatus = 'paid';

            }

            $this->orderModel->update($id, [

                'customer_name'    => $this->request->getPost('customer_name'),

                'customer_phone'   => $this->request->getPost('customer_phone'),

                'customer_email'   => $this->request->getPost('customer_email'),

                'order_type'       => $orderType,

                'total'            => $total,

                'remaining_amount' => $remaining,

                'payment_status'   => $paymentStatus,

                'order_status'     => $orderStatus,

                'target_date'      => $requestedTargetDate,

                'notes'            => $this->request->getPost('notes')

            ]);


            /*
            |--------------------------------------------------------------------------
            | Hapus Item Lama
            |--------------------------------------------------------------------------
            */

            $this->itemModel
                ->where('id_order', $id)
                ->delete();


            /*
            |--------------------------------------------------------------------------
            | Simpan Item Baru
            |--------------------------------------------------------------------------
            */

            $products     = $this->request->getPost('product');
            $names        = $this->request->getPost('product_name');
            $descriptions = $this->request->getPost('description');
            $qtys         = $this->request->getPost('qty');
            $prices       = $this->request->getPost('price');

            if ($products) {

                foreach ($products as $i => $productId) {

                    $isCustom = empty($productId) || strpos((string)$productId, 'custom') !== false;

                    if ($isCustom) {

                        $this->itemModel->insert([

                            'id_order'     => $id,

                            'id_product'   => null,

                            'product_name' => $names[$i],

                            'description'  => $descriptions[$i] ?? null,

                            'qty'          => (int) $qtys[$i],

                            'price'        => (float) $prices[$i],

                            'subtotal'     => (float) $qtys[$i] * (float) $prices[$i]

                        ]);

                    } else {

                        $product = $this->productModel->find($productId);

                        if (!$product) {
                            continue;
                        }

                        $this->itemModel->insert([

                            'id_order'     => $id,

                            'id_product'   => $productId,

                            'product_name' => $product['nama_product'],

                            'description'  => $product['description'],

                            'qty'          => (int) $qtys[$i],

                            'price'        => (float) $prices[$i],

                            'subtotal'     => (float) $qtys[$i] * (float) $prices[$i]

                        ]);

                    }

                }

            }

            $schedule = [];
            if ($orderType === 'po' && $orderStatus !== 'cancelled') {
                $schedule = $this->monthlyPoScheduler->replaceOrderSchedule(
                    (int) $id,
                    array_sum(array_column($items, 'qty')),
                    $monthlyPoLimit,
                    $requestedTargetDate
                );

                $rebalancedSchedules = $this->monthlyPoScheduler->rebalance($monthlyPoLimit);
                $schedule = $rebalancedSchedules[(int) $id] ?? $this->monthlyPoScheduler->getOrderSchedule((int) $id);

                if (! $this->orderModel->update($id, [
                    'target_date' => $schedule[0]['scheduled_month'] ?? $requestedTargetDate,
                ])) {
                    throw new \RuntimeException('Gagal memperbarui target jadwal PO.');
                }
            } else {
                // Ready stock dan order dibatalkan tidak boleh lagi mengambil
                // kuota produksi PO.
                $this->monthlyPoScheduler->deleteOrderSchedule((int) $id);
                $this->monthlyPoScheduler->rebalance($monthlyPoLimit);
            }

            $db->transCommit();

            $successMessage = $automaticallyChangedToPo
                ? 'Order berhasil diperbarui sebagai Pre-Order karena keranjang berisi produk PO.'
                : 'Order berhasil diperbarui.';

            if (count($schedule) > 1) {
                $successMessage .= ' Produksi dibagi ke ' . count($schedule) . ' bulan sesuai kapasitas bulanan.';
            } elseif ($schedule !== [] && $schedule[0]['scheduled_month'] !== date('Y-m-01', strtotime($requestedTargetDate))) {
                $successMessage .= ' Produksi dijadwalkan ke bulan berikutnya karena kapasitas bulan pilihan sudah penuh.';
            }

            return redirect()
                ->to('/admin/orders/show/' . $id)
                ->with('success', $successMessage);

        } catch (\Exception $e) {

            $db->transRollback();

            return redirect()
                ->back()
                ->withInput()
                ->with('error', $e->getMessage());

        }
    }

}
