<?php

namespace App\Controllers;

use App\Models\PembelianDetailModel;
use App\Models\PembelianModel;
use App\Models\PembelianPembayaranModel;
use App\Models\BarangModel;
use App\Models\SuplierModel;
use Config\Database;
use Dompdf\Dompdf;
use Dompdf\Options;

class PembelianController extends BaseController
{
    private PembelianModel $pembelianModel;
    private PembelianDetailModel $detailModel;
    private PembelianPembayaranModel $pembayaranModel;
    private BarangModel $barangModel;
    private SuplierModel $suplierModel;

    public function __construct()
    {
        $this->pembelianModel = new PembelianModel();
        $this->detailModel      = new PembelianDetailModel();
        $this->pembayaranModel  = new PembelianPembayaranModel();
        $this->barangModel      = new BarangModel();
        $this->suplierModel     = new SuplierModel();
    }

    public function index()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/dashboard', 'Akses ditolak. Pembelian hanya dapat diakses oleh administrator.')) {
            return $redirect;
        }

        $statusPembayaran = (string) $this->request->getGet('status_pembayaran');
        $statusPembayaran = in_array($statusPembayaran, ['lunas', 'hutang'], true) ? $statusPembayaran : '';

        $builder = $this->pembelianModel
            ->select('pembelian.*, suplier.nama AS nama_suplier, user.name AS nama_user, COUNT(pembelian_detail.id) AS total_item, COALESCE(SUM(pembelian_detail.jumlah), 0) AS total_jumlah')
            ->select('COALESCE(NULLIF(pembelian.total_pembelian, 0), SUM(pembelian_detail.subtotal), 0) AS total_harga', false)
            ->select('(SELECT COALESCE(SUM(jumlah), 0) FROM pembelian_pembayaran WHERE pembelian_pembayaran.pembelian_id = pembelian.id) AS total_dibayar', false)
            ->join('suplier', 'suplier.id = pembelian.suplier_id', 'left')
            ->join('user', 'user.id = pembelian.user_id', 'left')
            ->join('pembelian_detail', 'pembelian_detail.pembelian_id = pembelian.id', 'left');

        if ($statusPembayaran !== '') {
            $builder->where('pembelian.status_pembayaran', $statusPembayaran);
        }

        $transaksi = $builder
            ->groupBy('pembelian.id')
            ->orderBy('pembelian.id', 'DESC')
            ->findAll();

        return view('admin/pembelian/index', [
            'transaksi'        => $transaksi,
            'statusPembayaran' => $statusPembayaran,
            'userName'         => session()->get('userName'),
            'userEmail'        => session()->get('userEmail'),
            'userRole'         => session()->get('userRole'),
        ]);
    }

    public function create()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $tanggal = date('Y-m-d');

        return view('admin/pembelian/create', [
            'suplier'       => $this->suplierOptions(),
            'tanggalPembelian'  => $tanggal,
            'noTransaksi'   => $this->generateTransactionNumber($tanggal),
            'initialItems'  => [],
            'pajakPersen'   => $this->defaultTaxPercent(),
            'userName'      => session()->get('userName'),
            'userEmail'     => session()->get('userEmail'),
            'userRole'      => session()->get('userRole'),
        ]);
    }

    public function detail(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $transaksi = $this->pembelianModel
            ->select('pembelian.*, suplier.nama AS nama_suplier, suplier.perusahaan AS perusahaan_suplier, suplier.no_hp AS no_hp_suplier, suplier.email AS email_suplier, user.name AS nama_user')
            ->join('suplier', 'suplier.id = pembelian.suplier_id', 'left')
            ->join('user', 'user.id = pembelian.user_id', 'left')
            ->where('pembelian.id', $id)
            ->first();

        if (! $transaksi) {
            return redirect()->to('/admin/pembelian')->with('error', 'Transaksi pembelian tidak ditemukan.');
        }

        $details = $this->detailModel
            ->where('pembelian_id', $id)
            ->orderBy('id', 'ASC')
            ->findAll();

        return view('admin/pembelian/detail', [
            'transaksi' => $transaksi,
            'details'   => $details,
            'totalDibayar' => $this->paymentTotal($id),
            'userName'  => session()->get('userName'),
            'userEmail' => session()->get('userEmail'),
            'userRole'  => session()->get('userRole'),
        ]);
    }

    public function exportPdf(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $transaksi = $this->pembelianModel
            ->select('pembelian.*, suplier.nama AS nama_suplier, suplier.perusahaan AS perusahaan_suplier, suplier.no_hp AS no_hp_suplier, suplier.email AS email_suplier, suplier.alamat AS alamat_suplier, user.name AS nama_user')
            ->join('suplier', 'suplier.id = pembelian.suplier_id', 'left')
            ->join('user', 'user.id = pembelian.user_id', 'left')
            ->where('pembelian.id', $id)
            ->first();

        if (! $transaksi) {
            return redirect()->to('/admin/pembelian')->with('error', 'Transaksi pembelian tidak ditemukan.');
        }

        $details = $this->detailModel
            ->where('pembelian_id', $id)
            ->orderBy('id', 'ASC')
            ->findAll();

        $options = new Options();
        $options->set('isHtml5ParserEnabled', true);
        $options->set('isRemoteEnabled', false);

        $dompdf = new Dompdf($options);
        $dompdf->loadHtml(view('admin/pembelian/pdf', [
            'transaksi' => $transaksi,
            'details'   => $details,
        ]));
        $dompdf->setPaper('A4', 'portrait');
        $dompdf->render();

        $fileName = 'pembelian-' . preg_replace('/[^A-Za-z0-9_.-]/', '-', $transaksi['no_transaksi']) . '.pdf';

        return $this->response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
            ->setBody($dompdf->output());
    }

    public function store()
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        if (! $this->validate($this->validationRules())) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        $items = $this->validatedItems();

        if ($items === []) {
            return redirect()->back()->withInput()->with('errors', [
                'items' => 'Minimal tambahkan 1 item pembelian.',
            ]);
        }

        $tanggalPembelian = (string) $this->request->getPost('tanggal_pembelian');
        $pajakPersen = $this->defaultTaxPercent();
        $totals = $this->purchaseTotals($items, $pajakPersen);
        $jumlahPembayaran = $this->paymentAmountFromPost();

        if ($jumlahPembayaran > $totals['total']) {
            return redirect()->back()->withInput()->with('errors', [
                'jumlah_pembayaran' => 'Jumlah pembayaran tidak boleh lebih besar dari total pembelian.',
            ]);
        }

        $db = Database::connect();
        $db->transStart();

        $pembelianId = $this->pembelianModel->insert([
            'no_transaksi'  => $this->generateTransactionNumber($tanggalPembelian),
            'tanggal_pembelian' => $tanggalPembelian,
            'suplier_id'    => $this->request->getPost('suplier_id'),
            'nomor_dokumen' => $this->request->getPost('nomor_dokumen') ?: null,
            'subtotal'      => $totals['subtotal'],
            'pajak_persen'  => $pajakPersen,
            'pajak_nominal' => $totals['pajak'],
            'total_pembelian' => $totals['total'],
            'user_id'       => session()->get('userId'),
            'catatan'       => $this->request->getPost('catatan') ?: null,
            'status_pembayaran' => 'hutang',
        ], true);

        $this->saveDetails((int) $pembelianId, $items);
        $this->saveInitialPayment((int) $pembelianId, $tanggalPembelian, $jumlahPembayaran);
        $this->refreshPaymentStatus((int) $pembelianId);
        $this->applyStock($items, 1);
        $this->recalculateMovingAverageCosts($this->itemIds($items));

        $db->transComplete();

        if (! $db->transStatus()) {
            return redirect()->back()->withInput()->with('error', 'Gagal menyimpan transaksi pembelian.');
        }

        return redirect()->to('/admin/pembelian')->with('success', 'Transaksi pembelian berhasil ditambahkan. Harga beli barang diperbarui dengan moving average.');
    }

    public function edit(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $transaksi = $this->pembelianModel->find($id);

        if (! $transaksi) {
            return redirect()->to('/admin/pembelian')->with('error', 'Transaksi pembelian tidak ditemukan.');
        }

        $details = $this->detailModel
            ->where('pembelian_id', $id)
            ->orderBy('id', 'ASC')
            ->findAll();

        return view('admin/pembelian/edit', [
            'transaksi'    => $transaksi,
            'suplier'      => $this->suplierOptions(),
            'initialItems' => $this->detailsForForm($details),
            'totalDibayar' => $this->paymentTotal($id),
            'pajakPersen'   => (int) ($transaksi['pajak_persen'] ?? $this->defaultTaxPercent()),
            'showInitialPayment' => false,
            'userName'     => session()->get('userName'),
            'userEmail'    => session()->get('userEmail'),
            'userRole'     => session()->get('userRole'),
        ]);
    }

    public function update(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $transaksi = $this->pembelianModel->find($id);

        if (! $transaksi) {
            return redirect()->to('/admin/pembelian')->with('error', 'Transaksi pembelian tidak ditemukan.');
        }

        if (! $this->validate($this->validationRules())) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        $items = $this->validatedItems();

        if ($items === []) {
            return redirect()->back()->withInput()->with('errors', [
                'items' => 'Minimal tambahkan 1 item pembelian.',
            ]);
        }

        $oldItems = $this->detailModel
            ->where('pembelian_id', $id)
            ->findAll();
        $stockErrors = $this->stockErrorsAfterDelta($this->incomingStockDeltas($oldItems, $items));

        if ($stockErrors !== []) {
            return redirect()->back()->withInput()->with('errors', $stockErrors);
        }

        $tanggalPembelian = (string) $this->request->getPost('tanggal_pembelian');
        $pajakPersen = (int) ($transaksi['pajak_persen'] ?? $this->defaultTaxPercent());
        $totals = $this->purchaseTotals($items, $pajakPersen);
        $totalDibayar = $this->paymentTotal($id);

        if ($totalDibayar > $totals['total']) {
            return redirect()->back()->withInput()->with('errors', [
                'pembayaran' => 'Total pembayaran yang sudah tercatat tidak boleh lebih besar dari total pembelian. Silakan hapus riwayat pembayaran dulu untuk menyesuaikan jumlah pembayaran pembelian ini.',
            ]);
        }

        $db = Database::connect();
        $db->transStart();

        $this->detailModel->where('pembelian_id', $id)->delete();

        $this->pembelianModel->update($id, [
            'no_transaksi'  => $this->generateTransactionNumber($tanggalPembelian, $id),
            'tanggal_pembelian' => $tanggalPembelian,
            'suplier_id'    => $this->request->getPost('suplier_id'),
            'nomor_dokumen' => $this->request->getPost('nomor_dokumen') ?: null,
            'subtotal'      => $totals['subtotal'],
            'pajak_persen'  => $pajakPersen,
            'pajak_nominal' => $totals['pajak'],
            'total_pembelian' => $totals['total'],
            'catatan'       => $this->request->getPost('catatan') ?: null,
        ]);

        $this->saveDetails($id, $items);
        $this->refreshPaymentStatus($id);
        $affectedBarangIds = array_unique(array_merge($this->itemIds($oldItems), $this->itemIds($items)));
        $this->applyStockDeltas($this->incomingStockDeltas($oldItems, $items));
        $this->recalculateMovingAverageCosts($affectedBarangIds);

        $db->transComplete();

        if (! $db->transStatus()) {
            return redirect()->back()->withInput()->with('error', 'Gagal memperbarui transaksi pembelian.');
        }

        return redirect()->to('/admin/pembelian')->with('success', 'Transaksi pembelian berhasil diperbarui. Harga beli barang dihitung ulang dengan moving average.');
    }

    public function delete(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $transaksi = $this->pembelianModel->find($id);

        if (! $transaksi) {
            return redirect()->to('/admin/pembelian')->with('error', 'Transaksi pembelian tidak ditemukan.');
        }

        $items = $this->detailModel->where('pembelian_id', $id)->findAll();
        $affectedBarangIds = $this->itemIds($items);
        $stockErrors = $this->stockErrorsAfterDelta($this->incomingStockDeltas($items, []));

        if ($stockErrors !== []) {
            return redirect()->to('/admin/pembelian')->with('error', implode(' ', $stockErrors));
        }

        $db = Database::connect();
        $db->transStart();

        $this->applyStockDeltas($this->incomingStockDeltas($items, []));
        $this->pembelianModel->delete($id);
        $this->recalculateMovingAverageCosts($affectedBarangIds);

        $db->transComplete();

        if (! $db->transStatus()) {
            return redirect()->to('/admin/pembelian')->with('error', 'Gagal menghapus transaksi pembelian.');
        }

        return redirect()->to('/admin/pembelian')->with('success', 'Transaksi pembelian berhasil dihapus. Harga beli barang dihitung ulang dengan moving average.');
    }

    public function cicilan(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $transaksi = $this->pembelianWithSupplier($id);

        if (! $transaksi) {
            return redirect()->to('/admin/pembelian')->with('error', 'Transaksi pembelian tidak ditemukan.');
        }

        $totalPembelian = $this->purchaseTotal($id);
        $totalDibayar = $this->paymentTotal($id);

        return view('admin/pembelian/cicilan', [
            'transaksi'      => $transaksi,
            'payments'       => $this->payments($id),
            'totalPembelian' => $totalPembelian,
            'totalDibayar'   => $totalDibayar,
            'sisaHutang'     => max(0, $totalPembelian - $totalDibayar),
            'userName'       => session()->get('userName'),
            'userEmail'      => session()->get('userEmail'),
            'userRole'       => session()->get('userRole'),
        ]);
    }

    public function storeCicilan(int $id)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $transaksi = $this->pembelianModel->find($id);

        if (! $transaksi) {
            return redirect()->to('/admin/pembelian')->with('error', 'Transaksi pembelian tidak ditemukan.');
        }

        $rules = [
            'tanggal_pembayaran' => 'required|valid_date[Y-m-d]',
            'jumlah_setoran'     => 'required|max_length[30]',
            'catatan_pembayaran' => 'permit_empty|max_length[1000]',
        ];

        if (! $this->validate($rules)) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        $jumlahSetoran = $this->paymentAmountFromPost('jumlah_setoran');

        if ($jumlahSetoran < 1) {
            return redirect()->back()->withInput()->with('errors', [
                'jumlah_setoran' => 'Jumlah setoran wajib lebih dari 0.',
            ]);
        }

        $totalPembelian = $this->purchaseTotal($id);
        $totalDibayar = $this->paymentTotal($id);

        if (($totalDibayar + $jumlahSetoran) > $totalPembelian) {
            return redirect()->back()->withInput()->with('errors', [
                'jumlah_setoran' => 'Jumlah setoran tidak boleh lebih besar dari sisa hutang.',
            ]);
        }

        $db = Database::connect();
        $db->transStart();

        $this->pembayaranModel->insert([
            'pembelian_id'         => $id,
            'tanggal_pembayaran'   => $this->request->getPost('tanggal_pembayaran'),
            'jumlah'               => $jumlahSetoran,
            'is_pembayaran_awal'   => 0,
            'catatan'              => $this->request->getPost('catatan_pembayaran') ?: null,
            'user_id'              => session()->get('userId'),
        ]);

        $this->refreshPaymentStatus($id);

        $db->transComplete();

        if (! $db->transStatus()) {
            return redirect()->back()->withInput()->with('error', 'Gagal menyimpan cicilan pembelian.');
        }

        return redirect()->to('/admin/pembelian/cicilan/' . $id)->with('success', 'Cicilan pembelian berhasil disimpan.');
    }

    public function deleteCicilan(int $id, int $paymentId)
    {
        if (! $this->isLoggedIn()) {
            return redirect()->to('/login')->with('error', 'Silakan login terlebih dahulu.');
        }

        if ($redirect = $this->requireAdministrator('/admin/pembelian')) {
            return $redirect;
        }

        $transaksi = $this->pembelianModel->find($id);

        if (! $transaksi) {
            return redirect()->to('/admin/pembelian')->with('error', 'Transaksi pembelian tidak ditemukan.');
        }

        $payment = $this->pembayaranModel
            ->where('pembelian_id', $id)
            ->where('id', $paymentId)
            ->first();

        if (! $payment) {
            return redirect()->to('/admin/pembelian/cicilan/' . $id)->with('error', 'Pembayaran tidak ditemukan.');
        }

        $lastPayment = $this->lastPayment($id);

        if (! $lastPayment || (int) $lastPayment['id'] !== (int) $payment['id']) {
            return redirect()->to('/admin/pembelian/cicilan/' . $id)->with('error', 'Hanya pembayaran terakhir yang dapat dihapus.');
        }

        $db = Database::connect();
        $db->transStart();

        $this->pembayaranModel->delete($paymentId);
        $this->refreshPaymentStatus($id);

        $db->transComplete();

        if (! $db->transStatus()) {
            return redirect()->to('/admin/pembelian/cicilan/' . $id)->with('error', 'Gagal menghapus pembayaran.');
        }

        return redirect()->to('/admin/pembelian/cicilan/' . $id)->with('success', 'Pembayaran terakhir berhasil dihapus.');
    }

    public function generateNumber()
    {
        if (! $this->isLoggedIn() || ! $this->isAdministratorRole()) {
            return $this->response->setStatusCode(403)->setJSON(['message' => 'Akses ditolak.']);
        }

        $tanggal = (string) ($this->request->getGet('tanggal_pembelian') ?: date('Y-m-d'));

        if (! $this->isValidDate($tanggal)) {
            return $this->response->setStatusCode(422)->setJSON(['message' => 'Tanggal tidak valid.']);
        }

        return $this->response->setJSON([
            'no_transaksi' => $this->generateTransactionNumber($tanggal),
        ]);
    }

    public function lookupBarang()
    {
        if (! $this->isLoggedIn() || ! $this->isAdministratorRole()) {
            return $this->response->setStatusCode(403)->setJSON(['message' => 'Akses ditolak.']);
        }

        $kode = trim((string) $this->request->getGet('kode_barang'));

        if ($kode === '') {
            return $this->response->setStatusCode(422)->setJSON(['message' => 'Kode barang wajib diisi.']);
        }

        $barang = $this->barangModel
            ->select('barang.id, barang.kode_barang, barang.nama_barang, barang.satuan, barang.stok, barang.harga_beli, kategori.nama_kategori')
            ->join('kategori', 'kategori.id = barang.kategori_id', 'left')
            ->where('barang.kode_barang', strtoupper($kode))
            ->first();

        if (! $barang) {
            return $this->response->setStatusCode(404)->setJSON(['message' => 'Barang tidak ditemukan.']);
        }

        return $this->response->setJSON(['barang' => $barang]);
    }

    public function barangDataTable()
    {
        if (! $this->isLoggedIn() || ! $this->isAdministratorRole()) {
            return $this->response->setStatusCode(403)->setJSON(['message' => 'Akses ditolak.']);
        }

        $draw = (int) ($this->request->getGet('draw') ?? 1);
        $start = max(0, (int) ($this->request->getGet('start') ?? 0));
        $length = (int) ($this->request->getGet('length') ?? 10);
        $length = $length > 0 ? min($length, 100) : 10;
        $search = trim((string) ($this->request->getGet('search')['value'] ?? ''));

        $db = Database::connect();
        $recordsTotal = $db->table('barang')->countAllResults();
        $filteredBuilder = $this->barangModel
            ->select('barang.id, barang.kode_barang, barang.nama_barang, barang.satuan, barang.stok, barang.harga_beli, kategori.nama_kategori')
            ->join('kategori', 'kategori.id = barang.kategori_id', 'left');

        if ($search !== '') {
            $filteredBuilder
                ->groupStart()
                ->like('barang.kode_barang', $search)
                ->orLike('barang.nama_barang', $search)
                ->orLike('kategori.nama_kategori', $search)
                ->groupEnd();
        }

        $recordsFiltered = $filteredBuilder->countAllResults(false);
        $data = $filteredBuilder
            ->orderBy('barang.nama_barang', 'ASC')
            ->findAll($length, $start);

        $rows = array_map(static function (array $item): array {
            return [
                esc($item['kode_barang']),
                esc($item['nama_barang']),
                esc($item['nama_kategori'] ?? '-'),
                esc($item['satuan']),
                esc((string) ($item['stok'] ?? 0)),
                '<button type="button" class="btn btn-sm btn-dark pilih-barang-btn" data-id="' . esc($item['id'], 'attr') . '" data-kode="' . esc($item['kode_barang'], 'attr') . '" data-nama="' . esc($item['nama_barang'], 'attr') . '" data-satuan="' . esc($item['satuan'], 'attr') . '" data-stok="' . esc((string) ($item['stok'] ?? 0), 'attr') . '" data-harga-beli="' . esc((string) ($item['harga_beli'] ?? 0), 'attr') . '"><i class="bi bi-check2"></i>Pilih</button>',
            ];
        }, $data);

        return $this->response->setJSON([
            'draw'            => $draw,
            'recordsTotal'    => $recordsTotal,
            'recordsFiltered' => $recordsFiltered,
            'data'            => $rows,
        ]);
    }

    private function validationRules(): array
    {
        return [
            'tanggal_pembelian' => 'required|valid_date[Y-m-d]',
            'suplier_id'    => 'required|is_not_unique[suplier.id]',
            'nomor_dokumen' => 'permit_empty|max_length[100]',
            'pajak_persen'  => 'required|integer|greater_than_equal_to[0]|less_than_equal_to[100]',
            'jumlah_pembayaran' => 'permit_empty|max_length[30]',
            'catatan'       => 'permit_empty|max_length[1000]',
        ];
    }

    private function validatedItems(): array
    {
        $rawItems = json_decode((string) $this->request->getPost('items_json'), true);

        if (! is_array($rawItems)) {
            return [];
        }

        $items = [];

        foreach ($rawItems as $rawItem) {
            $barangId = (int) ($rawItem['barang_id'] ?? 0);
            $jumlah = (int) ($rawItem['jumlah'] ?? 0);
            $hargaBeli = (int) round((float) ($rawItem['harga_beli'] ?? -1));

            if ($barangId < 1 || $jumlah < 1 || $hargaBeli < 0) {
                continue;
            }

            $barang = $this->barangModel->find($barangId);

            if (! $barang) {
                continue;
            }

            $previousJumlah = (int) ($items[$barangId]['jumlah'] ?? 0);
            $previousHargaBeli = (int) ($items[$barangId]['harga_beli'] ?? 0);
            $newJumlah = $previousJumlah + $jumlah;
            $newHargaBeli = $previousJumlah > 0
                ? (($previousJumlah * $previousHargaBeli) + ($jumlah * $hargaBeli)) / $newJumlah
                : $hargaBeli;

            $items[$barangId] = [
                'barang_id'   => $barangId,
                'kode_barang' => $barang['kode_barang'],
                'nama_barang' => $barang['nama_barang'],
                'satuan'      => $barang['satuan'],
                'jumlah'      => $newJumlah,
                'harga_beli'  => (int) round($newHargaBeli),
            ];
        }

        return array_values($items);
    }

    private function saveDetails(int $pembelianId, array $items): void
    {
        foreach ($items as $item) {
            $jumlah = (int) $item['jumlah'];
            $hargaBeli = (int) $item['harga_beli'];

            $this->detailModel->insert([
                'pembelian_id' => $pembelianId,
                'barang_id'       => $item['barang_id'],
                'kode_barang'     => $item['kode_barang'],
                'nama_barang'     => $item['nama_barang'],
                'satuan'          => $item['satuan'],
                'jumlah'          => $jumlah,
                'harga_beli'      => $hargaBeli,
                'subtotal'        => $jumlah * $hargaBeli,
            ]);
        }
    }

    private function applyStock(array $items, int $direction): void
    {
        foreach ($items as $item) {
            $barang = $this->barangModel->find((int) $item['barang_id']);

            if (! $barang) {
                continue;
            }

            $stok = max(0, ((int) ($barang['stok'] ?? 0)) + ((int) $item['jumlah'] * $direction));
            $this->barangModel->update((int) $item['barang_id'], ['stok' => $stok]);
        }
    }

    private function incomingStockDeltas(array $oldItems, array $newItems): array
    {
        $deltas = [];

        foreach ($oldItems as $item) {
            $barangId = (int) $item['barang_id'];
            $deltas[$barangId] = ($deltas[$barangId] ?? 0) - (int) $item['jumlah'];
        }

        foreach ($newItems as $item) {
            $barangId = (int) $item['barang_id'];
            $deltas[$barangId] = ($deltas[$barangId] ?? 0) + (int) $item['jumlah'];
        }

        return array_filter($deltas, static fn (int $delta): bool => $delta !== 0);
    }

    private function stockErrorsAfterDelta(array $deltas): array
    {
        $errors = [];

        foreach ($deltas as $barangId => $delta) {
            $barang = $this->barangModel->find((int) $barangId);

            if (! $barang) {
                continue;
            }

            $currentStock = (int) ($barang['stok'] ?? 0);
            $newStock = $currentStock + (int) $delta;

            if ($newStock < 0) {
                $errors[] = 'Transaksi tidak dapat diproses karena stok ' . $barang['kode_barang'] . ' - ' . $barang['nama_barang'] . ' akan menjadi negatif. Stok saat ini ' . $currentStock . '.';
            }
        }

        return $errors;
    }

    private function applyStockDeltas(array $deltas): void
    {
        foreach ($deltas as $barangId => $delta) {
            $barang = $this->barangModel->find((int) $barangId);

            if (! $barang) {
                continue;
            }

            $this->barangModel->update((int) $barangId, [
                'stok' => (int) ($barang['stok'] ?? 0) + (int) $delta,
            ]);
        }
    }

    private function detailsForForm(array $details): array
    {
        return array_map(function (array $detail): array {
            $barang = $this->barangModel->find((int) $detail['barang_id']);

            return [
                'barang_id'   => (int) $detail['barang_id'],
                'kode_barang' => $detail['kode_barang'],
                'nama_barang' => $detail['nama_barang'],
                'satuan'      => $detail['satuan'],
                'jumlah'      => (int) $detail['jumlah'],
                'harga_beli'  => (int) ($detail['harga_beli'] ?? 0),
                'stok_saat_ini' => (int) ($barang['stok'] ?? 0),
                'harga_beli_saat_ini' => (int) ($barang['harga_beli'] ?? $detail['harga_beli'] ?? 0),
            ];
        }, $details);
    }

    private function itemIds(array $items): array
    {
        return array_values(array_unique(array_map(static fn (array $item): int => (int) $item['barang_id'], $items)));
    }

    private function itemsTotal(array $items): int
    {
        $total = 0;

        foreach ($items as $item) {
            $total += (int) $item['jumlah'] * (int) $item['harga_beli'];
        }

        return $total;
    }

    private function purchaseTotals(array $items, int $pajakPersen): array
    {
        $subtotal = $this->itemsTotal($items);
        $pajak = (int) round($subtotal * $pajakPersen / 100);

        return [
            'subtotal' => $subtotal,
            'pajak'    => $pajak,
            'total'    => $subtotal + $pajak,
        ];
    }

    private function defaultTaxPercent(): int
    {
        $setting = function_exists('app_setting') ? app_setting() : [];

        return max(0, min(100, (int) ($setting['pajak_persen'] ?? 11)));
    }

    private function paymentAmountFromPost(string $field = 'jumlah_pembayaran'): int
    {
        $value = (string) $this->request->getPost($field);
        $digits = preg_replace('/\D/', '', $value) ?: '0';

        return max(0, (int) $digits);
    }

    private function saveInitialPayment(int $pembelianId, string $tanggalPembelian, int $amount): void
    {
        $existing = $this->pembayaranModel
            ->where('pembelian_id', $pembelianId)
            ->where('is_pembayaran_awal', 1)
            ->orderBy('id', 'ASC')
            ->first();

        if ($amount < 1) {
            if ($existing) {
                $this->pembayaranModel->delete((int) $existing['id']);
            }

            return;
        }

        $data = [
            'pembelian_id'       => $pembelianId,
            'tanggal_pembayaran' => $tanggalPembelian,
            'jumlah'             => $amount,
            'is_pembayaran_awal' => 1,
            'catatan'            => 'Pembayaran awal',
            'user_id'            => session()->get('userId'),
        ];

        if ($existing) {
            $this->pembayaranModel->update((int) $existing['id'], $data);
            return;
        }

        $this->pembayaranModel->insert($data);
    }

    private function initialPaymentAmount(int $pembelianId): int
    {
        $payment = $this->pembayaranModel
            ->where('pembelian_id', $pembelianId)
            ->where('is_pembayaran_awal', 1)
            ->orderBy('id', 'ASC')
            ->first();

        return (int) ($payment['jumlah'] ?? 0);
    }

    private function purchaseTotal(int $pembelianId): int
    {
        $pembelian = $this->pembelianModel->find($pembelianId);

        if ($pembelian && array_key_exists('total_pembelian', $pembelian) && (int) $pembelian['total_pembelian'] > 0) {
            return (int) $pembelian['total_pembelian'];
        }

        $row = Database::connect()
            ->table('pembelian_detail')
            ->select('COALESCE(SUM(subtotal), 0) AS total', false)
            ->where('pembelian_id', $pembelianId)
            ->get()
            ->getRowArray();

        return (int) ($row['total'] ?? 0);
    }

    private function paymentTotal(int $pembelianId, bool $includeInitial = true): int
    {
        $builder = Database::connect()
            ->table('pembelian_pembayaran')
            ->select('COALESCE(SUM(jumlah), 0) AS total', false)
            ->where('pembelian_id', $pembelianId);

        if (! $includeInitial) {
            $builder->where('is_pembayaran_awal', 0);
        }

        $row = $builder->get()->getRowArray();

        return (int) ($row['total'] ?? 0);
    }

    private function payments(int $pembelianId): array
    {
        return $this->pembayaranModel
            ->select('pembelian_pembayaran.*, user.name AS nama_user')
            ->join('user', 'user.id = pembelian_pembayaran.user_id', 'left')
            ->where('pembelian_id', $pembelianId)
            ->orderBy('tanggal_pembayaran', 'ASC')
            ->orderBy('id', 'ASC')
            ->findAll();
    }

    private function lastPayment(int $pembelianId): ?array
    {
        return $this->pembayaranModel
            ->where('pembelian_id', $pembelianId)
            ->orderBy('tanggal_pembayaran', 'DESC')
            ->orderBy('id', 'DESC')
            ->first();
    }

    private function refreshPaymentStatus(int $pembelianId): void
    {
        $totalPembelian = $this->purchaseTotal($pembelianId);
        $totalDibayar = $this->paymentTotal($pembelianId);
        $status = $totalPembelian > 0 && $totalDibayar >= $totalPembelian ? 'lunas' : 'hutang';

        $this->pembelianModel->update($pembelianId, [
            'status_pembayaran' => $status,
        ]);
    }

    private function pembelianWithSupplier(int $id): ?array
    {
        return $this->pembelianModel
            ->select('pembelian.*, suplier.nama AS nama_suplier, suplier.perusahaan AS perusahaan_suplier, user.name AS nama_user')
            ->join('suplier', 'suplier.id = pembelian.suplier_id', 'left')
            ->join('user', 'user.id = pembelian.user_id', 'left')
            ->where('pembelian.id', $id)
            ->first();
    }

    private function recalculateMovingAverageCosts(array $barangIds): void
    {
        foreach ($barangIds as $barangId) {
            $this->recalculateMovingAverageCost((int) $barangId);
        }
    }

    private function recalculateMovingAverageCost(int $barangId): void
    {
        $barang = $this->barangModel->find($barangId);

        if (! $barang) {
            return;
        }

        $stock = max(0, (int) ($barang['stok_awal'] ?? 0));
        $averageCost = (int) ($barang['harga_beli_awal'] ?? $barang['harga_beli'] ?? 0);
        $details = Database::connect()
            ->table('pembelian_detail bmd')
            ->select('bmd.jumlah, bmd.harga_beli')
            ->join('pembelian bm', 'bm.id = bmd.pembelian_id', 'inner')
            ->where('bmd.barang_id', $barangId)
            ->orderBy('bm.tanggal_pembelian', 'ASC')
            ->orderBy('bm.id', 'ASC')
            ->orderBy('bmd.id', 'ASC')
            ->get()
            ->getResultArray();

        foreach ($details as $detail) {
            $jumlah = max(0, (int) ($detail['jumlah'] ?? 0));
            $hargaBeli = max(0, (int) ($detail['harga_beli'] ?? 0));

            if ($jumlah < 1) {
                continue;
            }

            $newStock = $stock + $jumlah;
            $averageCost = $newStock > 0
                ? (($stock * $averageCost) + ($jumlah * $hargaBeli)) / $newStock
                : $hargaBeli;
            $stock = $newStock;
        }

        $this->barangModel->update($barangId, [
            'harga_beli' => (int) round($averageCost),
        ]);
    }

    private function suplierOptions(): array
    {
        return $this->suplierModel
            ->orderBy('nama', 'ASC')
            ->findAll();
    }

    private function generateTransactionNumber(string $tanggal, ?int $excludeId = null): string
    {
        $date = date_create($tanggal) ?: date_create();
        $prefix = $date->format('Ymd') . '/PBL';
        $builder = $this->pembelianModel
            ->select('no_transaksi')
            ->like('no_transaksi', $prefix . '/', 'after')
            ->orderBy('no_transaksi', 'DESC');

        if ($excludeId !== null) {
            $builder->where('id !=', $excludeId);
        }

        $last = $builder->first();
        $nextNumber = 1;

        if ($last && preg_match('/\/(\d+)$/', $last['no_transaksi'], $matches)) {
            $nextNumber = ((int) $matches[1]) + 1;
        }

        return $prefix . '/' . str_pad((string) $nextNumber, 3, '0', STR_PAD_LEFT);
    }

    private function isValidDate(string $date): bool
    {
        $parsed = date_create_from_format('Y-m-d', $date);

        return $parsed !== false && $parsed->format('Y-m-d') === $date;
    }

    private function isLoggedIn(): bool
    {
        return (bool) session()->get('isLoggedIn') && $this->isUserAccount();
    }
}
