<?php
namespace App\Controllers;

use App\Core\Acl;
use App\Core\Audit;
use App\Core\Auth;
use App\Core\Controller;
use App\Core\Database;
use App\Core\Flash;
use App\Core\Request;
use App\Core\Response;
use App\Core\Settings;
use App\Models\User;

/** Manajemen pengguna, peran, dan hak akses. */
class UserController extends Controller
{
    protected string $module = 'pengguna';

    public function index(): void
    {
        $f = ['cari' => trim((string)Request::get('cari', ''))];
        $r = Request::int('role_id'); if ($r > 0) $f['role_id'] = $r;
        $a = Request::get('is_active', ''); if ($a !== '') $f['is_active'] = (int)$a;
        [$page, $per] = $this->pageParams();
        $this->view('pengguna/index', [
            'judul' => 'Pengguna',
            'subjudul' => 'Akun pengguna sistem beserta peran dan cakupan datanya',
            'p' => (new User())->listing($f, $page, $per), 'filters' => $f,
            'roles' => Database::i()->all('SELECT * FROM roles WHERE deleted_at IS NULL ORDER BY id'),
        ]);
    }

    public function create(): void
    {
        $this->view('pengguna/form', array_merge($this->formData(), [
            'judul' => 'Tambah Pengguna', 'u' => null, 'extraRoles' => [],
        ]));
    }

    public function edit(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $m = new User();
        $u = $m->find($id);
        if (!$u) Response::abort(404, 'Pengguna tidak ditemukan.');
        $this->view('pengguna/form', array_merge($this->formData(), [
            'judul' => 'Ubah Pengguna ' . $u['username'], 'u' => $u,
            'extraRoles' => array_map('intval', $m->extraRoles($id)),
        ]));
    }

    private function formData(): array
    {
        $db = Database::i();
        return [
            'roles' => $db->all('SELECT * FROM roles WHERE deleted_at IS NULL ORDER BY id'),
            'areas' => $db->all('SELECT id,name FROM areas WHERE deleted_at IS NULL ORDER BY name'),
            'bus' => $db->all('SELECT id,name FROM business_units WHERE deleted_at IS NULL ORDER BY name'),
            'salespersons' => $db->all('SELECT id,name FROM salespersons WHERE deleted_at IS NULL ORDER BY name'),
            'officers' => $db->all('SELECT id,name FROM collection_officers WHERE deleted_at IS NULL ORDER BY name'),
            'minSandi' => Settings::int('keamanan.panjang_sandi_minimal', 8),
        ];
    }

    public function store(): void
    {
        $data = $this->validate([
            'username' => 'required|minlen:3|maxlen:60|unique:users,username',
            'name'     => 'required|maxlen:120',
            'email'    => 'required|email|maxlen:150|unique:users,email',
            'role_id'  => 'required|integer|exists:roles,id',
            'password' => 'required|minlen:' . Settings::int('keamanan.panjang_sandi_minimal', 8),
        ], ['username' => 'Nama pengguna', 'name' => 'Nama lengkap', 'email' => 'Email',
            'role_id' => 'Peran', 'password' => 'Kata sandi'], '/pengguna/tambah');

        $err = Auth::validatePassword($data['password'], Settings::int('keamanan.panjang_sandi_minimal', 8));
        if ($err) { Flash::error($err); $this->redirect('/pengguna/tambah'); }

        $db = Database::i();
        $id = $db->insert('users', [
            'username' => strtolower(trim($data['username'])),
            'name' => $data['name'], 'email' => strtolower(trim($data['email'])),
            'password_hash' => Auth::hash($data['password']),
            'role_id' => (int)$data['role_id'],
            'must_change_password' => Request::bool('must_change_password', true) ? 1 : 0,
            'is_active' => Request::bool('is_active', true) ? 1 : 0,
            'phone' => mb_substr((string)Request::post('phone', ''), 0, 40) ?: null,
            'area_id' => Request::int('area_id') ?: null,
            'business_unit_id' => Request::int('business_unit_id') ?: null,
            'salesperson_id' => Request::int('salesperson_id') ?: null,
            'collection_officer_id' => Request::int('collection_officer_id') ?: null,
            'created_by' => Auth::id() ?: null,
        ]);
        (new User())->setExtraRoles($id, array_map('intval', Request::array('extra_roles')));
        Audit::log('pengguna', 'create', 'user', $id, 'Pengguna baru dibuat: ' . $data['username']);
        Flash::success('Pengguna ' . $data['username'] . ' berhasil dibuat.');
        $this->redirect('/pengguna');
    }

    public function update(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $m = new User();
        $before = $m->find($id);
        if (!$before) Response::abort(404, 'Pengguna tidak ditemukan.');

        $data = $this->validate([
            'name'  => 'required|maxlen:120',
            'email' => 'required|email|maxlen:150|unique:users,email,' . $id,
            'role_id' => 'required|integer|exists:roles,id',
        ], ['name' => 'Nama lengkap', 'email' => 'Email', 'role_id' => 'Peran']);

        // Cegah super admin terakhir kehilangan aksesnya
        if ((int)$before['role_id'] === 1 && (int)$data['role_id'] !== 1) {
            $n = (int)Database::i()->value('SELECT COUNT(*) FROM users WHERE role_id = 1 AND is_active = 1 AND deleted_at IS NULL AND id <> ?', [$id]);
            if ($n === 0) {
                Flash::error('Tidak dapat mengubah peran: ini adalah satu-satunya Super Administrator aktif.');
                $this->redirect('/pengguna/' . $id . '/ubah');
            }
        }

        $upd = [
            'name' => $data['name'], 'email' => strtolower(trim($data['email'])),
            'role_id' => (int)$data['role_id'],
            'is_active' => Request::bool('is_active', true) ? 1 : 0,
            'phone' => mb_substr((string)Request::post('phone', ''), 0, 40) ?: null,
            'area_id' => Request::int('area_id') ?: null,
            'business_unit_id' => Request::int('business_unit_id') ?: null,
            'salesperson_id' => Request::int('salesperson_id') ?: null,
            'collection_officer_id' => Request::int('collection_officer_id') ?: null,
        ];
        Database::i()->update('users', $upd, 'id = :pk', ['pk' => $id]);
        $m->setExtraRoles($id, array_map('intval', Request::array('extra_roles')));
        Audit::logDiff('pengguna', 'update', 'user', $id, $before, $upd, 'Data pengguna diubah');
        Flash::success('Data pengguna berhasil diperbarui.');
        $this->redirect('/pengguna');
    }

    public function destroy(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        if ($id === Auth::id()) { Flash::error('Anda tidak dapat menghapus akun Anda sendiri.'); $this->back('/pengguna'); }
        $m = new User();
        $u = $m->find($id);
        if (!$u) Response::abort(404, 'Pengguna tidak ditemukan.');
        if ((int)$u['role_id'] === 1) {
            $n = (int)Database::i()->value('SELECT COUNT(*) FROM users WHERE role_id = 1 AND is_active = 1 AND deleted_at IS NULL AND id <> ?', [$id]);
            if ($n === 0) { Flash::error('Super Administrator terakhir tidak dapat dihapus.'); $this->back('/pengguna'); }
        }
        $m->delete($id);
        Audit::log('pengguna', 'delete', 'user', $id, 'Pengguna dihapus: ' . $u['username'], $u, null);
        Flash::success('Pengguna ' . $u['username'] . ' dihapus.');
        $this->redirect('/pengguna');
    }

    public function toggleActive(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        if ($id === Auth::id()) { Flash::error('Anda tidak dapat menonaktifkan akun Anda sendiri.'); $this->back('/pengguna'); }
        $u = (new User())->find($id);
        if (!$u) Response::abort(404, 'Pengguna tidak ditemukan.');
        $new = (int)$u['is_active'] === 1 ? 0 : 1;
        Database::i()->update('users', ['is_active' => $new, 'failed_attempts' => 0, 'locked_until' => null], 'id = :pk', ['pk' => $id]);
        Audit::log('pengguna', 'update', 'user', $id, 'Status akun ' . $u['username'] . ' diubah menjadi ' . ($new ? 'aktif' : 'nonaktif'));
        Flash::success('Akun ' . $u['username'] . ' berhasil ' . ($new ? 'diaktifkan' : 'dinonaktifkan') . '.');
        $this->back('/pengguna');
    }

    public function resetPassword(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $u = (new User())->find($id);
        if (!$u) Response::abort(404, 'Pengguna tidak ditemukan.');
        $new = trim((string)Request::post('sandi_baru', ''));
        if ($new === '') {
            // Buat kata sandi acak yang memenuhi kebijakan
            $new = 'Rp' . bin2hex(random_bytes(4)) . random_int(10, 99);
        }
        $res = Auth::changePassword($id, $new, false);
        if (!$res['sukses']) { Flash::error($res['pesan']); $this->back('/pengguna'); }
        Database::i()->update('users', ['must_change_password' => 1, 'failed_attempts' => 0, 'locked_until' => null], 'id = :pk', ['pk' => $id]);
        Audit::log('pengguna', 'reset_sandi', 'user', $id, 'Kata sandi ' . $u['username'] . ' direset oleh administrator');
        Flash::success('Kata sandi ' . $u['username'] . ' direset menjadi: ' . $new
            . ' — catat sekarang, pengguna wajib menggantinya saat login berikutnya.');
        $this->back('/pengguna');
    }

    /** Matriks hak akses per peran. */
    public function roles(): void
    {
        $db = Database::i();
        $perms = $db->all('SELECT * FROM permissions ORDER BY sort_order, module, action');
        $byModule = [];
        foreach ($perms as $p) $byModule[$p['module']][] = $p;
        $matrix = [];
        foreach ($db->all('SELECT role_id, permission_id FROM role_permissions') as $rp) {
            $matrix[(int)$rp['role_id']][(int)$rp['permission_id']] = true;
        }
        $this->view('pengguna/peran', [
            'judul' => 'Peran & Hak Akses',
            'subjudul' => 'Matriks izin per modul dan aksi — dapat disesuaikan per peran',
            'roles' => $db->all('SELECT * FROM roles WHERE deleted_at IS NULL ORDER BY id'),
            'byModule' => $byModule, 'matrix' => $matrix,
            'jumlahPengguna' => $db->all('SELECT role_id, COUNT(*) n FROM users WHERE deleted_at IS NULL GROUP BY role_id'),
            'actionLabels' => ['view' => 'Lihat', 'create' => 'Tambah', 'edit' => 'Ubah', 'delete' => 'Hapus',
                               'approve' => 'Setujui', 'import' => 'Import', 'export' => 'Export', 'print' => 'Cetak'],
        ]);
    }

    public function saveRolePermissions(): void
    {
        $roleId = Request::int('role_id');
        if ($roleId <= 0) { Flash::error('Peran tidak valid.'); $this->back('/pengguna/peran'); }
        if ($roleId === 1) { Flash::warning('Hak akses Super Administrator selalu penuh dan tidak dapat diubah.'); $this->back('/pengguna/peran'); }

        $db = Database::i();
        $ids = array_map('intval', Request::array('permissions'));
        $db->transaction(function () use ($db, $roleId, $ids) {
            $db->exec('DELETE FROM role_permissions WHERE role_id = ?', [$roleId]);
            foreach (array_unique($ids) as $pid) {
                if ($pid > 0) $db->exec('INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?,?)', [$roleId, $pid]);
            }
        });
        $scope = (string)Request::post('data_scope', '');
        if (in_array($scope, ['all', 'own_collection', 'own_sales', 'own_area'], true)) {
            $db->update('roles', ['data_scope' => $scope], 'id = :pk', ['pk' => $roleId]);
        }
        Acl::reset();
        Audit::log('pengguna', 'update', 'role', $roleId, 'Hak akses peran diperbarui (' . count($ids) . ' izin)');
        Flash::success('Hak akses peran berhasil disimpan (' . count($ids) . ' izin aktif).');
        $this->redirect('/pengguna/peran?role_id=' . $roleId);
    }
}
