<?php

namespace App\Controllers;

use App\Models\BarangModel;
use App\Models\JasaModel;
use App\Models\PelangganModel;
use App\Models\ServiceBarangModel;
use App\Models\ServiceJasaModel;
use App\Models\ServiceModel;
use App\Models\ServicePembayaranModel;
use App\Models\TeknisiModel;
use Config\Database;
use Dompdf\Dompdf;
use Dompdf\Options;

class ServiceController extends BaseController
{
    private ServiceModel $serviceModel;
    private ServiceBarangModel $serviceBarangModel;
    private ServiceJasaModel $serviceJasaModel;
    private ServicePembayaranModel $pembayaranModel;
    private BarangModel $barangModel;
    private JasaModel $jasaModel;
    private PelangganModel $pelangganModel;
    private TeknisiModel $teknisiModel;

    public function __construct()
    {
        $this->serviceModel       = new ServiceModel();
        $this->serviceBarangModel = new ServiceBarangModel();
        $this->serviceJasaModel   = new ServiceJasaModel();
        $this->pembayaranModel    = new ServicePembayaranModel();
        $this->barangModel        = new BarangModel();
        $this->jasaModel          = new JasaModel();
        $this->pelangganModel     = new PelangganModel();
        $this->teknisiModel       = new TeknisiModel();
    }

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

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

        $filters = [
            'keyword'           => trim((string) $this->request->getGet('keyword')),
            'status_service'    => (string) $this->request->getGet('status_service'),
            'status_pembayaran' => (string) $this->request->getGet('status_pembayaran'),
        ];

        if (! array_key_exists($filters['status_service'], $this->statusLabels())) {
            $filters['status_service'] = '';
        }

        if (! in_array($filters['status_pembayaran'], ['lunas', 'piutang'], true)) {
            $filters['status_pembayaran'] = '';
        }

        $query = $this->serviceModel
            ->select('service.*, pelanggan.nama AS nama_pelanggan, teknisi.nama AS nama_teknisi, user.name AS nama_user')
            ->select('(SELECT COALESCE(SUM(jumlah), 0) FROM service_barang WHERE service_barang.service_id = service.id) AS total_part', false)
            ->select('(SELECT COUNT(*) FROM service_jasa WHERE service_jasa.service_id = service.id) AS total_jasa', false)
            ->select('(SELECT COALESCE(SUM(jumlah), 0) FROM service_pembayaran WHERE service_pembayaran.service_id = service.id) AS total_dibayar', false)
            ->join('pelanggan', 'pelanggan.id = service.pelanggan_id', 'left')
            ->join('teknisi', 'teknisi.id = service.teknisi_id', 'left')
            ->join('user', 'user.id = service.user_id', 'left');

        if ($filters['status_service'] !== '') {
            $query->where('service.status_service', $filters['status_service']);
        }

        if ($filters['status_pembayaran'] !== '') {
            $query->where('service.status_pembayaran', $filters['status_pembayaran']);
        }

        if ($filters['keyword'] !== '') {
            $query
                ->groupStart()
                ->like('service.no_transaksi', $filters['keyword'])
                ->orLike('service.nomor_objek', $filters['keyword'])
                ->orLike('service.merk_tipe', $filters['keyword'])
                ->orLike('service.keterangan', $filters['keyword'])
                ->orLike('pelanggan.nama', $filters['keyword'])
                ->orLike('teknisi.nama', $filters['keyword'])
                ->groupEnd();
        }

        $transaksi = $query
            ->orderBy('service.id', 'DESC')
            ->paginate(6, 'service');

        return view('admin/service/index', [
            'transaksi'    => $transaksi,
            'statusLabels' => $this->statusLabels(),
            'filters'      => $filters,
            'pager'        => $this->serviceModel->pager,
            '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->requireInventoryAccess('/admin/service')) {
            return $redirect;
        }

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

        return view('admin/service/create', [
            'pelanggan'         => $this->pelangganOptions(),
            'teknisi'           => $this->teknisiOptions(),
            'jasaOptions'       => $this->jasaOptions(),
            'statusLabels'      => $this->statusLabels(),
            'tanggalService'    => $tanggal,
            'noTransaksi'       => $this->generateTransactionNumber($tanggal),
            'initialBarang'     => [],
            'initialJasa'       => [],
            'pajakPersen'       => $this->defaultTaxPercent(),
            'showInitialPayment' => true,
            'lookupUrl'         => site_url('admin/service/barang/lookup'),
            'datatableUrl'      => site_url('admin/service/barang/datatable'),
            'generateNumberUrl' => site_url('admin/service/generate-number'),
            '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->requireInventoryAccess('/admin/service')) {
            return $redirect;
        }

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

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

        return view('admin/service/detail', [
            'transaksi'    => $transaksi,
            'barangItems'  => $this->serviceBarangModel->where('service_id', $id)->orderBy('id', 'ASC')->findAll(),
            'jasaItems'    => $this->serviceJasaModel->where('service_id', $id)->orderBy('id', 'ASC')->findAll(),
            'totalDibayar' => $this->paymentTotal($id),
            'statusLabels' => $this->statusLabels(),
            'userName'     => session()->get('userName'),
            'userEmail'    => session()->get('userEmail'),
            'userRole'     => session()->get('userRole'),
        ]);
    }

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

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

        $data = $this->notaData($id);

        if ($data === null) {
            return redirect()->to('/admin/service')->with('error', 'Transaksi service tidak ditemukan.');
        }

        $receiptSize = $this->serviceReceiptSize();

        if (in_array($receiptSize, ['a4', 'continuous_form'], true)) {
            return redirect()->to('/admin/service/nota/pdf/' . $id);
        }

        return view($this->serviceReceiptPrintView($receiptSize), $data);
    }

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

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

        $data = $this->notaData($id);

        if ($data === null) {
            return redirect()->to('/admin/service')->with('error', 'Transaksi service tidak ditemukan.');
        }

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

        $receiptSize = $this->serviceReceiptSize();
        $paper = $this->serviceReceiptPdfPaper($receiptSize);

        $dompdf = new Dompdf($options);
        $dompdf->loadHtml(view($this->serviceReceiptPdfView($receiptSize), $data));
        $dompdf->setPaper($paper['size'], $paper['orientation']);
        $dompdf->render();

        $fileName = 'nota-service-' . preg_replace('/[^A-Za-z0-9_.-]/', '-', (string) $data['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->requireInventoryAccess('/admin/service')) {
            return $redirect;
        }

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

        $barangItems = $this->validatedBarangItems();
        $jasaItems = $this->validatedJasaItems();
        $stockErrors = $this->stockErrorsAfterDelta($this->outgoingStockDeltas([], $barangItems));

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

        $tanggalService = (string) $this->request->getPost('tanggal_service');
        $pajakPersen = $this->defaultTaxPercent();
        $totals = $this->serviceTotals($barangItems, $jasaItems, $pajakPersen);
        $jumlahPembayaran = $this->paymentAmountFromPost();

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

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

        $serviceId = $this->serviceModel->insert([
            'no_transaksi'     => $this->generateTransactionNumber($tanggalService),
            'tanggal_service'  => $tanggalService,
            'pelanggan_id'     => $this->request->getPost('pelanggan_id'),
            'teknisi_id'       => $this->request->getPost('teknisi_id'),
            'nomor_objek'      => $this->request->getPost('nomor_objek') ?: null,
            'merk_tipe'        => $this->request->getPost('merk_tipe') ?: null,
            'keterangan'       => $this->request->getPost('keterangan') ?: null,
            'status_service'   => $this->statusFromPost(),
            'subtotal_barang'  => $totals['barang'],
            'subtotal_jasa'    => $totals['jasa'],
            'biaya_tambahan'   => 0,
            'pajak_persen'     => $pajakPersen,
            'pajak_nominal'    => $totals['pajak'],
            'total_service'    => $totals['total'],
            'status_pembayaran' => 'piutang',
            'user_id'          => session()->get('userId'),
        ], true);

        $this->saveBarangDetails((int) $serviceId, $barangItems);
        $this->saveJasaDetails((int) $serviceId, $jasaItems);
        $this->saveInitialPayment((int) $serviceId, $tanggalService, $jumlahPembayaran);
        $this->refreshPaymentStatus((int) $serviceId);
        $this->applyStockDeltas($this->outgoingStockDeltas([], $barangItems));

        $db->transComplete();

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

        return redirect()->to('/admin/service')->with('success', 'Transaksi service berhasil ditambahkan.');
    }

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

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

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

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

        $tanggal = (string) ($transaksi['tanggal_service'] ?? date('Y-m-d'));

        return view('admin/service/edit', [
            'transaksi'         => $transaksi,
            'pelanggan'         => $this->pelangganOptions(),
            'teknisi'           => $this->teknisiOptions(),
            'jasaOptions'       => $this->jasaOptions(),
            'statusLabels'      => $this->statusLabels(),
            'initialBarang'     => $this->barangForForm($this->serviceBarangModel->where('service_id', $id)->orderBy('id', 'ASC')->findAll()),
            'initialJasa'       => $this->jasaForForm($this->serviceJasaModel->where('service_id', $id)->orderBy('id', 'ASC')->findAll()),
            'pajakPersen'       => (int) ($transaksi['pajak_persen'] ?? $this->defaultTaxPercent()),
            'totalDibayar'      => $this->paymentTotal($id),
            'showInitialPayment' => false,
            'lookupUrl'         => site_url('admin/service/barang/lookup'),
            'datatableUrl'      => site_url('admin/service/barang/datatable'),
            'generateNumberUrl' => site_url('admin/service/generate-number'),
            'tanggalService'    => $tanggal,
            '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/service')) {
            return $redirect;
        }

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

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

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

        $barangItems = $this->validatedBarangItems();
        $jasaItems = $this->validatedJasaItems();
        $oldBarangItems = $this->serviceBarangModel->where('service_id', $id)->findAll();
        $deltas = $this->outgoingStockDeltas($oldBarangItems, $barangItems);
        $stockErrors = $this->stockErrorsAfterDelta($deltas);

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

        $tanggalService = (string) $this->request->getPost('tanggal_service');
        $pajakPersen = (int) ($transaksi['pajak_persen'] ?? $this->defaultTaxPercent());
        $totals = $this->serviceTotals($barangItems, $jasaItems, $pajakPersen);
        $totalDibayar = $this->paymentTotal($id);

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

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

        $this->serviceBarangModel->where('service_id', $id)->delete();
        $this->serviceJasaModel->where('service_id', $id)->delete();

        $this->serviceModel->update($id, [
            'no_transaksi'     => $this->generateTransactionNumber($tanggalService, $id),
            'tanggal_service'  => $tanggalService,
            'pelanggan_id'     => $this->request->getPost('pelanggan_id'),
            'teknisi_id'       => $this->request->getPost('teknisi_id'),
            'nomor_objek'      => $this->request->getPost('nomor_objek') ?: null,
            'merk_tipe'        => $this->request->getPost('merk_tipe') ?: null,
            'keterangan'       => $this->request->getPost('keterangan') ?: null,
            'status_service'   => $this->statusFromPost(),
            'subtotal_barang'  => $totals['barang'],
            'subtotal_jasa'    => $totals['jasa'],
            'biaya_tambahan'   => 0,
            'pajak_persen'     => $pajakPersen,
            'pajak_nominal'    => $totals['pajak'],
            'total_service'    => $totals['total'],
        ]);

        $this->saveBarangDetails($id, $barangItems);
        $this->saveJasaDetails($id, $jasaItems);
        $this->applyStockDeltas($deltas);
        $this->refreshPaymentStatus($id);

        $db->transComplete();

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

        return redirect()->to('/admin/service')->with('success', 'Transaksi service berhasil diperbarui.');
    }

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

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

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

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

        $barangItems = $this->serviceBarangModel->where('service_id', $id)->findAll();

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

        $this->applyStockDeltas($this->outgoingStockDeltas($barangItems, []));
        $this->serviceModel->delete($id);

        $db->transComplete();

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

        return redirect()->to('/admin/service')->with('success', 'Transaksi service berhasil dihapus dan stok part dikembalikan.');
    }

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

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

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

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

        $totalService = (int) ($transaksi['total_service'] ?? 0);
        $totalDibayar = $this->paymentTotal($id);

        return view('admin/service/cicilan', [
            'transaksi'    => $transaksi,
            'payments'     => $this->payments($id),
            'totalService' => $totalService,
            'totalDibayar' => $totalDibayar,
            'sisaPiutang'  => max(0, $totalService - $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->requireInventoryAccess('/admin/service')) {
            return $redirect;
        }

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

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

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

        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.',
            ]);
        }

        $totalService = (int) ($transaksi['total_service'] ?? 0);
        $totalDibayar = $this->paymentTotal($id);

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

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

        $this->pembayaranModel->insert([
            'service_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 service.');
        }

        return redirect()->to('/admin/service/cicilan/' . $id)->with('success', 'Cicilan service 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->requireInventoryAccess('/admin/service/cicilan/' . $id)) {
            return $redirect;
        }

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

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

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

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

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

        if (! $lastPayment || (int) $lastPayment['id'] !== (int) $payment['id']) {
            return redirect()->to('/admin/service/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/service/cicilan/' . $id)->with('error', 'Gagal menghapus pembayaran.');
        }

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

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

        $tanggal = (string) ($this->request->getGet('tanggal_service') ?: 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->isInventoryRole()) {
            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_jual, 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->isInventoryRole()) {
            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_jual, 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 {
            $stok = (int) ($item['stok'] ?? 0);
            $disabled = $stok <= 0 ? ' disabled' : '';

            return [
                esc($item['kode_barang']),
                esc($item['nama_barang']),
                esc($item['nama_kategori'] ?? '-'),
                esc($item['satuan']),
                esc((string) $stok),
                'Rp ' . esc(number_format((float) ($item['harga_jual'] ?? 0), 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) $stok, 'attr') . '" data-harga-jual="' . esc((string) ($item['harga_jual'] ?? 0), 'attr') . '"' . $disabled . '><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_service' => 'required|valid_date[Y-m-d]',
            'pelanggan_id'    => 'required|is_not_unique[pelanggan.id]',
            'teknisi_id'      => 'required|is_not_unique[teknisi.id]',
            'nomor_objek'     => 'permit_empty|max_length[100]',
            'merk_tipe'       => 'permit_empty|max_length[150]',
            'keterangan'      => 'permit_empty|max_length[2000]',
            'status_service'  => 'required|in_list[menunggu,dalam_pengerjaan,selesai_siap_ambil,selesai_diambil]',
            'jumlah_pembayaran' => 'permit_empty|max_length[30]',
        ];
    }

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

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

        $items = [];

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

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

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

            if (! $barang) {
                continue;
            }

            $previousJumlah = (int) ($items[$barangId]['jumlah'] ?? 0);
            $previousHargaJual = (int) ($items[$barangId]['harga_jual'] ?? 0);
            $newJumlah = $previousJumlah + $jumlah;
            $newHargaJual = $previousJumlah > 0
                ? (($previousJumlah * $previousHargaJual) + ($jumlah * $hargaJual)) / $newJumlah
                : $hargaJual;

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

        return array_values($items);
    }

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

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

        $items = [];

        foreach ($rawItems as $rawItem) {
            $namaJasa = trim((string) ($rawItem['nama_jasa'] ?? ''));
            $biaya = (int) round((float) ($rawItem['biaya'] ?? -1));

            if ($namaJasa === '' || mb_strlen($namaJasa) > 150 || $biaya < 0) {
                continue;
            }

            $items[] = [
                'jasa_id'   => ! empty($rawItem['jasa_id']) ? (int) $rawItem['jasa_id'] : null,
                'nama_jasa' => $namaJasa,
                'biaya'     => $biaya,
            ];
        }

        return $items;
    }

    private function saveBarangDetails(int $serviceId, array $items): void
    {
        foreach ($items as $item) {
            $jumlah = (int) $item['jumlah'];
            $hargaJual = (int) $item['harga_jual'];
            $hargaBeli = (int) ($item['harga_beli'] ?? 0);

            $this->serviceBarangModel->insert([
                'service_id'  => $serviceId,
                'barang_id'   => $item['barang_id'],
                'kode_barang' => $item['kode_barang'],
                'nama_barang' => $item['nama_barang'],
                'satuan'      => $item['satuan'],
                'jumlah'      => $jumlah,
                'harga_jual'  => $hargaJual,
                'harga_beli'  => $hargaBeli,
                'subtotal'    => $jumlah * $hargaJual,
                'total_modal' => $jumlah * $hargaBeli,
            ]);
        }
    }

    private function saveJasaDetails(int $serviceId, array $items): void
    {
        foreach ($items as $item) {
            $this->serviceJasaModel->insert([
                'service_id' => $serviceId,
                'jasa_id'    => $item['jasa_id'],
                'nama_jasa'  => $item['nama_jasa'],
                'biaya'      => $item['biaya'],
            ]);
        }
    }

    private function serviceTotals(array $barangItems, array $jasaItems, int $pajakPersen): array
    {
        $subtotalBarang = 0;
        $subtotalJasa = 0;

        foreach ($barangItems as $item) {
            $subtotalBarang += (int) $item['jumlah'] * (int) $item['harga_jual'];
        }

        foreach ($jasaItems as $item) {
            $subtotalJasa += (int) $item['biaya'];
        }

        $subtotal = $subtotalBarang + $subtotalJasa;
        $pajak = (int) round($subtotal * $pajakPersen / 100);

        return [
            'barang' => $subtotalBarang,
            'jasa'   => $subtotalJasa,
            'pajak'  => $pajak,
            'total'  => $subtotal + $pajak,
        ];
    }

    private function outgoingStockDeltas(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'] . ' tidak cukup. 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 barangForForm(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_jual'  => (int) ($detail['harga_jual'] ?? 0),
                'harga_beli'  => (int) ($detail['harga_beli'] ?? ($barang['harga_beli'] ?? 0)),
                'stok_saat_ini' => (int) ($barang['stok'] ?? 0) + (int) $detail['jumlah'],
            ];
        }, $details);
    }

    private function jasaForForm(array $details): array
    {
        return array_map(static fn (array $detail): array => [
            'jasa_id'   => ! empty($detail['jasa_id']) ? (int) $detail['jasa_id'] : null,
            'nama_jasa' => $detail['nama_jasa'],
            'biaya'     => (int) ($detail['biaya'] ?? 0),
        ], $details);
    }

    private function serviceWithRelations(int $id): ?array
    {
        return $this->serviceModel
            ->select('service.*, pelanggan.nama AS nama_pelanggan, pelanggan.no_hp AS no_hp_pelanggan, pelanggan.alamat AS alamat_pelanggan, teknisi.nama AS nama_teknisi, user.name AS nama_user')
            ->join('pelanggan', 'pelanggan.id = service.pelanggan_id', 'left')
            ->join('teknisi', 'teknisi.id = service.teknisi_id', 'left')
            ->join('user', 'user.id = service.user_id', 'left')
            ->where('service.id', $id)
            ->first();
    }

    private function notaData(int $id): ?array
    {
        $transaksi = $this->serviceWithRelations($id);

        if (! $transaksi) {
            return null;
        }

        return [
            'transaksi'    => $transaksi,
            'barangItems'  => $this->serviceBarangModel->where('service_id', $id)->orderBy('id', 'ASC')->findAll(),
            'jasaItems'    => $this->serviceJasaModel->where('service_id', $id)->orderBy('id', 'ASC')->findAll(),
            'totalDibayar' => $this->paymentTotal($id),
            'statusLabels' => $this->statusLabels(),
            'userName'     => session()->get('userName'),
        ];
    }

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

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

    private function jasaOptions(): array
    {
        return $this->jasaModel->orderBy('nama_jasa', 'ASC')->findAll();
    }

    private function statusLabels(): array
    {
        return [
            'menunggu'            => 'Menunggu',
            'dalam_pengerjaan'    => 'Dalam Pengerjaan',
            'selesai_siap_ambil'  => 'Selesai dan Siap Ambil',
            'selesai_diambil'     => 'Selesai Diambil',
        ];
    }

    private function statusFromPost(): string
    {
        $status = (string) $this->request->getPost('status_service');

        return array_key_exists($status, $this->statusLabels()) ? $status : 'menunggu';
    }

    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 $serviceId, string $tanggalService, int $amount): void
    {
        if ($amount < 1) {
            return;
        }

        $this->pembayaranModel->insert([
            'service_id'           => $serviceId,
            'tanggal_pembayaran'   => $tanggalService,
            'jumlah'               => $amount,
            'is_pembayaran_awal'   => 1,
            'catatan'              => 'Pembayaran awal transaksi service.',
            'user_id'              => session()->get('userId'),
        ]);
    }

    private function paymentTotal(int $serviceId): int
    {
        $row = Database::connect()
            ->table('service_pembayaran')
            ->select('COALESCE(SUM(jumlah), 0) AS total', false)
            ->where('service_id', $serviceId)
            ->get()
            ->getRowArray();

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

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

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

    private function refreshPaymentStatus(int $serviceId): void
    {
        $service = $this->serviceModel->find($serviceId);

        if (! $service) {
            return;
        }

        $totalService = (int) ($service['total_service'] ?? 0);
        $totalDibayar = $this->paymentTotal($serviceId);
        $status = $totalService > 0 && $totalDibayar >= $totalService ? 'lunas' : 'piutang';

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

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

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

    private function serviceReceiptSize(): string
    {
        $setting = function_exists('app_setting') ? app_setting() : [];
        $size = (string) ($setting['nota_penjualan_ukuran'] ?? 'thermal_58');

        return in_array($size, ['thermal_58', 'thermal_80', 'a4', 'continuous_form'], true) ? $size : 'thermal_58';
    }

    private function serviceReceiptPrintView(string $size): string
    {
        return $size === 'thermal_80'
            ? 'admin/service/nota_thermal_80'
            : 'admin/service/nota_thermal_58';
    }

    private function serviceReceiptPdfView(string $size): string
    {
        return match ($size) {
            'thermal_80' => 'admin/service/nota_pdf_thermal_80',
            'a4' => 'admin/service/nota_pdf_a4',
            'continuous_form' => 'admin/service/nota_pdf_continuous_form',
            default => 'admin/service/nota_pdf_thermal_58',
        };
    }

    private function serviceReceiptPdfPaper(string $size): array
    {
        return match ($size) {
            'thermal_80' => ['size' => [0, 0, 226.77, 841.89], 'orientation' => 'portrait'],
            'a4' => ['size' => 'A4', 'orientation' => 'portrait'],
            'continuous_form' => ['size' => [0, 0, 595.28, 396.85], 'orientation' => 'portrait'],
            default => ['size' => [0, 0, 164.41, 841.89], 'orientation' => 'portrait'],
        };
    }

    private function generateTransactionNumber(string $tanggal, ?int $excludeId = null): string
    {
        $date = date_create($tanggal) ?: date_create();
        $prefix = 'SRV' . $date->format('Ymd');
        $builder = $this->serviceModel
            ->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('/^' . preg_quote($prefix, '/') . '(\d+)$/', $last['no_transaksi'], $matches)) {
            $nextNumber = ((int) $matches[1]) + 1;
        }

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

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