<?php

declare(strict_types=1);

namespace App\Services\Admin;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\BaseConnection;
use Config\Database;
use Config\Services;

/**
 * API admin tambahan: master perusahaan, presensi (lapangan/lembur/libur/jadwal/aktivitas), panel Ion, util backup.
 */
class AdminExtraApiService
{
    protected BaseConnection $db;

    public function __construct(?BaseConnection $db = null)
    {
        $this->db = $db ?? Database::connect();
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function kantorList(): array
    {
        $rows = $this->db->table('kantor')->orderBy('nama_kantor', 'ASC')->get()->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function kantorSave(array $input, ?int $id = null): array
    {
        $rules = [
            'kode_kantor'            => 'required|max_length[100]',
            'nama_kantor'            => 'required|max_length[50]',
            'alamat_kantor'          => 'required|max_length[255]',
            'tipe'                   => 'required|max_length[50]',
            'lat'                    => 'required',
            'lng'                    => 'required',
            'jarak_rekam_presensi'   => 'permit_empty|integer',
        ];
        $v = Services::validation();
        $v->setRules($rules);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $row = [
            'kode_kantor'            => (string) $input['kode_kantor'],
            'nama_kantor'            => (string) $input['nama_kantor'],
            'alamat_kantor'          => (string) $input['alamat_kantor'],
            'tipe'                   => (string) $input['tipe'],
            'lat'                    => (float) $input['lat'],
            'lng'                    => (float) $input['lng'],
            'jarak_rekam_presensi'   => (int) ($input['jarak_rekam_presensi'] ?? 30),
        ];
        if ($id === null) {
            $this->db->table('kantor')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_kantor' => (int) $this->db->insertID()]];
        }
        $this->db->table('kantor')->where('id_kantor', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function kantorDelete(int $id): array
    {
        $n = (int) $this->db->table('pegawai')->where('kantor', $id)->countAllResults();
        if ($n > 0) {
            return ['status' => 0, 'pesan' => 'Kantor masih dipakai ' . $n . ' pegawai.'];
        }
        $this->db->table('kantor')->where('id_kantor', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function unitKerjaList(): array
    {
        $rows = $this->db->table('unit_kerja')->orderBy('nama_unit_kerja', 'ASC')->get()->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function unitKerjaSave(array $input, ?int $id = null): array
    {
        $v = Services::validation();
        $v->setRules(['nama_unit_kerja' => 'required|max_length[50]']);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $row = ['nama_unit_kerja' => (string) $input['nama_unit_kerja']];
        if ($id === null) {
            $this->db->table('unit_kerja')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_unit_kerja' => (int) $this->db->insertID()]];
        }
        $this->db->table('unit_kerja')->where('id_unit_kerja', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function unitKerjaDelete(int $id): array
    {
        $n = (int) $this->db->table('pegawai')->where('unit_kerja', $id)->countAllResults();
        if ($n > 0) {
            return ['status' => 0, 'pesan' => 'Unit masih dipakai pegawai.'];
        }
        $this->db->table('unit_kerja')->where('id_unit_kerja', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function golonganList(): array
    {
        $rows = $this->db->table('golongan')->orderBy('nama_golongan', 'ASC')->get()->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function golonganSave(array $input, ?int $id = null): array
    {
        $v = Services::validation();
        $v->setRules(['nama_golongan' => 'permit_empty|max_length[100]']);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $row = ['nama_golongan' => (string) ($input['nama_golongan'] ?? '')];
        if ($id === null) {
            $this->db->table('golongan')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_golongan' => (int) $this->db->insertID()]];
        }
        $this->db->table('golongan')->where('id_golongan', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function golonganDelete(int $id): array
    {
        $n = (int) $this->db->table('pegawai')->where('golongan_pekerjaan', $id)->countAllResults();
        if ($n > 0) {
            return ['status' => 0, 'pesan' => 'Golongan masih dipakai pegawai.'];
        }
        $this->db->table('golongan')->where('id_golongan', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function jabatanList(): array
    {
        $rows = $this->db->table('jabatan')->orderBy('nama_jabatan', 'ASC')->get()->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function jabatanSave(array $input, ?int $id = null): array
    {
        $v = Services::validation();
        $v->setRules(['nama_jabatan' => 'required|max_length[50]']);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $row = ['nama_jabatan' => (string) $input['nama_jabatan']];
        if ($id === null) {
            $this->db->table('jabatan')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_jabatan' => (int) $this->db->insertID()]];
        }
        $this->db->table('jabatan')->where('id_jabatan', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function jabatanDelete(int $id): array
    {
        $n = (int) $this->db->table('pegawai')->where('jabatan', $id)->countAllResults();
        if ($n > 0) {
            return ['status' => 0, 'pesan' => 'Jabatan masih dipakai pegawai.'];
        }
        $this->db->table('jabatan')->where('id_jabatan', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function beritaList(): array
    {
        $rows = $this->db->table('berita')->orderBy('tanggal', 'DESC')->get()->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function beritaSave(array $input, ?int $id = null): array
    {
        $v = Services::validation();
        $v->setRules([
            'tanggal' => 'required|valid_date',
            'judul'   => 'required|max_length[255]',
            'isi'     => 'required',
            'photo'   => 'permit_empty|max_length[255]',
        ]);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $photo = trim((string) ($input['photo'] ?? ''));
        if ($photo === '') {
            $photo = '-';
        }
        $row = [
            'tanggal' => (string) $input['tanggal'],
            'judul'   => (string) $input['judul'],
            'isi'     => (string) $input['isi'],
            'photo'   => $photo,
        ];
        if ($id === null) {
            $this->db->table('berita')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_berita' => (int) $this->db->insertID()]];
        }
        $this->db->table('berita')->where('id_berita', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function beritaDelete(int $id): array
    {
        $this->db->table('berita')->where('id_berita', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function dilapanganList(?int $cabangKantorId = null): array
    {
        $b = $this->db->table('dilapangan d')
            ->select('d.*, p.nama_lengkap, p.nip')
            ->join('pegawai p', 'p.id_pegawai = d.pegawai', 'left');
        $this->applyCabangPegawaiAlias($b, 'p', $cabangKantorId);
        $rows = $b->orderBy('d.id_dilapangan', 'DESC')
            ->get()
            ->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function dilapanganSave(array $input, ?int $id = null, ?int $cabangKantorId = null): array
    {
        $v = Services::validation();
        $v->setRules([
            'pegawai' => 'required|integer',
            'alasan'  => 'required|max_length[255]',
        ]);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $pegawaiId = (int) $input['pegawai'];
        if (! $this->pegawaiIdAllowedCabang($pegawaiId, $cabangKantorId)) {
            return ['status' => 0, 'pesan' => 'Pegawai tidak termasuk cabang Anda.'];
        }
        if ($id !== null && $id > 0) {
            $ex = $this->db->table('dilapangan')->select('pegawai')->where('id_dilapangan', $id)->get()->getRowArray();
            if ($ex === null) {
                return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
            }
            if (! $this->pegawaiIdAllowedCabang((int) $ex['pegawai'], $cabangKantorId)) {
                return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
            }
        }
        $row = [
            'pegawai' => $pegawaiId,
            'alasan'  => (string) $input['alasan'],
        ];
        if ($id === null) {
            $this->db->table('dilapangan')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_dilapangan' => (int) $this->db->insertID()]];
        }
        $this->db->table('dilapangan')->where('id_dilapangan', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function dilapanganDelete(int $id, ?int $cabangKantorId = null): array
    {
        $ex = $this->db->table('dilapangan')->select('pegawai')->where('id_dilapangan', $id)->get()->getRowArray();
        if ($ex === null) {
            return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
        }
        if (! $this->pegawaiIdAllowedCabang((int) $ex['pegawai'], $cabangKantorId)) {
            return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
        }
        $this->db->table('dilapangan')->where('id_dilapangan', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function lemburList(?int $cabangKantorId = null): array
    {
        $b = $this->db->table('lembur l')
            ->select('l.*, p.nama_lengkap, p.nip')
            ->join('pegawai p', 'p.id_pegawai = l.pegawai', 'left');
        $this->applyCabangPegawaiAlias($b, 'p', $cabangKantorId);
        $rows = $b->orderBy('l.tanggal_lembur', 'DESC')
            ->get()
            ->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function lemburSave(array $input, ?int $id = null, ?int $cabangKantorId = null): array
    {
        $v = Services::validation();
        $v->setRules([
            'tanggal_lembur' => 'required|valid_date',
            'pegawai'        => 'required|integer',
            'masuk'          => 'required|max_length[5]',
            'beres'          => 'required|max_length[5]',
            'keterangan'     => 'required|max_length[255]',
        ]);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $pegawaiId = (int) $input['pegawai'];
        if (! $this->pegawaiIdAllowedCabang($pegawaiId, $cabangKantorId)) {
            return ['status' => 0, 'pesan' => 'Pegawai tidak termasuk cabang Anda.'];
        }
        if ($id !== null && $id > 0) {
            $ex = $this->db->table('lembur')->select('pegawai')->where('id_lembur', $id)->get()->getRowArray();
            if ($ex === null) {
                return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
            }
            if (! $this->pegawaiIdAllowedCabang((int) $ex['pegawai'], $cabangKantorId)) {
                return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
            }
        }
        $row = [
            'tanggal_lembur' => (string) $input['tanggal_lembur'],
            'pegawai'        => $pegawaiId,
            'masuk'          => (string) $input['masuk'],
            'beres'          => (string) $input['beres'],
            'keterangan'     => (string) $input['keterangan'],
        ];
        if ($id === null) {
            $this->db->table('lembur')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_lembur' => (int) $this->db->insertID()]];
        }
        $this->db->table('lembur')->where('id_lembur', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function lemburDelete(int $id, ?int $cabangKantorId = null): array
    {
        $ex = $this->db->table('lembur')->select('pegawai')->where('id_lembur', $id)->get()->getRowArray();
        if ($ex === null) {
            return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
        }
        if (! $this->pegawaiIdAllowedCabang((int) $ex['pegawai'], $cabangKantorId)) {
            return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
        }
        $this->db->table('lembur')->where('id_lembur', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function liburList(): array
    {
        $rows = $this->db->table('libur')->orderBy('tanggal_libur', 'DESC')->get()->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function liburSave(array $input, ?int $id = null): array
    {
        $v = Services::validation();
        $v->setRules([
            'tanggal_libur'     => 'required|valid_date',
            'keterangan_libur'  => 'required|max_length[255]',
        ]);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $row = [
            'tanggal_libur'    => (string) $input['tanggal_libur'],
            'keterangan_libur' => (string) $input['keterangan_libur'],
        ];
        if ($id === null) {
            $this->db->table('libur')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_libur' => (int) $this->db->insertID()]];
        }
        $this->db->table('libur')->where('id_libur', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function liburDelete(int $id): array
    {
        $this->db->table('libur')->where('id_libur', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function jadwalList(): array
    {
        $rows = $this->db->table('jadwal')->orderBy('nama_jadwal', 'ASC')->get()->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function jadwalSave(array $input, ?int $id = null): array
    {
        $v = Services::validation();
        $v->setRules([
            'nama_jadwal'             => 'required|max_length[50]',
            'toleransi_terlambat'     => 'required|integer',
            'toleransi_pulang_cepat'  => 'required|integer',
            '1_in' => 'required|max_length[5]',
            '1_out' => 'required|max_length[5]',
            '2_in' => 'required|max_length[5]',
            '2_out' => 'required|max_length[5]',
            '3_in' => 'required|max_length[5]',
            '3_out' => 'required|max_length[5]',
            '4_in' => 'required|max_length[5]',
            '4_out' => 'required|max_length[5]',
            '5_in' => 'required|max_length[5]',
            '5_out' => 'required|max_length[5]',
            '6_in' => 'permit_empty|max_length[5]',
            '6_out' => 'permit_empty|max_length[5]',
            '7_in' => 'permit_empty|max_length[5]',
            '7_out' => 'permit_empty|max_length[5]',
        ]);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $row = [
            'nama_jadwal'            => (string) $input['nama_jadwal'],
            'toleransi_terlambat'    => (int) $input['toleransi_terlambat'],
            'toleransi_pulang_cepat' => (int) $input['toleransi_pulang_cepat'],
            '1_in' => (string) $input['1_in'],
            '1_out' => (string) $input['1_out'],
            '2_in' => (string) $input['2_in'],
            '2_out' => (string) $input['2_out'],
            '3_in' => (string) $input['3_in'],
            '3_out' => (string) $input['3_out'],
            '4_in' => (string) $input['4_in'],
            '4_out' => (string) $input['4_out'],
            '5_in' => (string) $input['5_in'],
            '5_out' => (string) $input['5_out'],
            '6_in' => (string) ($input['6_in'] ?? ''),
            '6_out' => (string) ($input['6_out'] ?? ''),
            '7_in' => (string) ($input['7_in'] ?? ''),
            '7_out' => (string) ($input['7_out'] ?? ''),
        ];
        if ($id === null) {
            $this->db->table('jadwal')->insert($row);

            return ['status' => 1, 'pesan' => 'Disimpan.', 'data' => ['id_jadwal' => (int) $this->db->insertID()]];
        }
        $this->db->table('jadwal')->where('id_jadwal', $id)->update($row);

        return ['status' => 1, 'pesan' => 'Diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function jadwalDelete(int $id): array
    {
        $n = (int) $this->db->table('pegawai')->where('jadwal', $id)->countAllResults();
        if ($n > 0) {
            return ['status' => 0, 'pesan' => 'Jadwal dipakai ' . $n . ' pegawai.'];
        }
        $this->db->table('jadwal')->where('id_jadwal', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function aktifitasList(int $page, int $perPage, ?int $cabangKantorId = null): array
    {
        $perPage = max(5, min(100, $perPage));
        $page    = max(1, $page);
        $offset  = ($page - 1) * $perPage;
        $countB  = $this->db->table('aktifitas_harian a')->join('pegawai p', 'p.id_pegawai = a.pegawai', 'left');
        $this->applyCabangPegawaiAlias($countB, 'p', $cabangKantorId);
        $total = (int) $countB->countAllResults();
        $rowsB = $this->db->table('aktifitas_harian a')
            ->select('a.*, p.nama_lengkap, p.nip')
            ->join('pegawai p', 'p.id_pegawai = a.pegawai', 'left');
        $this->applyCabangPegawaiAlias($rowsB, 'p', $cabangKantorId);
        $rows = $rowsB->orderBy('a.waktu_aktifitas', 'DESC')
            ->limit($perPage, $offset)
            ->get()
            ->getResultArray();

        return [
            'status' => 1,
            'pesan'  => 'OK',
            'data'   => [
                'rows'       => $rows,
                'total'      => $total,
                'page'       => $page,
                'per_page'   => $perPage,
                'total_page' => (int) ceil($total / $perPage),
            ],
        ];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function aktifitasDelete(int $id, ?int $cabangKantorId = null): array
    {
        $ex = $this->db->table('aktifitas_harian')->select('pegawai')->where('id_aktifitas_harian', $id)->get()->getRowArray();
        if ($ex === null) {
            return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
        }
        if (! $this->pegawaiIdAllowedCabang((int) $ex['pegawai'], $cabangKantorId)) {
            return ['status' => 0, 'pesan' => 'Data tidak ditemukan.'];
        }
        $this->db->table('aktifitas_harian')->where('id_aktifitas_harian', $id)->delete();

        return ['status' => 1, 'pesan' => 'Dihapus.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function laporanCutiRentang(string $dari, string $sampai, ?int $cabangKantorId = null): array
    {
        $b = $this->db->table('cuti c')
            ->select('c.*, p.nama_lengkap, p.nip')
            ->join('pegawai p', 'p.id_pegawai = c.pegawai', 'left')
            ->where('c.tanggal_cuti >=', $dari)
            ->where('c.tanggal_cuti <=', $sampai);
        $this->applyCabangPegawaiAlias($b, 'p', $cabangKantorId);
        $rows = $b->orderBy('c.tanggal_cuti', 'DESC')
            ->get()
            ->getResultArray();

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows, 'dari' => $dari, 'sampai' => $sampai]];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function adminUsersList(): array
    {
        if (! $this->db->tableExists('admin_users')) {
            return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => []]];
        }
        $users = $this->db->table('admin_users')->orderBy('id', 'ASC')->get()->getResultArray();
        $hasPegawaiCol = $this->db->fieldExists('id_pegawai', 'admin_users');
        foreach ($users as &$u) {
            unset($u['password'], $u['remember_code'], $u['activation_code'], $u['forgotten_password_code']);
            $u['groups'] = $this->adminUserGroupNames((int) $u['id']);
            $u['pegawai_proxy'] = null;
            if ($hasPegawaiCol && $this->db->tableExists('pegawai')) {
                $pid = (int) ($u['id_pegawai'] ?? 0);
                if ($pid > 0) {
                    $p = $this->db->table('pegawai')
                        ->select('nip, nama_lengkap')
                        ->where('id_pegawai', $pid)
                        ->get()
                        ->getRowArray();
                    if ($p !== null) {
                        $u['pegawai_proxy'] = trim((string) ($p['nama_lengkap'] ?? '')) . ' (NIP ' . (string) ($p['nip'] ?? '') . ')';
                    }
                }
            }
        }

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $users]];
    }

    /**
     * @return list<string>
     */
    public function adminUserGroupNames(int $userId): array
    {
        if (! $this->db->tableExists('admin_users_groups') || ! $this->db->tableExists('admin_groups')) {
            return [];
        }
        $q = $this->db->table('admin_users_groups ug')
            ->select('g.name')
            ->join('admin_groups g', 'g.id = ug.group_id', 'inner')
            ->where('ug.user_id', $userId)
            ->get()
            ->getResultArray();

        return array_values(array_filter(array_map(static fn ($r) => (string) ($r['name'] ?? ''), $q)));
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function adminGroupsList(): array
    {
        if (! $this->db->tableExists('admin_groups')) {
            return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => []]];
        }
        $rows = $this->db->table('admin_groups')->orderBy('id', 'ASC')->get()->getResultArray();
        if ($this->db->tableExists('admin_users_groups')) {
            foreach ($rows as &$r) {
                $r['user_count'] = (int) $this->db->table('admin_users_groups')
                    ->where('group_id', (int) ($r['id'] ?? 0))
                    ->countAllResults();
            }
        } else {
            foreach ($rows as &$r) {
                $r['user_count'] = 0;
            }
        }
        unset($r);

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['rows' => $rows]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function adminGroupCreate(array $input): array
    {
        if (! $this->db->tableExists('admin_groups')) {
            return ['status' => 0, 'pesan' => 'Tabel admin_groups tidak ada.'];
        }
        $v = Services::validation();
        $v->setRules([
            'name'        => 'required|max_length[20]|regex_match[/^[a-zA-Z0-9_]+$/]',
            'description' => 'required|max_length[100]',
        ]);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $name = strtolower(trim((string) $input['name']));
        if ($name === 'webmaster') {
            return ['status' => 0, 'pesan' => 'Nama grup webmaster sudah dipakai sistem.'];
        }
        $exists = $this->db->table('admin_groups')->where('name', $name)->countAllResults();
        if ($exists > 0) {
            return ['status' => 0, 'pesan' => 'Nama grup sudah dipakai.'];
        }
        $this->db->table('admin_groups')->insert([
            'name'        => $name,
            'description' => trim((string) $input['description']),
        ]);

        return ['status' => 1, 'pesan' => 'Grup dibuat.', 'data' => ['id' => (int) $this->db->insertID()]];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string}
     */
    public function adminGroupUpdate(int $groupId, array $input): array
    {
        if (! $this->db->tableExists('admin_groups')) {
            return ['status' => 0, 'pesan' => 'Tabel admin_groups tidak ada.'];
        }
        $row = $this->db->table('admin_groups')->where('id', $groupId)->get()->getRowArray();
        if ($row === null) {
            return ['status' => 0, 'pesan' => 'Grup tidak ditemukan.'];
        }
        $v = Services::validation();
        $v->setRules([
            'name'        => "required|max_length[20]|regex_match[/^[a-zA-Z0-9_]+$/]|is_unique[admin_groups.name,id,{$groupId}]",
            'description' => 'required|max_length[100]',
        ]);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $name = strtolower(trim((string) $input['name']));
        if (strtolower((string) $row['name']) === 'webmaster' && $name !== 'webmaster') {
            return ['status' => 0, 'pesan' => 'Grup webmaster tidak boleh diubah namanya.'];
        }
        if ($name === 'webmaster' && strtolower((string) $row['name']) !== 'webmaster') {
            return ['status' => 0, 'pesan' => 'Tidak boleh mengubah nama grup menjadi webmaster.'];
        }
        $this->db->table('admin_groups')->where('id', $groupId)->update([
            'name'        => $name,
            'description' => trim((string) $input['description']),
        ]);

        return ['status' => 1, 'pesan' => 'Grup diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function adminGroupDelete(int $groupId): array
    {
        if (! $this->db->tableExists('admin_groups')) {
            return ['status' => 0, 'pesan' => 'Tabel admin_groups tidak ada.'];
        }
        $row = $this->db->table('admin_groups')->where('id', $groupId)->get()->getRowArray();
        if ($row === null) {
            return ['status' => 0, 'pesan' => 'Grup tidak ditemukan.'];
        }
        if (strtolower((string) $row['name']) === 'webmaster') {
            return ['status' => 0, 'pesan' => 'Grup webmaster tidak boleh dihapus.'];
        }
        if ($this->db->tableExists('admin_users_groups')) {
            $n = (int) $this->db->table('admin_users_groups')->where('group_id', $groupId)->countAllResults();
            if ($n > 0) {
                return ['status' => 0, 'pesan' => 'Grup masih dipakai ' . $n . ' pengguna. Pindahkan pengguna dulu.'];
            }
        }
        $this->db->table('admin_groups')->where('id', $groupId)->delete();

        return ['status' => 1, 'pesan' => 'Grup dihapus.'];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function adminUserCreate(array $input): array
    {
        if (! $this->db->tableExists('admin_users')) {
            return ['status' => 0, 'pesan' => 'Tabel admin_users tidak ada.'];
        }
        $hasPegawaiCol = $this->db->fieldExists('id_pegawai', 'admin_users');
        $rules         = [
            'username'     => 'required|max_length[100]|is_unique[admin_users.username]',
            'email'        => 'permit_empty|max_length[100]',
            'password'     => 'required|min_length[6]',
            'nama_lengkap' => 'required|max_length[100]',
            'no_telepon'   => 'permit_empty|max_length[25]',
            'group_id'     => 'required|integer',
        ];
        if ($hasPegawaiCol) {
            $rules['id_pegawai'] = 'permit_empty|integer';
        }
        $v = Services::validation();
        $v->setRules($rules);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $gid = (int) $input['group_id'];
        $g   = $this->db->table('admin_groups')->where('id', $gid)->get()->getRowArray();
        if ($g === null) {
            return ['status' => 0, 'pesan' => 'Grup tidak valid.'];
        }
        if (strtolower((string) $g['name']) === 'webmaster') {
            return ['status' => 0, 'pesan' => 'Tidak boleh membuat akun webmaster dari sini.'];
        }
        $idPegawaiInsert = null;
        if ($hasPegawaiCol && $this->db->tableExists('pegawai')) {
            $pid = (int) ($input['id_pegawai'] ?? 0);
            if ($pid > 0) {
                $okP = $this->db->table('pegawai')->where('id_pegawai', $pid)->countAllResults() > 0;
                if (! $okP) {
                    return ['status' => 0, 'pesan' => 'Pegawai (proxy API) tidak ditemukan.'];
                }
                $idPegawaiInsert = $pid;
            }
        }
        $hash = password_hash((string) $input['password'], PASSWORD_BCRYPT);
        $rowInsert = [
            'ip_address' => $this->db->escapeString(service('request')->getIPAddress()),
            'username'   => (string) $input['username'],
            'password'   => $hash,
            'email'      => (string) ($input['email'] ?? ''),
            'created_on' => time(),
            'active'     => 1,
            'first_name' => '',
            'last_name'  => '',
            'photo'      => '',
            'nama_lengkap' => (string) $input['nama_lengkap'],
            'no_telepon'   => (string) ($input['no_telepon'] ?? ''),
        ];
        if ($hasPegawaiCol) {
            $rowInsert['id_pegawai'] = $idPegawaiInsert;
        }
        $this->db->table('admin_users')->insert($rowInsert);
        $uid = (int) $this->db->insertID();
        if ($this->db->tableExists('admin_users_groups')) {
            $this->db->table('admin_users_groups')->insert(['user_id' => $uid, 'group_id' => $gid]);
        }

        return ['status' => 1, 'pesan' => 'Pengguna dibuat.', 'data' => ['id' => $uid]];
    }

    /**
     * Satu pengguna untuk form edit (tanpa password).
     *
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function adminUserShow(int $userId): array
    {
        if (! $this->db->tableExists('admin_users')) {
            return ['status' => 0, 'pesan' => 'Tabel admin_users tidak ada.'];
        }
        $row = $this->db->table('admin_users')->where('id', $userId)->get()->getRowArray();
        if ($row === null) {
            return ['status' => 0, 'pesan' => 'Pengguna tidak ditemukan.'];
        }
        unset($row['password'], $row['remember_code'], $row['activation_code'], $row['forgotten_password_code']);
        $row['groups'] = $this->adminUserGroupNames($userId);
        $row['group_id'] = null;
        if ($this->db->tableExists('admin_users_groups')) {
            $ug = $this->db->table('admin_users_groups')
                ->where('user_id', $userId)
                ->orderBy('group_id', 'ASC')
                ->get()
                ->getRowArray();
            if ($ug !== null) {
                $row['group_id'] = (int) $ug['group_id'];
            }
        }

        return ['status' => 1, 'pesan' => 'OK', 'data' => $row];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string}
     */
    public function adminUserUpdate(int $userId, array $input): array
    {
        if (! $this->db->tableExists('admin_users')) {
            return ['status' => 0, 'pesan' => 'Tabel admin_users tidak ada.'];
        }
        $existing = $this->db->table('admin_users')->where('id', $userId)->get()->getRowArray();
        if ($existing === null) {
            return ['status' => 0, 'pesan' => 'Pengguna tidak ditemukan.'];
        }
        $hasPegawaiCol = $this->db->fieldExists('id_pegawai', 'admin_users');
        $rules         = [
            'username'     => "required|max_length[100]|is_unique[admin_users.username,id,{$userId}]",
            'email'        => 'permit_empty|max_length[100]',
            'nama_lengkap' => 'required|max_length[100]',
            'no_telepon'   => 'permit_empty|max_length[25]',
            'group_id'     => 'required|integer',
            'active'       => 'required|in_list[0,1]',
        ];
        if ($hasPegawaiCol) {
            $rules['id_pegawai'] = 'permit_empty|integer';
        }
        $v = Services::validation();
        $v->setRules($rules);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $gidNew = (int) $input['group_id'];
        $gNew   = $this->db->table('admin_groups')->where('id', $gidNew)->get()->getRowArray();
        if ($gNew === null) {
            return ['status' => 0, 'pesan' => 'Grup tidak valid.'];
        }
        $currentNames = array_map('strtolower', $this->adminUserGroupNames($userId));
        $isWebmaster  = in_array('webmaster', $currentNames, true);
        $newName      = strtolower((string) $gNew['name']);
        if ($newName === 'webmaster' && ! $isWebmaster) {
            return ['status' => 0, 'pesan' => 'Tidak boleh mengangkat pengguna menjadi webmaster dari sini.'];
        }
        if ($isWebmaster && $newName !== 'webmaster') {
            return ['status' => 0, 'pesan' => 'Tidak boleh mengubah grup akun webmaster.'];
        }
        $idPegawaiVal = null;
        if ($hasPegawaiCol && $this->db->tableExists('pegawai')) {
            $pid = (int) ($input['id_pegawai'] ?? 0);
            if ($pid > 0) {
                $okP = $this->db->table('pegawai')->where('id_pegawai', $pid)->countAllResults() > 0;
                if (! $okP) {
                    return ['status' => 0, 'pesan' => 'Pegawai (proxy API) tidak ditemukan.'];
                }
                $idPegawaiVal = $pid;
            }
        }
        $update = [
            'username'     => (string) $input['username'],
            'email'        => (string) ($input['email'] ?? ''),
            'nama_lengkap' => (string) $input['nama_lengkap'],
            'no_telepon'   => (string) ($input['no_telepon'] ?? ''),
            'active'       => (int) $input['active'] === 1 ? 1 : 0,
        ];
        if ($hasPegawaiCol) {
            $update['id_pegawai'] = $idPegawaiVal;
        }
        $this->db->table('admin_users')->where('id', $userId)->update($update);
        if ($this->db->tableExists('admin_users_groups')) {
            $this->db->table('admin_users_groups')->where('user_id', $userId)->delete();
            $this->db->table('admin_users_groups')->insert(['user_id' => $userId, 'group_id' => $gidNew]);
        }

        return ['status' => 1, 'pesan' => 'Pengguna diperbarui.'];
    }

    /**
     * @param array<string, scalar|null> $input
     *
     * @return array{status: int, pesan: string}
     */
    public function adminUserResetPassword(int $userId, array $input): array
    {
        $v = Services::validation();
        $v->setRules(['password' => 'required|min_length[6]']);
        if (! $v->run($input)) {
            return ['status' => 0, 'pesan' => implode(' ', $v->getErrors())];
        }
        $exists = $this->db->table('admin_users')->where('id', $userId)->get()->getRowArray();
        if ($exists === null) {
            return ['status' => 0, 'pesan' => 'Pengguna tidak ditemukan.'];
        }
        $hash = password_hash((string) $input['password'], PASSWORD_BCRYPT);
        $this->db->table('admin_users')->where('id', $userId)->update(['password' => $hash]);

        return ['status' => 1, 'pesan' => 'Password diperbarui.'];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function backupListFiles(): array
    {
        $dir = WRITEPATH . 'admin_db_backup';
        if (! is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        $files = glob($dir . DIRECTORY_SEPARATOR . '*.sql') ?: [];
        rsort($files);
        $out = [];
        foreach ($files as $f) {
            $out[] = [
                'name' => basename($f),
                'size' => filesize($f) ?: 0,
                'mtime'=> date('Y-m-d H:i:s', (int) filemtime($f)),
            ];
        }

        return ['status' => 1, 'pesan' => 'OK', 'data' => ['directory' => $dir, 'files' => $out]];
    }

    /**
     * @return array{status: int, pesan: string, data?: mixed}
     */
    public function backupRun(bool $alsoLatest): array
    {
        $dir = WRITEPATH . 'admin_db_backup';
        if (! is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        try {
            $util   = \Config\Database::utils();
            $backup = $util->backup(['format' => 'sql']);
        } catch (\Throwable $e) {
            return ['status' => 0, 'pesan' => 'Backup gagal: ' . $e->getMessage()];
        }
        $name = date('Y-m-d_H-i-s') . '.sql';
        $path = $dir . DIRECTORY_SEPARATOR . $name;
        if (file_put_contents($path, $backup) === false) {
            return ['status' => 0, 'pesan' => 'Gagal menulis file backup.'];
        }
        if ($alsoLatest) {
            file_put_contents($dir . DIRECTORY_SEPARATOR . 'latest.sql', $backup);
        }

        return ['status' => 1, 'pesan' => 'Backup dibuat.', 'data' => ['file' => $name]];
    }

    /**
     * @return array{status: int, pesan: string}
     */
    public function backupDelete(string $filename): array
    {
        if (! preg_match('/^[a-zA-Z0-9._-]+\\.sql$/', $filename)) {
            return ['status' => 0, 'pesan' => 'Nama file tidak valid.'];
        }
        $path = WRITEPATH . 'admin_db_backup' . DIRECTORY_SEPARATOR . $filename;
        if (! is_file($path)) {
            return ['status' => 0, 'pesan' => 'File tidak ada.'];
        }
        @unlink($path);

        return ['status' => 1, 'pesan' => 'File dihapus.'];
    }

    public function backupFilePath(string $filename): ?string
    {
        if (! preg_match('/^[a-zA-Z0-9._-]+\\.sql$/', $filename)) {
            return null;
        }
        $path = WRITEPATH . 'admin_db_backup' . DIRECTORY_SEPARATOR . $filename;

        return is_file($path) ? $path : null;
    }

    private function applyCabangPegawaiAlias(BaseBuilder $b, string $alias, ?int $cabangKantorId): void
    {
        if ($cabangKantorId !== null && $cabangKantorId > 0) {
            $b->where("{$alias}.kantor", $cabangKantorId);
        }
    }

    private function pegawaiIdAllowedCabang(int $pegawaiId, ?int $cabangKantorId): bool
    {
        if ($cabangKantorId === null || $cabangKantorId <= 0) {
            return true;
        }

        return $this->db->table('pegawai')->where('id_pegawai', $pegawaiId)->where('kantor', $cabangKantorId)->countAllResults() > 0;
    }
}
