<?php
// ============================================================
//  MediCore — Authentication & Session Helper
//  File: includes/auth.php
// ============================================================

if (session_status() === PHP_SESSION_NONE) {
    // 8-hour session — appropriate for a POS/business system kept open
    // all day. Default PHP timeout (24 min of inactivity) is far too
    // short and was causing staff to be logged out mid-shift.
    $sessionLifetime = 8 * 60 * 60; // 28800 seconds
    ini_set('session.gc_maxlifetime', (string)$sessionLifetime);

    session_start([
        'cookie_httponly' => true,
        'cookie_samesite' => 'Lax', // 'Strict' can break some same-site
                                     // navigations (e.g. opening print/
                                     // thermal windows); 'Lax' keeps CSRF
                                     // protection while staying reliable.
        'cookie_path'      => '/',  // explicit — without this, some PHP/
                                     // server configs scope the session
                                     // cookie to the current directory,
                                     // so AJAX calls from /api/ never
                                     // receive it and look "logged out".
        'cookie_lifetime'  => $sessionLifetime,
        'gc_maxlifetime'   => $sessionLifetime,
    ]);
}

require_once __DIR__ . '/db.php';

// ── All modules with display names ───────────────────────────
// Used by permissions system across settings + header
define('APP_MODULES', [
    'dashboard'      => ['label' => 'Dashboard',         'icon' => '📊'],
    'product_list'   => ['label' => 'Product List',      'icon' => '📋'],
    'companies'      => ['label' => 'Companies',         'icon' => '🏭'],
    'generics'       => ['label' => 'Generics',          'icon' => '🧬'],
    'categories'     => ['label' => 'Categories',        'icon' => '🏷️'],
    'requisition'    => ['label' => 'Requisition',       'icon' => '📝'],
    'req_record'     => ['label' => 'Requisition Record','icon' => '🗂'],
    'expired'        => ['label' => 'Expired Products',  'icon' => '⏰'],
    'supplier'       => ['label' => 'Supplier List',     'icon' => '🏢'],
    'purchase_entry' => ['label' => 'Purchase Entry',    'icon' => '📦'],
    'purchase_list'  => ['label' => 'Purchase Record',   'icon' => '📥'],
    'customer'       => ['label' => 'Customer List',     'icon' => '👤'],
    'sale_entry'     => ['label' => 'Sale Entry',        'icon' => '🛒'],
    'sale_list'      => ['label' => 'Sale Record',       'icon' => '📤'],
    'cash'           => ['label' => 'Cash Balance',      'icon' => '💵'],
    'investment'     => ['label' => 'Investment',        'icon' => '🏦'],
    'accounts'       => ['label' => 'Income / Expense',  'icon' => '💰'],
    'profit'         => ['label' => 'Profit Report',     'icon' => '📈'],
    // Separate from the 'profit' module above (which just gates the
    // Profit Report page's sidebar link). This one controls whether a
    // user sees profit *figures* anywhere they surface — Sale Entry's
    // Buy%/Profit% columns, Product List's margin %, the Dashboard
    // profit KPI/chart, Sale Record's profit column, and the numbers
    // inside the Profit Report page itself. See can_view_profit().
    'profit_view'    => ['label' => 'Profit Visibility', 'icon' => '💹'],
    'settings'       => ['label' => 'Settings',          'icon' => '⚙️'],
    'reporting_engine' => ['label' => 'Reporting Engine', 'icon' => '📊'],
    // 'view' action = "View Product History" from the spec. Read-only —
    // Product History has no create/edit/delete action of its own, it's
    // just a report over stock_ledger.
    'product_history'   => ['label' => 'Product History',    'icon' => '📜'],
    // 'view' action = "View Stock Modification Log", 'create' action =
    // "Create Stock Modification" — the two separate permissions the
    // spec asks for both live under this one module, same as every
    // other module's view/create/edit/delete split elsewhere in can().
    'stock_modification' => ['label' => 'Stock Modification', 'icon' => '🛠️'],
]);

// ── Permission check ──────────────────────────────────────────
/**
 * Check if current user has permission for a module.
 * Admin always returns true.
 * $level = 'view' | 'full'
 * Permissions loaded once per request (static cache).
 */
// Granular action types usable as the 2nd arg of can()
const PERM_ACTIONS = ['view','create','edit','delete'];

function can(string $module, string $action = 'view'): bool {
    if (is_admin()) return true;

    static $perms = null;
    if ($perms === null) {
        $uid  = current_user_id();
        $rows = $uid
            ? db_rows(
                "SELECT module, level, actions FROM user_permissions WHERE user_id = ?",
                [$uid]
              )
            : [];
        $perms = [];
        foreach ($rows as $r) {
            $perms[$r['module']] = [
                'level'   => $r['level'],
                'actions' => $r['actions'] ?: '',
            ];
        }
    }

    $p = $perms[$module] ?? null;
    if (!$p) return false; // no row = none

    // 'full' level always grants every action
    if ($p['level'] === 'full') return true;

    // Granular custom action list (level='custom', actions='view,create,edit')
    if ($p['actions']) {
        $allowed = array_map('trim', explode(',', $p['actions']));
        if (in_array('full', $allowed)) return true;
        return in_array($action, $allowed);
    }

    // Legacy/simple 'view' level — only the 'view' action is permitted
    if ($p['level'] === 'view') return $action === 'view';

    return false;
}

// ── Require module permission (for page-level guard) ─────────
function require_permission(string $module, string $level = 'view'): void {
    require_login();
    if (!can($module, $level)) {
        if (is_ajax_request()) {
            http_response_code(403);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error'   => 'You do not have permission to perform this action.',
            ]);
            exit;
        }
        http_response_code(403);
        die('
        <div style="font-family:sans-serif;padding:60px;text-align:center">
          <div style="font-size:40px;margin-bottom:12px">🔒</div>
          <h2 style="color:#2d3a8c;margin-bottom:8px">Access Denied</h2>
          <p style="color:#666">You do not have permission to access this page.</p>
          <a href="javascript:history.back()"
             style="color:#2d3a8c;font-weight:600">← Go Back</a>
        </div>');
    }
}

// ── Require login ─────────────────────────────────────────────
function is_ajax_request(): bool {
    return (
        (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
            && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest')
        || (!empty($_SERVER['CONTENT_TYPE'])
            && stripos($_SERVER['CONTENT_TYPE'], 'application/json') !== false)
        || strpos($_SERVER['SCRIPT_NAME'] ?? '', '/api/') !== false
    );
}

function require_login(): void {
    if (empty($_SESSION['user_id'])) {
        if (is_ajax_request()) {
            http_response_code(401);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error'   => 'Session expired. Please log in again.',
                'session_expired' => true,
            ]);
            exit;
        }
        header('Location: ' . base_url('login.php'));
        exit;
    }
}

// ── Require admin role ────────────────────────────────────────
function require_admin(): void {
    require_login();
    if (!is_admin()) {
        if (is_ajax_request()) {
            http_response_code(403);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error'   => 'Access denied. Admin role required.',
            ]);
            exit;
        }
        http_response_code(403);
        die('<p style="font-family:sans-serif;padding:40px">
              Access denied. Admin role required.
             </p>');
    }
}

// ── Login / Logout ────────────────────────────────────────────
function attempt_login(string $username, string $password): bool {
    $user = db_row(
        "SELECT id, name, username, password, role, avatar
           FROM users
          WHERE username = ? AND is_active = 1",
        [$username]
    );
    if (!$user || !password_verify($password, $user['password'])) {
        return false;
    }
    session_regenerate_id(true);
    $_SESSION['user_id']   = $user['id'];
    $_SESSION['user_name'] = $user['name'];
    $_SESSION['user_role'] = $user['role'];
    $_SESSION['user_av']   = $user['avatar'] ?: strtoupper(substr($user['name'], 0, 2));
    return true;
}

function logout(): void {
    session_unset();
    session_destroy();
    header('Location: ' . base_url('login.php'));
    exit;
}

// ── Current user helpers ──────────────────────────────────────
function current_user_id(): int      { return (int)($_SESSION['user_id']   ?? 0); }
function current_user_name(): string { return $_SESSION['user_name'] ?? 'Guest'; }
function current_user_role(): string { return $_SESSION['user_role'] ?? ''; }
function current_user_av(): string   { return $_SESSION['user_av']   ?? '??'; }
function is_admin(): bool            { return in_array(current_user_role(), ['admin','super_admin']); }
function is_super_admin(): bool      { return current_user_role() === 'super_admin'; }

// ── Profit visibility permission ──────────────────────────────
// Single gate used everywhere a profit figure could appear (Sale
// Entry's Buy%/Profit% columns, Product List margin %, Dashboard
// profit KPI/chart, Sale Record profit column, Profit Report page).
// Admin always passes (via can()). Runs the one-time backfill
// migration lazily on first check so existing managers — who could
// already see profit via the old hardcoded role check — don't lose
// that visibility the moment this permission ships (see
// ensure_profit_view_migrated() in includes/db.php).
function can_view_profit(): bool {
    ensure_profit_view_migrated();
    return can('profit_view');
}

// Photo is deliberately NOT kept in $_SESSION (base64 data-URIs can run to
// ~2MB — fine as a DB column, too heavy to duplicate into every session
// file). Queried fresh once per request instead, same DB-is-truth approach
// used for topbar notification/task counts. Statically cached so header.php
// rendering the topbar avatar never issues more than one query for it.
function current_user_photo(): string {
    static $photo = null;
    if ($photo !== null) return $photo;
    $uid = current_user_id();
    if (!$uid) return $photo = '';
    ensure_users_photo_column();
    $val = db_val("SELECT photo FROM users WHERE id = ?", [$uid]);
    return $photo = ($val !== null ? (string)$val : '');
}

// ── URL helper ────────────────────────────────────────────────
function base_url(string $path = ''): string {
    $base = defined('APP_BASE') ? APP_BASE : '';
    return $base . '/' . ltrim($path, '/');
}
