<?php
// includes/auth.php — Eden RMS auth + DB + RBAC helpers (safe to include multiple times)
declare(strict_types=1);

// --------- load guard (prevents double-include redeclare fatals) ---------
if (defined('EDEN_AUTH_LOADED')) { return; }
define('EDEN_AUTH_LOADED', 1);

// ---- SAFE session utility (idempotent) ----
if (!function_exists('ec_session_start')) {
  function ec_session_start(): void {
    if (session_status() !== PHP_SESSION_ACTIVE) {
      @ini_set('session.use_strict_mode', '1');
      @session_start();
    }
  }
}
ec_session_start(); // ensure session is active for anything that includes auth.php


// --------- ACL map (expand as needed) ----------
$GLOBALS['EC_ACL'] = $GLOBALS['EC_ACL'] ?? [
  'students.view'   => ['super_admin','admin','branch_admin','manager','accountant','coordinator','staff'],
  'students.manage' => ['super_admin','admin','branch_admin'],
];

// --------- user / role helpers ----------
function ec_user(): array {
  foreach (['user','admin'] as $k) {
    if (isset($_SESSION[$k]) && is_array($_SESSION[$k])) return $_SESSION[$k];
  }
  // legacy flags → pseudo user
  foreach (['super_admin','admin','branch_admin','manager','accountant','coordinator','staff'] as $r) {
    if (!empty($_SESSION[$r])) return ['id'=>0,'name'=>ucfirst($r),'email'=>$r.'@local','role'=>$r,'active'=>1];
  }
  return [];
}

function ec_role(): string {
  $u = ec_user();
  $role = strtolower($u['role'] ?? '');
  if ($role !== '') return $role;

  foreach (['super_admin','admin','branch_admin','manager','accountant','coordinator','staff'] as $r) {
    if (!empty($_SESSION[$r])) return $r;
  }
  return 'staff';
}
function ec_role_label(): string { return ucwords(str_replace('_',' ', ec_role())); }

function ec_is_logged_in(): bool {
  $u = ec_user();
  if ($u) return (int)($u['active'] ?? 1) === 1;
  return !empty($_SESSION['logged_in']); // last-resort legacy flag
}

function ec_rank_of(string $role): int {
  static $map = [
    'staff'=>1, 'coordinator'=>2, 'accountant'=>2,
    'manager'=>3, 'branch_admin'=>3, 'admin'=>4, 'super_admin'=>5
  ];
  return $map[strtolower($role)] ?? 0;
}
function ec_require_min_role(string $minRole): void {
  ec_require_login();
  if (ec_rank_of(ec_role()) < ec_rank_of($minRole)) {
    http_response_code(403);
    echo '403 – Forbidden';
    exit;
  }
}

// --------- DB connection (single definition) ----------
function ec_db(): ?mysqli {
  static $db = null;
  if ($db instanceof mysqli && !$db->connect_errno) return $db;

  mysqli_report(MYSQLI_REPORT_OFF);

  // 1) Try app bootstrap first: includes/db.php (support many styles)
  $bootstrap = __DIR__ . '/db.php';
  if (is_file($bootstrap)) {
    @include_once $bootstrap;

    // a) common variables created by db.php
    foreach (['conn','mysqli','db','dbcon','con','connection'] as $var) {
      if (isset($$var) && $$var instanceof mysqli) {
        $db = $$var; $db->set_charset('utf8mb4'); return $db;
      }
    }
    // b) common factory functions
    foreach (['db','connect_db','get_db','getConnection'] as $fn) {
      if (function_exists($fn)) {
        $x = @$fn();
        if ($x instanceof mysqli) { $db = $x; $db->set_charset('utf8mb4'); return $db; }
      }

}
    // c) constants defined in db.php
    if (defined('DB_HOST') && defined('DB_USER') && defined('DB_NAME')) {
      $pass = defined('DB_PASS') ? DB_PASS : '';
      $db = @new mysqli(DB_HOST, DB_USER, $pass, DB_NAME);
      if ($db && !$db->connect_errno) { $db->set_charset('utf8mb4'); return $db; }
    }
    // d) plain variables defined in db.php
    if (isset($host,$user,$name) || isset($hostname,$username,$dbname)) {
      $h = $host     ?? ($hostname ?? 'localhost');
      $u = $user     ?? ($username ?? 'root');
      $p = $pass     ?? ($password ?? '');
      $n = $name     ?? ($dbname   ?? 'edencamp_edencampus');
      $db = @new mysqli((string)$h,(string)$u,(string)$p,(string)$n);
      if ($db && !$db->connect_errno) { $db->set_charset('utf8mb4'); return $db; }
    }
  }

  // 2) Local override file returning ['host'=>...,'user'=>...,'pass'=>...,'name'=>...]
  $localCfg = __DIR__ . '/db.local.php';
  if (is_file($localCfg)) {
    $cfg = @include $localCfg;
    if (is_array($cfg)) {
      $db = @new mysqli((string)($cfg['host'] ?? 'localhost'),
                        (string)($cfg['user'] ?? 'root'),
                        (string)($cfg['pass'] ?? ''),
                        (string)($cfg['name'] ?? 'edencamp_edencampus'));
      if ($db && !$db->connect_errno) { $db->set_charset('utf8mb4'); return $db; }
    }
  }

  // 3) Environment variables
  $envHost = getenv('EDEN_DB_HOST') ?: null;
  $envUser = getenv('EDEN_DB_USER') ?: null;
  $envPass = getenv('EDEN_DB_PASS') ?: null;
  $envName = getenv('EDEN_DB_NAME') ?: null;
  if ($envHost || $envUser || $envName) {
    $db = @new mysqli($envHost ?: 'localhost', $envUser ?: 'root', $envPass ?: '', $envName ?: 'edencamp_edencampus');
    if ($db && !$db->connect_errno) { $db->set_charset('utf8mb4'); return $db; }
  }

  // 4) .env file next to includes/
  $envFile = __DIR__ . '/../.env';
  if (is_file($envFile) && is_readable($envFile)) {
    $kv = [];
    foreach (@file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
      $line = trim($line);
      if ($line==='' || $line[0]==='#') continue;
      $p = explode('=', $line, 2);
      if (count($p)===2) $kv[trim($p[0])] = trim($p[1]);
    }
    $db = @new mysqli($kv['EDEN_DB_HOST'] ?? 'localhost',
                      $kv['EDEN_DB_USER'] ?? 'root',
                      $kv['EDEN_DB_PASS'] ?? '',
                      $kv['EDEN_DB_NAME'] ?? 'edencamp_edencampus');
    if ($db && !$db->connect_errno) { $db->set_charset('utf8mb4'); return $db; }
  }

  // 5) Last-resort fallback (same as before)
  $db = @new mysqli('localhost','root','','edencamp_edencampus');
  if ($db && !$db->connect_errno) { $db->set_charset('utf8mb4'); return $db; }

  return null;
}


// --------- schema guards (guarded to avoid redeclare) ----------
if (!function_exists('db_has')) {
  function db_has(mysqli $db, string $tbl): bool {
    $tbl = $db->real_escape_string($tbl);
    $res = $db->query("SHOW TABLES LIKE '{$tbl}'");
    return $res && $res->num_rows > 0;
  }
}
if (!function_exists('col_exists')) {
  function col_exists(mysqli $db, string $tbl, string $col): bool {
    $tbl = $db->real_escape_string($tbl);
    $col = $db->real_escape_string($col);
    $res = $db->query("SHOW COLUMNS FROM `{$tbl}` LIKE '{$col}'");
    return $res && $res->num_rows > 0;
  }
}
if (!function_exists('table_exists')) {
  function table_exists(mysqli $db, string $tbl): bool { return db_has($db,$tbl); }
}

// --------- login URL + guards ----------
if (!function_exists('ec_login_url')) {
  function ec_login_url(): string {
    $docroot = rtrim($_SERVER['DOCUMENT_ROOT'] ?? '', '/');
    $script  = $_SERVER['SCRIPT_NAME'] ?? '';
    $base    = (strpos($script, '/edenrms/') !== false) ? '/edenrms' : '';

    $candidates = array_unique([
      $base.'/pages/login.php',
      $base.'/login.php',
      '/edenrms/pages/login.php',
      '/edenrms/login.php',
      '/login.php',
    ]);
    foreach ($candidates as $u) {
      if ($docroot && is_file($docroot.$u)) return $u;
    }
    return $candidates[0]; // last resort
  }
}

function ec_require_login(): void {
  if (!ec_is_logged_in()) {
    $next  = $_SERVER['REQUEST_URI'] ?? '';
    $login = ec_login_url();
    header('Location: '.$login.(strpos($login,'?')===false?'?':'&').'next='.rawurlencode($next));
    exit;
  }
}

// --------- session helper ----------
function ec_set_session_user(array $u): void {
  $_SESSION['user'] = [
    'id'        => (int)($u['id'] ?? 0),
    'name'      => $u['name'] ?? ($u['username'] ?? 'User'),
    'email'     => $u['email'] ?? (($u['username'] ?? 'user').'@local'),
    'role'      => strtolower($u['role'] ?? 'staff'),
    'avatar_url'=> $u['avatar_url'] ?? null,
    'active'    => (int)($u['active'] ?? 1),
    'username'  => $u['username'] ?? ($u['email'] ?? 'user'),
  ];
}

// --------- permissions ----------
if (!function_exists('ec_is_admin_all')) {
  // Admin & Super Admin should see everything
  function ec_is_admin_all(): bool {
    $r = strtolower(ec_role());
    return ($r === 'super_admin' || $r === 'admin');
  }
}

if (!function_exists('ec_pick_col')) {
  // Pick first existing column name from candidates
  function ec_pick_col(mysqli $db, string $tbl, array $cands): ?string {
    foreach ($cands as $c) { if (col_exists($db, $tbl, $c)) return $c; }
    return null;
  }
}

if (!function_exists('ec_can')) {
  function ec_can(string $perm): bool {
    $perm = strtolower(trim($perm));
    if ($perm === '') return false;

    // 1) Admins: full access
    if (ec_is_admin_all()) return true;

    // 2) If RBAC tables exist, prefer them and do NOT fall back to ACL
    $db = ec_db();
    if ($db instanceof mysqli && db_has($db,'roles') && db_has($db,'permissions')) {
      static $cache = [];                 // role => set of slugs
      $role = strtolower(ec_role());

      if (!isset($cache[$role])) {
        $cache[$role] = [];
        $rbacDetected = false;

        // Detect slug columns in roles/permissions
        $roleSlugCol = ec_pick_col($db, 'roles',        ['slug','role_slug','name','code','key']);
        $permSlugCol = ec_pick_col($db, 'permissions',  ['slug','name','code','key','perm_code','identifier']);

        /* ---------- Strategy A: pivot with IDs (role_id <-> permission_id) ---------- */
        $pivot = null;
        foreach (['role_permissions','permission_role','role_has_permissions','roles_permissions','permissions_roles','role_permission','roles_perms','role_perms'] as $pt) {
          if (db_has($db, $pt)) { $pivot = $pt; break; }
        }
        if ($pivot && $roleSlugCol && $permSlugCol) {
          $roleIdCol  = ec_pick_col($db, $pivot,       ['role_id','rid','roles_id']);
          $permIdCol  = ec_pick_col($db, $pivot,       ['permission_id','perm_id','pid','permissions_id']);
          $rolesIdCol = ec_pick_col($db, 'roles',      ['id','role_id']);
          $permsIdCol = ec_pick_col($db, 'permissions',['id','permission_id']);

          if ($roleIdCol && $permIdCol && $rolesIdCol && $permsIdCol) {
            $rbacDetected = true;
            $sql = "
              SELECT LOWER(p.`$permSlugCol`) AS slug
                FROM roles r
                JOIN `$pivot` rp ON rp.`$roleIdCol` = r.`$rolesIdCol`
                JOIN permissions p ON p.`$permsIdCol` = rp.`$permIdCol`
               WHERE LOWER(r.`$roleSlugCol`) = ?
            ";
            if ($st = $db->prepare($sql)) {
              $st->bind_param('s', $role);
              $st->execute();
              if ($rs = $st->get_result()) {
                while ($row = $rs->fetch_assoc()) {
                  $s = strtolower((string)$row['slug']);
                  if ($s !== '') $cache[$role][$s] = true;
                }
              }
              $st->close();
            }
          }
        }

        /* ---------- Strategy B: pivot stores slugs directly (role_slug / permission_slug) ---------- */
        if ($pivot && $roleSlugCol && $permSlugCol && !$cache[$role]) {
          $roleSlugPivot = ec_pick_col($db, $pivot, ['role_slug','rslug','role','role_code']);
          $permSlugPivot = ec_pick_col($db, $pivot, ['permission_slug','perm_slug','pslug','permission','perm','permission_code']);
          if ($roleSlugPivot && $permSlugPivot) {
            $rbacDetected = true;
            $sql = "SELECT LOWER(p.`$permSlugCol`) AS slug
                      FROM permissions p
                      JOIN `$pivot` rp ON LOWER(rp.`$permSlugPivot`) = LOWER(p.`$permSlugCol`)
                     WHERE LOWER(rp.`$roleSlugPivot`) = ?";
            if ($st = $db->prepare($sql)) {
              $st->bind_param('s', $role);
              $st->execute();
              if ($rs = $st->get_result()) {
                while ($row = $rs->fetch_assoc()) {
                  $s = strtolower((string)$row['slug']);
                  if ($s !== '') $cache[$role][$s] = true;
                }
              }
              $st->close();
            }
          }
        }

        /* ---------- Strategy C: roles table keeps a list of slugs (JSON/CSV) ---------- */
        if ($roleSlugCol && !$cache[$role]) {
          $permsCol = ec_pick_col($db, 'roles', ['perms','permissions','permission_slugs','allowed']);
          if ($permsCol) {
            $rbacDetected = true;
            $sql = "SELECT `$permsCol` FROM roles WHERE LOWER(`$roleSlugCol`) = ? LIMIT 1";
            if ($st = $db->prepare($sql)) {
              $st->bind_param('s', $role);
              $st->execute();
              $val = (string)($st->get_result()->fetch_column() ?? '');
              $st->close();
              if ($val !== '') {
                $list = json_decode($val, true);
                if (!is_array($list)) $list = preg_split('/[,\s]+/', $val) ?: [];
                foreach ($list as $v) {
                  $v = strtolower(trim((string)$v));
                  if ($v !== '') $cache[$role][$v] = true;
                }
              }
            }
          }
        }

        // If RBAC is present, rely on it strictly (no ACL fallback here)
        if ($rbacDetected === false && !$cache[$role]) {
          // We couldn't detect any RBAC wiring — leave cache empty and allow later ACL fallback.
        }
      }

      // If we built a set for this role, use it strictly
      if ($cache[$role]) return !empty($cache[$role][$perm]);
      // else (RBAC tables exist but unrecognized structure) fall through to ACL
    }

    // 3) Fallback to built-in ACL map (only when RBAC not in use / not detected)
    $map = $GLOBALS['EC_ACL'][$perm] ?? null;
    if (is_array($map) && $map) {
      $r = strtolower(ec_role());
      if (in_array($r, $map, true)) return true;
      foreach ($map as $flag) { if (!empty($_SESSION[$flag])) return true; }
    }

    return false;
  }
}

if (!function_exists('ec_can_any')) {
  function ec_can_any(array $perms): bool {
    foreach ($perms as $p) if (ec_can($p)) return true;
    return false;
  }
}

if (!function_exists('ec_clear_perm_cache')) {
  function ec_clear_perm_cache(): void { /* no-op for now */ }
}

