<?php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

$host = 'localhost';
$user = 'root';
$pass = '';
$dbname = 'audit_system';

$conn = @new mysqli($host, $user, $pass, $dbname);

if ($conn->connect_errno) {
    $bootstrapConn = @new mysqli($host, $user, $pass);
    if ($bootstrapConn->connect_errno) {
        die('Sambungan database gagal: ' . $bootstrapConn->connect_error);
    }

    $safeDbName = preg_replace('/[^a-zA-Z0-9_]/', '', $dbname);
    $bootstrapConn->query("CREATE DATABASE IF NOT EXISTS `{$safeDbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci");
    $bootstrapConn->close();

    $conn = new mysqli($host, $user, $pass, $dbname);
    if ($conn->connect_error) {
        die('Sambungan database gagal: ' . $conn->connect_error);
    }
}

$conn->set_charset('utf8mb4');

define('SESSION_WARNING_SECONDS', 600);
define('SESSION_GRACE_SECONDS', 60);

function table_exists(mysqli $conn, string $tableName): bool
{
    $safeTable = $conn->real_escape_string($tableName);
    $result = $conn->query("SHOW TABLES LIKE '{$safeTable}'");
    return $result instanceof mysqli_result && $result->num_rows > 0;
}

function column_exists(mysqli $conn, string $tableName, string $columnName): bool
{
    $safeTable = $conn->real_escape_string($tableName);
    $safeColumn = $conn->real_escape_string($columnName);
    $result = $conn->query("SHOW COLUMNS FROM `{$safeTable}` LIKE '{$safeColumn}'");
    return $result instanceof mysqli_result && $result->num_rows > 0;
}

function audit_bahagian_list(): array
{
    return [
        'Bahagian Pentadbiran & Sumber Manusia (BPSM)',
        'Bahagian Kewangan & Akaun (BKA)',
        'Bahagian Teknologi Maklumat (BTM)',
        'Bahagian Undang-Undang (BUU)',
        'Bahagian Audit Dalam (BAD)',
        'Unit Pengurusan dan Penyelengaraan Premis (UPPP)',
        'Unit Pentadbiran Tanah (UPT)',
        'Unit Pembangunan (UP)',
        'Unit Pengurusan Dana Wakaf (UPPW)',
        'Unit Latihan (UL)',
    ];
}

function ensure_audit_schema(mysqli $conn): void
{
    $conn->query("
        CREATE TABLE IF NOT EXISTS users (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            nama VARCHAR(150) NOT NULL,
            ic VARCHAR(20) NOT NULL UNIQUE,
            emel VARCHAR(150) NOT NULL UNIQUE,
            role ENUM('Pengguna Biasa', 'Admin Audit', 'Pentadbir Sistem') NOT NULL DEFAULT 'Pengguna Biasa',
            jabatan VARCHAR(150) NOT NULL,
            approval_status ENUM('pending', 'active', 'rejected') NOT NULL DEFAULT 'pending',
            account_status ENUM('active', 'inactive') NOT NULL DEFAULT 'inactive',
            deactivated_at DATETIME NULL DEFAULT NULL,
            password VARCHAR(255) NOT NULL,
            reset_token VARCHAR(255) NULL DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        )
    ");

    $conn->query("ALTER TABLE users MODIFY role ENUM('User Biasa', 'Pengguna Biasa', 'Admin Audit', 'Pentadbir Sistem') NOT NULL DEFAULT 'Pengguna Biasa'");
    $conn->query("UPDATE users SET role = 'Pengguna Biasa' WHERE role = 'User Biasa'");
    $conn->query("ALTER TABLE users MODIFY role ENUM('Pengguna Biasa', 'Admin Audit', 'Pentadbir Sistem') NOT NULL DEFAULT 'Pengguna Biasa'");

    if (!column_exists($conn, 'users', 'approval_status')) {
        $conn->query("ALTER TABLE users ADD COLUMN approval_status ENUM('pending', 'active', 'rejected') NOT NULL DEFAULT 'pending' AFTER jabatan");
        $conn->query("UPDATE users SET approval_status = 'active' WHERE role IN ('Admin Audit', 'Pentadbir Sistem')");
    }

    if (!column_exists($conn, 'users', 'account_status')) {
        $conn->query("ALTER TABLE users ADD COLUMN account_status ENUM('active', 'inactive') NOT NULL DEFAULT 'inactive' AFTER approval_status");
        $conn->query("UPDATE users SET account_status = 'active' WHERE approval_status = 'active'");
    }

    if (!column_exists($conn, 'users', 'deactivated_at')) {
        $conn->query("ALTER TABLE users ADD COLUMN deactivated_at DATETIME NULL DEFAULT NULL AFTER account_status");
    }

    $conn->query("UPDATE users SET account_status = 'active', deactivated_at = NULL WHERE approval_status = 'active' AND (account_status IS NULL OR account_status = '')");
    $conn->query("UPDATE users SET account_status = 'inactive' WHERE approval_status IN ('pending', 'rejected') AND account_status <> 'inactive'");

    $conn->query("
        CREATE TABLE IF NOT EXISTS password_reset_codes (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id INT UNSIGNED NOT NULL,
            emel VARCHAR(150) NOT NULL,
            ic VARCHAR(20) NOT NULL,
            code_hash VARCHAR(255) NOT NULL,
            expires_at DATETIME NOT NULL,
            used_at DATETIME NULL DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_password_reset_codes_user (user_id),
            INDEX idx_password_reset_codes_expires (expires_at),
            CONSTRAINT fk_password_reset_codes_user
                FOREIGN KEY (user_id) REFERENCES users(id)
                ON DELETE CASCADE
        )
    ");

    $conn->query("
        CREATE TABLE IF NOT EXISTS audit_forms (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            perkara VARCHAR(255) NOT NULL,
            tarikh_mula DATE NULL,
            tarikh_akhir DATE NULL,
            remarks TEXT DEFAULT NULL,
            audit_approval_status ENUM('perlu_kemaskini', 'lulus_diaudit') NOT NULL DEFAULT 'perlu_kemaskini',
            created_by INT UNSIGNED DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            CONSTRAINT fk_audit_forms_user
                FOREIGN KEY (created_by) REFERENCES users(id)
                ON DELETE SET NULL
        )
    ");

    if (!column_exists($conn, 'audit_forms', 'audit_approval_status')) {
        $conn->query("ALTER TABLE audit_forms ADD COLUMN audit_approval_status ENUM('perlu_kemaskini', 'lulus_diaudit') NOT NULL DEFAULT 'perlu_kemaskini' AFTER remarks");
    }

    $conn->query("
        CREATE TABLE IF NOT EXISTS audit_form_assignments (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            audit_form_id INT UNSIGNED NOT NULL,
            assigned_unit VARCHAR(150) NOT NULL,
            audit_approval_status ENUM('perlu_kemaskini', 'lulus_diaudit') NOT NULL DEFAULT 'perlu_kemaskini',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            CONSTRAINT fk_audit_assignment_form
                FOREIGN KEY (audit_form_id) REFERENCES audit_forms(id)
                ON DELETE CASCADE
        )
    ");

    if (!column_exists($conn, 'audit_form_assignments', 'audit_approval_status')) {
        $conn->query("ALTER TABLE audit_form_assignments ADD COLUMN audit_approval_status ENUM('perlu_kemaskini', 'lulus_diaudit') NOT NULL DEFAULT 'perlu_kemaskini' AFTER assigned_unit");
        $conn->query("
            UPDATE audit_form_assignments afa
            INNER JOIN audit_forms af ON af.id = afa.audit_form_id
            SET afa.audit_approval_status = af.audit_approval_status
        ");
    }

    $conn->query("
        CREATE TABLE IF NOT EXISTS audit_form_attachments (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            audit_form_id INT UNSIGNED NOT NULL,
            attachment_path VARCHAR(255) NOT NULL,
            attachment_name VARCHAR(255) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            CONSTRAINT fk_audit_form_attachment_form
                FOREIGN KEY (audit_form_id) REFERENCES audit_forms(id)
                ON DELETE CASCADE
        )
    ");

    $conn->query("
        CREATE TABLE IF NOT EXISTS user_transaction_logs (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id INT UNSIGNED NULL DEFAULT NULL,
            nama VARCHAR(150) NOT NULL,
            jabatan VARCHAR(150) NOT NULL,
            role VARCHAR(100) NOT NULL,
            operasi VARCHAR(255) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_user_transaction_logs_created_at (created_at),
            INDEX idx_user_transaction_logs_user_id (user_id)
        )
    ");

    $conn->query("
        CREATE TABLE IF NOT EXISTS department_access_permissions (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            jabatan VARCHAR(150) NOT NULL,
            module_key VARCHAR(80) NOT NULL,
            can_create TINYINT(1) NOT NULL DEFAULT 0,
            can_read TINYINT(1) NOT NULL DEFAULT 0,
            can_update TINYINT(1) NOT NULL DEFAULT 0,
            can_delete TINYINT(1) NOT NULL DEFAULT 0,
            can_rollback TINYINT(1) NOT NULL DEFAULT 0,
            updated_by INT UNSIGNED NULL DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_department_module_access (jabatan, module_key),
            INDEX idx_department_access_jabatan (jabatan)
        )
    ");

    if (!column_exists($conn, 'department_access_permissions', 'can_rollback')) {
        $conn->query("ALTER TABLE department_access_permissions ADD COLUMN can_rollback TINYINT(1) NOT NULL DEFAULT 0 AFTER can_delete");
    }

    $conn->query("
        CREATE TABLE IF NOT EXISTS user_access_permissions (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id INT UNSIGNED NOT NULL,
            module_key VARCHAR(80) NOT NULL,
            can_create TINYINT(1) NOT NULL DEFAULT 0,
            can_read TINYINT(1) NOT NULL DEFAULT 0,
            can_update TINYINT(1) NOT NULL DEFAULT 0,
            can_delete TINYINT(1) NOT NULL DEFAULT 0,
            can_rollback TINYINT(1) NOT NULL DEFAULT 0,
            updated_by INT UNSIGNED NULL DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_user_module_access (user_id, module_key),
            INDEX idx_user_access_user (user_id),
            CONSTRAINT fk_user_access_permissions_user
                FOREIGN KEY (user_id) REFERENCES users(id)
                ON DELETE CASCADE
        )
    ");

    $conn->query("
        CREATE TABLE IF NOT EXISTS department_access_permission_rollbacks (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            jabatan VARCHAR(150) NOT NULL,
            snapshot LONGTEXT NOT NULL,
            created_by INT UNSIGNED NULL DEFAULT NULL,
            restored_by INT UNSIGNED NULL DEFAULT NULL,
            restored_at DATETIME NULL DEFAULT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_department_access_rollback_jabatan (jabatan),
            INDEX idx_department_access_rollback_restored (restored_at)
        )
    ");

    $conn->query("
        CREATE TABLE IF NOT EXISTS notifications (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id INT UNSIGNED NOT NULL,
            type VARCHAR(50) NOT NULL,
            title VARCHAR(255) NOT NULL,
            message TEXT NOT NULL,
            link VARCHAR(255) DEFAULT NULL,
            is_read TINYINT(1) NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            read_at DATETIME NULL DEFAULT NULL,
            INDEX idx_notifications_user_read (user_id, is_read),
            INDEX idx_notifications_created_at (created_at),
            CONSTRAINT fk_notifications_user
                FOREIGN KEY (user_id) REFERENCES users(id)
                ON DELETE CASCADE
        )
    ");

    $conn->query("
        CREATE TABLE IF NOT EXISTS audit_feedbacks (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            audit_form_id INT UNSIGNED NOT NULL,
            submitted_by INT UNSIGNED NOT NULL,
            submitted_unit VARCHAR(150) NOT NULL,
            submitted_role VARCHAR(100) NOT NULL,
            assigned_to_user_id INT UNSIGNED NULL DEFAULT NULL,
            status_tindakan ENUM('Perlu Tindakan', 'Dalam Tindakan', 'Selesai') NOT NULL DEFAULT 'Perlu Tindakan',
            ringkasan VARCHAR(255) NOT NULL,
            maklum_balas TEXT NOT NULL,
            catatan_audit TEXT NULL DEFAULT NULL,
            catatan_audit_by INT UNSIGNED NULL DEFAULT NULL,
            catatan_audit_at DATETIME NULL DEFAULT NULL,
            attachment_path VARCHAR(255) NOT NULL,
            attachment_name VARCHAR(255) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_audit_feedback_user (audit_form_id, submitted_by),
            CONSTRAINT fk_audit_feedback_form
                FOREIGN KEY (audit_form_id) REFERENCES audit_forms(id)
                ON DELETE CASCADE,
            CONSTRAINT fk_audit_feedback_user
                FOREIGN KEY (submitted_by) REFERENCES users(id)
                ON DELETE CASCADE
        )
    ");

    if (!column_exists($conn, 'audit_feedbacks', 'assigned_to_user_id')) {
        $conn->query("ALTER TABLE audit_feedbacks ADD COLUMN assigned_to_user_id INT UNSIGNED NULL DEFAULT NULL AFTER submitted_role");
    }

    if (!column_exists($conn, 'audit_feedbacks', 'catatan_audit')) {
        $conn->query("ALTER TABLE audit_feedbacks ADD COLUMN catatan_audit TEXT NULL DEFAULT NULL AFTER maklum_balas");
    }

    if (!column_exists($conn, 'audit_feedbacks', 'catatan_audit_by')) {
        $conn->query("ALTER TABLE audit_feedbacks ADD COLUMN catatan_audit_by INT UNSIGNED NULL DEFAULT NULL AFTER catatan_audit");
    }

    if (!column_exists($conn, 'audit_feedbacks', 'catatan_audit_at')) {
        $conn->query("ALTER TABLE audit_feedbacks ADD COLUMN catatan_audit_at DATETIME NULL DEFAULT NULL AFTER catatan_audit_by");
    }

    $conn->query("
        CREATE TABLE IF NOT EXISTS audit_feedback_attachments (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            audit_feedback_id INT UNSIGNED NOT NULL,
            attachment_path VARCHAR(255) NOT NULL,
            attachment_name VARCHAR(255) NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            CONSTRAINT fk_audit_feedback_attachment_feedback
                FOREIGN KEY (audit_feedback_id) REFERENCES audit_feedbacks(id)
                ON DELETE CASCADE
        )
    ");

    $result = $conn->query("SELECT COUNT(*) AS total FROM users WHERE role = 'Admin Audit'");
    $adminCount = $result ? (int) ($result->fetch_assoc()['total'] ?? 0) : 0;

    if ($adminCount === 0) {
        $hashedPassword = password_hash('Admin@123', PASSWORD_DEFAULT);
        $stmt = $conn->prepare("
            INSERT INTO users (nama, ic, emel, role, jabatan, approval_status, account_status, password)
            VALUES (?, ?, ?, 'Admin Audit', ?, 'active', 'active', ?)
        ");
        $nama = 'Admin Dashboard Audit';
        $ic = '900101019999';
        $emel = 'admin@audit.local';
        $jabatan = 'Bahagian Audit Dalam (BAD)';
        $stmt->bind_param('sssss', $nama, $ic, $emel, $jabatan, $hashedPassword);
        $stmt->execute();
    }
}

ensure_audit_schema($conn);

function require_login(): void
{
    if (!isset($_SESSION['user_id'])) {
        header('Location: login.php');
        exit();
    }

    $now = time();
    $maxIdle = SESSION_WARNING_SECONDS + SESSION_GRACE_SECONDS;

    if (isset($_SESSION['last_activity']) && ($now - (int) $_SESSION['last_activity']) > $maxIdle) {
        if (isset($_SESSION['user_id'])) {
            add_current_user_log($GLOBALS['conn'], 'Sesi tamat kerana tidak aktif');
        }
        session_unset();
        session_destroy();
        header('Location: login.php?error=session_expired');
        exit();
    }

    $_SESSION['last_activity'] = $now;
}

function require_roles(array $roles): void
{
    if (($_SESSION['user_role'] ?? '') === 'User Biasa') {
        $_SESSION['user_role'] = 'Pengguna Biasa';
    }
    $currentRole = $_SESSION['user_role'] ?? '';
    if (!in_array($currentRole, $roles, true)) {
        header('Location: dashboard.php');
        exit();
    }
}

function current_user_name(): string
{
    return $_SESSION['user_nama'] ?? 'Pengguna';
}

function current_user_id(): int
{
    return (int) ($_SESSION['user_id'] ?? 0);
}

function current_user_role(): string
{
    if (($_SESSION['user_role'] ?? '') === 'User Biasa') {
        $_SESSION['user_role'] = 'Pengguna Biasa';
    }
    return $_SESSION['user_role'] ?? 'Pengguna Biasa';
}

function current_user_unit(): string
{
    return $_SESSION['user_jabatan'] ?? '-';
}

function current_user_initial(): string
{
    $name = current_user_name();
    return strtoupper(function_exists('mb_substr') ? mb_substr($name, 0, 1) : substr($name, 0, 1));
}

function is_management_role(): bool
{
    return in_array(current_user_role(), ['Admin Audit', 'Pentadbir Sistem'], true);
}

function approval_badge_meta(string $status): array
{
    if ($status === 'active') {
        return ['Diluluskan', 'approval-approved'];
    }
    if ($status === 'rejected') {
        return ['Tidak Diluluskan', 'approval-rejected'];
    }
    return ['Menunggu Kelulusan', 'approval-pending'];
}

function account_badge_meta(string $status): array
{
    if ($status === 'active') {
        return ['Aktif', 'account-active'];
    }
    return ['Tidak Aktif', 'account-inactive'];
}

function add_user_log(mysqli $conn, int $userId, string $nama, string $jabatan, string $role, string $operasi): void
{
    $stmt = $conn->prepare("
        INSERT INTO user_transaction_logs (user_id, nama, jabatan, role, operasi)
        VALUES (?, ?, ?, ?, ?)
    ");
    $stmt->bind_param('issss', $userId, $nama, $jabatan, $role, $operasi);
    $stmt->execute();
}

function add_current_user_log(mysqli $conn, string $operasi): void
{
    add_user_log(
        $conn,
        current_user_id(),
        current_user_name(),
        current_user_unit(),
        current_user_role(),
        $operasi
    );
}

function create_notification(mysqli $conn, int $userId, string $type, string $title, string $message, string $link = ''): void
{
    if ($userId <= 0) {
        return;
    }

    $stmt = $conn->prepare("
        INSERT INTO notifications (user_id, type, title, message, link)
        VALUES (?, ?, ?, ?, ?)
    ");
    $stmt->bind_param('issss', $userId, $type, $title, $message, $link);
    $stmt->execute();
}

function create_notifications_for_unit(mysqli $conn, string $unit, string $type, string $title, string $message, string $link = ''): void
{
    if ($unit === '') {
        return;
    }

    $stmt = $conn->prepare("
        SELECT id
        FROM users
        WHERE jabatan = ?
          AND approval_status = 'active'
          AND account_status = 'active'
    ");
    $stmt->bind_param('s', $unit);
    $stmt->execute();
    $result = $stmt->get_result();

    while ($row = $result->fetch_assoc()) {
        create_notification($conn, (int) ($row['id'] ?? 0), $type, $title, $message, $link);
    }
}

function get_unread_notification_count(mysqli $conn, int $userId): int
{
    if ($userId <= 0) {
        return 0;
    }

    $stmt = $conn->prepare("SELECT COUNT(*) AS total FROM notifications WHERE user_id = ? AND is_read = 0");
    $stmt->bind_param('i', $userId);
    $stmt->execute();
    $row = $stmt->get_result()->fetch_assoc();
    return (int) ($row['total'] ?? 0);
}

function get_recent_notifications(mysqli $conn, int $userId, int $limit = 8): array
{
    if ($userId <= 0) {
        return [];
    }

    $safeLimit = max(1, min($limit, 20));
    $stmt = $conn->prepare("
        SELECT id, type, title, message, link, is_read, created_at
        FROM notifications
        WHERE user_id = ?
        ORDER BY created_at DESC, id DESC
        LIMIT {$safeLimit}
    ");
    $stmt->bind_param('i', $userId);
    $stmt->execute();
    return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}

function mark_notification_as_read(mysqli $conn, int $notificationId, int $userId): void
{
    if ($notificationId <= 0 || $userId <= 0) {
        return;
    }

    $stmt = $conn->prepare("
        UPDATE notifications
        SET is_read = 1, read_at = NOW()
        WHERE id = ? AND user_id = ? AND is_read = 0
    ");
    $stmt->bind_param('ii', $notificationId, $userId);
    $stmt->execute();
}

function mark_all_notifications_as_read(mysqli $conn, int $userId): void
{
    if ($userId <= 0) {
        return;
    }

    $stmt = $conn->prepare("
        UPDATE notifications
        SET is_read = 1, read_at = NOW()
        WHERE user_id = ? AND is_read = 0
    ");
    $stmt->bind_param('i', $userId);
    $stmt->execute();
}

function access_module_options(): array
{
    return [
        'senarai_maklum_balas_audit' => 'Senarai Maklum Balas Audit',
        'senarai_maklumat_audit' => 'Senarai Maklumat Audit',
        'daftar_pengguna' => 'Daftar Pengguna',
        'pengurusan_pendaftaran' => 'Pengurusan Pendaftaran',
        'senarai_pengguna' => 'Senarai Pengguna',
        'transaksi_log_pengguna' => 'Transaksi Log Pengguna',
    ];
}

function access_module_allowed_actions(string $moduleKey): array
{
    if ($moduleKey === 'daftar_pengguna') {
        return ['create', 'read'];
    }

    if ($moduleKey === 'pengurusan_pendaftaran') {
        return ['read', 'update'];
    }

    return ['create', 'read', 'update', 'delete', 'rollback'];
}

function has_department_access(mysqli $conn, string $moduleKey, string $action): bool
{
    if (current_user_role() === 'Pentadbir Sistem') {
        return true;
    }

    if (!in_array(current_user_role(), ['Admin Audit', 'Pengguna Biasa'], true)) {
        return false;
    }

    $columns = [
        'create' => 'can_create',
        'read' => 'can_read',
        'update' => 'can_update',
        'delete' => 'can_delete',
        'rollback' => 'can_rollback',
    ];

    if (
        !isset($columns[$action])
        || !isset(access_module_options()[$moduleKey])
        || !in_array($action, access_module_allowed_actions($moduleKey), true)
    ) {
        return false;
    }

    $column = $columns[$action];
    $userId = current_user_id();

    $userStmt = $conn->prepare("SELECT {$column} AS allowed FROM user_access_permissions WHERE user_id = ? AND module_key = ? LIMIT 1");
    $userStmt->bind_param('is', $userId, $moduleKey);
    $userStmt->execute();
    $userRow = $userStmt->get_result()->fetch_assoc();
    if ($userRow) {
        return !empty($userRow['allowed']);
    }

    $jabatan = current_user_unit();
    $stmt = $conn->prepare("SELECT {$column} AS allowed FROM department_access_permissions WHERE jabatan = ? AND module_key = ? LIMIT 1");
    $stmt->bind_param('ss', $jabatan, $moduleKey);
    $stmt->execute();
    $row = $stmt->get_result()->fetch_assoc();
    return !empty($row['allowed']);
}

function require_department_access(mysqli $conn, string $moduleKey, string $action): void
{
    if (!has_department_access($conn, $moduleKey, $action)) {
        header('Location: akses_dilarang.php');
        exit();
    }
}

function send_password_reset_code(string $recipientEmail, string $recipientName, string $code): bool
{
    $subject = 'Kod Verifikasi Reset Kata Laluan Dashboard Audit MAINPP';
    $safeName = $recipientName !== '' ? $recipientName : 'Pengguna';
    $message = "Assalamualaikum / Salam sejahtera {$safeName},\n\n";
    $message .= "Kod verifikasi untuk reset kata laluan Dashboard Audit MAINPP anda ialah: {$code}\n\n";
    $message .= "Kod ini sah selama 15 minit. Abaikan emel ini jika anda tidak membuat permintaan reset kata laluan.\n\n";
    $message .= "Terima kasih.\nDashboard Audit MAINPP";

    $headers = [
        'From: Dashboard Audit MAINPP <admin@mainpp.gov.my>',
        'Reply-To: admin@mainpp.gov.my',
        'MIME-Version: 1.0',
        'Content-Type: text/plain; charset=UTF-8',
        'X-Mailer: PHP/' . PHP_VERSION,
    ];

    return mail($recipientEmail, $subject, $message, implode("\r\n", $headers));
}
