<?php
/*
 * erp-cube — موقع الاشتراكات ولوحة التحكم (PHP 7.4 أو أحدث، بدون قاعدة بيانات).
 * كل الطلبات تمر من هنا عبر ملف .htaccess.
 */
define('ERPQ', 1);
/* رابط لوحة التحكم. غيّره إذا أردت رابطاً آخر (بدون / في النهاية). */
define('ADMIN_PATH', '/panel');
/* اجعلها true إذا كان ملف .htaccess يعمل في استضافتك، لتصبح الروابط بدون index.php */
define('PRETTY_URLS', false);
date_default_timezone_set('Asia/Riyadh');
require __DIR__ . '/lib/core.php';
require __DIR__ . '/lib/views_public.php';
require __DIR__ . '/lib/views_admin.php';
require __DIR__ . '/lib/zatca.php';
require __DIR__ . '/lib/fatoora.php';
require __DIR__ . '/lib/mail.php';
require __DIR__ . '/lib/erp.php';
require __DIR__ . '/lib/integ.php';
require __DIR__ . '/lib/tmail.php';
require __DIR__ . '/lib/support.php';
require __DIR__ . '/lib/partners.php';
require __DIR__ . '/lib/mobile.php';
require __DIR__ . '/lib/mpos.php';
require __DIR__ . '/lib/recurring.php';
require __DIR__ . '/lib/portal.php';
require __DIR__ . '/lib/adminsec.php';
require __DIR__ . '/lib/subs.php';

/* المسار الأساسي (لو رُفع الموقع داخل مجلد فرعي) */
$BASE = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/')), '/');
$GLOBALS['BASE'] = $BASE;
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
$path = rawurldecode($path);
if ($BASE !== '' && strpos($path, $BASE) === 0) $path = substr($path, strlen($BASE));
if (strpos($path, '/index.php') === 0) $path = substr($path, 10);
$path = '/' . trim($path, '/');
/* تبديل اللغة من أي صفحة: ?lang=en أو ?lang=ar */
if (isset($_GET['lang']) && in_array($_GET['lang'], ['ar', 'en'], true)) {
  setcookie('erpq_lang', $_GET['lang'], ['expires' => time() + 31536000, 'path' => (rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/')), '/') ?: '') . '/', 'samesite' => 'Lax']);
  $q = $_GET; unset($q['lang']);
  header('Location: ' . (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/') . ($q ? '?' . http_build_query($q) : ''), true, 303); exit;
}
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';

header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Referrer-Policy: strict-origin-when-cross-origin');
header("Content-Security-Policy: default-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data:; script-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'self'");

function out($html, $code = 200) { http_response_code($code); header('Content-Type: text/html; charset=utf-8'); echo $html; exit; }
function hmac($s) { return rtrim(strtr(base64_encode(hash_hmac('sha256', $s, db()['secret'], true)), '+/', '-_'), '='); }

$d = db();
/* الموقع بالإنجليزية: نصوص الإعدادات والباقات بنسختها الإنجليزية */
if (is_en() && $path !== ADMIN_PATH && strpos($path, ADMIN_PATH . '/') !== 0) $d = site_localize($d);

/* ---------- البرنامج المحاسبي للمشتركين ---------- */
if ($path === '/m/manifest.webmanifest') mobile_manifest($d);
if ($path === '/m/sw.js') mobile_sw();
if ($path === '/pos/manifest.webmanifest') pos_manifest($d);
if (strpos($path, '/partners/') === 0) { partner_serve(substr($path, 10)); out(page_404($d), 404); }
if ($path === '/pos/sw.js') pos_sw();
if (strpos($path, '/f/') === 0 && erp_available()) { erp_public_file($path); out(page_404($d), 404); }
if (strpos($path, '/p/') === 0 && erp_available()) { portal_route($path, $method, $d); out(page_404($d), 404); }
if (strpos($path, '/api/v1/') === 0 && erp_available()) erp_api_v1($path, $method);
if (in_array($path, ['/login', '/login/2fa', '/logout', '/app', '/m', '/pos', '/account/password', '/renew/pay', '/switch'], true) || strpos($path, '/api/') === 0 || strpos($path, '/oauth/') === 0) {
  erp_route($path, $method, $d);
}

/* مهمة مجدولة: إرسال الفواتير المعلقة إلى منصة فاتورة */
if ($path === '/cron') {
  header('Content-Type: text/plain; charset=utf-8');
  if (!hash_equals(erp_cron_key($d), (string)($_GET['key'] ?? ''))) { http_response_code(403); exit('forbidden'); }
  @set_time_limit(300);
  $out = [];
  if (erp_available() && erp_backup_now()) $out[] = 'backup ok';
  if (erp_available() && ($ob = offsite_send())) $out[] = 'offsite ' . ($ob['ok'] ? 'ok' : 'failed: ' . $ob['error']);
  $rm = subs_remind(); if ($rm) $out[] = 'reminders: ' . implode(', ', $rm);
  /* الفوترة المتكررة */
  foreach (erp_q("SELECT DISTINCT tenant_id FROM docs WHERE col = 'contracts' AND deleted = 0")->fetchAll() as $r) {
    $tt = erp_tenant((int)$r['tenant_id']);
    if (!$tt || erp_state($tt) !== 'active') continue;
    $n = rc_run((int)$r['tenant_id']);
    if ($n) $out[] = 'recurring ' . $r['tenant_id'] . ': ' . implode(', ', $n);
  }
  foreach (erp_q('SELECT tenant_id FROM zatca_cfg')->fetchAll() as $r) {
    $n = erp_flush((int)$r['tenant_id'], 50, 300);
    if (array_sum($n)) $out[] = $r['tenant_id'] . ': ' . json_encode($n);
  }
  if (erp_available()) { try { $out = array_merge($out, integ_cron()); } catch (Throwable $e) { $out[] = 'integ: ' . $e->getMessage(); } }
  exit('ok ' . date('Y-m-d H:i:s') . "\n" . implode("\n", $out));
}

/* ---------- الدفع الإلكتروني وإشعارات المتاجر ---------- */
if (strpos($path, '/pay/') === 0 && erp_available()) integ_public_route($path, $method, $d);
if (strpos($path, '/hooks/') === 0 && erp_available()) { integ_hooks($path, $d); out(page_404($d), 404); }
/* تذكير التجديد مرة يومياً بعد الرد (لو ما فيه مهمة مجدولة) */
if ((($d['settings']['remindDay'] ?? '') !== today()) && $path !== '/cron') register_shutdown_function(function () { if (function_exists('fastcgi_finish_request')) @fastcgi_finish_request(); elseif (function_exists('litespeed_finish_request')) @litespeed_finish_request(); try { subs_remind(); } catch (Throwable $e) {} });
/* رابط التجديد من رسالة التذكير (موقّع، بدون تسجيل دخول) */
if ($path === '/subscribe/renew' && erp_available()) {
  $ref = (string)($_GET['ref'] ?? ''); $r = null;
  if ($ref !== '' && hash_equals(sub_renew_key($ref), (string)($_GET['k'] ?? ''))) foreach ($d['requests'] as $x) if ($x['ref'] === $ref) $r = $x;
  if (!$r || $r['status'] !== 'active') redirect('/');
  $tok = site_pay_start($d, $r, 'renew');
  redirect($tok ? '/pay/' . $tok : '/contact');
}
if ($path === '/subscribe/pay' && erp_available()) {
  $ref = (string)($_GET['ref'] ?? ''); $r = null;
  if ($ref !== '' && hash_equals(substr(hmac('t|' . $ref), 0, 16), (string)($_GET['k'] ?? ''))) foreach ($d['requests'] as $x) if ($x['ref'] === $ref) $r = $x;
  if (!$r || $r['status'] !== 'new') redirect('/');
  $tok = site_pay_start($d, $r, 'new');
  if (!$tok) redirect('/thanks?ref=' . rawurlencode($ref) . '&k=' . rawurlencode((string)$_GET['k']));
  redirect('/pay/' . $tok);
}

/* ---------- الموقع العام ---------- */
if ($path !== ADMIN_PATH && strpos($path, ADMIN_PATH . '/') !== 0) {
  if ($path === '/robots.txt') { header('Content-Type: text/plain'); echo "User-agent: *\nDisallow: " . ADMIN_PATH . "\n"; exit; }
  if ($path === '/health') { header('Content-Type: text/plain'); echo 'ok'; exit; }

  if ($method === 'POST' && $path === '/subscribe') {
    $b = $_POST;
    if (!empty($b['website'])) redirect('/thanks');
    if (rate_limited('sub:' . client_ip(), 6, 3600)) out(page_subscribe($d, $b, [], (t('أرسلت طلبات كثيرة خلال وقت قصير. حاول بعد ساعة أو تواصل معنا مباشرة') . '.')), 429);
    $v = [
      'plan' => clip($b['plan'] ?? '', 60), 'cycle' => (($b['cycle'] ?? '') === 'yearly') ? 'yearly' : 'monthly',
      'company' => clip($b['company'] ?? '', 120), 'name' => clip($b['name'] ?? '', 80), 'phone' => norm_phone(clip($b['phone'] ?? '', 30)),
      'email' => strtolower(clip($b['email'] ?? '', 120)), 'city' => clip($b['city'] ?? '', 60), 'cr' => clip($b['cr'] ?? '', 20),
      'vat' => clip($b['vat'] ?? '', 20), 'activity' => clip($b['activity'] ?? '', 120), 'notes' => clip($b['notes'] ?? '', 1500),
    ];
    $plan = null;
    foreach (db()['plans'] as $p) if ($p['id'] === $v['plan'] && !empty($p['active'])) $plan = $p;
    $e = [];
    if (!$plan) $e['plan'] = (t('اختر الباقة') . '.');
    if ($v['company'] === '') $e['company'] = (t('اكتب اسم المنشأة') . '.');
    if ($v['name'] === '') $e['name'] = (t('اكتب اسم المسؤول') . '.');
    if (!valid_phone($v['phone'])) $e['phone'] = (t('اكتب رقم جوال سعودي صحيح، مثل') . ' 0551234567.');
    if (!valid_email($v['email'])) $e['email'] = (t('اكتب بريداً إلكترونياً صحيحاً') . '.');
    if ($v['vat'] !== '' && !valid_vat($v['vat'])) $e['vat'] = (t('الرقم الضريبي 15 رقماً يبدأ وينتهي بالرقم') . ' 3.');
    if ($v['cr'] !== '' && !preg_match('/^\d{10}$/', $v['cr'])) $e['cr'] = (t('السجل التجاري 10 أرقام') . '.');
    if (empty($b['agree'])) $e['agree'] = (t('وافق على الشروط والأحكام وسياسة الخصوصية لنكمل الطلب') . '.');
    if ($e) out(page_subscribe($d, $b, $e, (t('راجع الحقول المظللة ثم أرسل الطلب') . '.')), 400);
    // تجربة مجانية: يُنشأ الحساب فوراً بدون انتظار التفعيل
    if (($b['mode'] ?? '') === 'trial') {
      $res = sub_start_trial($v, $plan);
      if (!empty($res['error'])) out(page_subscribe($d, $b, [], $res['error']), 400);
      out(page_trial_ready($d, $res));
    }
    $ref = db_tx(function (&$db) use ($v, $plan) {
      $db['counters']['req'] = ($db['counters']['req'] ?? 0) + 1;
      $ref = 'EQ-' . date('ym') . '-' . str_pad((string)$db['counters']['req'], 4, '0', STR_PAD_LEFT);
      $db['requests'][] = [
        'id' => rid(), 'ref' => $ref, 'createdAt' => time(), 'status' => 'new',
        'planId' => $plan['id'], 'planName' => $plan['name'], 'cycle' => $v['cycle'],
        'price' => ($pp = plan_price($plan, $v['cycle']))['price'], 'basePrice' => $pp['base'],
        'offerLabel' => $pp['offer'] ? trim((string)($pp['offer']['label'] ?? '')) : '', 'offerFirstOnly' => $pp['offer'] ? !empty($pp['offer']['firstOnly']) : false,
        'company' => $v['company'], 'name' => $v['name'], 'phone' => $v['phone'], 'email' => $v['email'], 'city' => $v['city'],
        'cr' => $v['cr'], 'vat' => $v['vat'], 'activity' => $v['activity'], 'notes' => $v['notes'], 'ip' => client_ip(),
        'history' => [['at' => time(), 'status' => 'new', 'note' => t('طلب من الموقع')]],
      ];
      return $ref;
    });
    redirect('/thanks?ref=' . rawurlencode($ref) . '&k=' . substr(hmac('t|' . $ref), 0, 16));
  }

  if ($method === 'POST' && $path === '/contact') {
    $b = $_POST;
    if (!empty($b['website'])) redirect('/contact?sent=1');
    $topics = [t('استفسار عن الباقات'), t('طلب عرض تجريبي'), t('دعم فني'), t('الفوترة والاشتراك'), t('أخرى')];
    $v = ['name' => clip($b['name'] ?? '', 80), 'phone' => norm_phone(clip($b['phone'] ?? '', 30)), 'email' => clip($b['email'] ?? '', 120), 'topic' => in_array($b['topic'] ?? '', $topics, true) ? $b['topic'] : '', 'message' => clip($b['message'] ?? '', 2000)];
    $e = [];
    if (rate_limited('msg:' . client_ip(), 8, 3600)) $e['message'] = (t('أرسلت رسائل كثيرة. حاول لاحقاً') . '.');
    if ($v['name'] === '') $e['name'] = (t('اكتب اسمك') . '.');
    if (!valid_phone($v['phone'])) $e['phone'] = (t('اكتب رقم جوال صحيح') . '.');
    if ($v['email'] !== '' && !valid_email($v['email'])) $e['email'] = (t('البريد غير صحيح') . '.');
    if (mb_strlen($v['message']) < 5) $e['message'] = $e['message'] ?? (t('اكتب رسالتك') . '.');
    if ($e) out(page_contact($d, $b, $e), 400);
    db_tx(function (&$db) use ($v) {
      $db['counters']['msg'] = ($db['counters']['msg'] ?? 0) + 1;
      $db['messages'][] = ['id' => rid(), 'createdAt' => time(), 'read' => false] + $v;
    });
    $to = trim($d['settings']['email'] ?? '');
    if ($to !== '' && function_exists('mail_send')) {
      $txt = "رسالة جديدة من صفحة تواصل معنا\n\nالاسم: {$v['name']}\nالجوال: {$v['phone']}\nالبريد: {$v['email']}\nالموضوع: {$v['topic']}\n\n{$v['message']}";
      @mail_send($to, (t('رسالة جديدة') . ': ') . ($v['topic'] ?: t('تواصل معنا')) . ' — ' . $v['name'], '<div dir="rtl" style="font-family:Tahoma,Arial;white-space:pre-line">' . h($txt) . '</div>', $txt);
    }
    redirect('/contact?sent=1');
  }

  if ($method === 'GET' || $method === 'HEAD') {
    if ($path === '/') out(page_home($d));
    if ($path === '/pricing') out(page_pricing($d));
    if ($path === '/subscribe') out(page_subscribe($d, ['plan' => $_GET['plan'] ?? '', 'cycle' => $_GET['cycle'] ?? '', 'trial' => !empty($_GET['trial'])]));
    if ($path === '/contact') out(page_contact($d, [], [], ($_GET['sent'] ?? '') === '1'));
    if ($path === '/privacy') out(page_legal($d, 'privacy'));
    if ($path === '/terms') out(page_legal($d, 'terms'));
    if ($path === '/thanks') {
      $ref = (string)($_GET['ref'] ?? ''); $r = null;
      if ($ref !== '' && hash_equals(substr(hmac('t|' . $ref), 0, 16), (string)($_GET['k'] ?? ''))) {
        foreach ($d['requests'] as $x) if ($x['ref'] === $ref) $r = $x;
      }
      $payUrl = ($r && $r['status'] === 'new' && erp_available() && pay_ready($d['settings']['sitePay'] ?? []) && (float)$r['price'] > 0) ? '/subscribe/pay?ref=' . rawurlencode($ref) . '&k=' . rawurlencode((string)$_GET['k']) : '';
      out(page_thanks($d, $r, $payUrl));
    }
  }
  out(page_404($d), 404);
}

/* ---------- لوحة التحكم ---------- */
session_name('erpq_admin');
session_set_cookie_params(['lifetime' => 0, 'path' => ($BASE ?: '') . '/', 'secure' => is_https(), 'httponly' => true, 'samesite' => 'Lax']);
session_start();
header('Cache-Control: no-store');

function logged_in() {
  $a = db()['admin'] ?? null;
  return $a && !empty($_SESSION['admin']) && ($_SESSION['v'] ?? 0) === ($a['v'] ?? 1) && ($_SESSION['exp'] ?? 0) > time();
}
function login_now() {
  session_regenerate_id(true);
  $_SESSION['admin'] = true; $_SESSION['v'] = db()['admin']['v'] ?? 1; $_SESSION['exp'] = time() + 12 * 3600;
  $_SESSION['csrf'] = bin2hex(random_bytes(16));
}
function csrf_ok() { return isset($_SESSION['csrf'], $_POST['_csrf']) && hash_equals($_SESSION['csrf'], (string)$_POST['_csrf']); }

// الإعداد الأول
if (empty($d['admin'])) {
  if ($path === ADMIN_PATH . '/setup' && $method === 'POST') {
    if (rate_limited('setup:' . client_ip(), 10, 3600)) out(adm_setup($d, (t('محاولات كثيرة. حاول بعد ساعة') . '.')), 429);
    $code = setup_code();
    if ($code === '' || strtoupper(trim((string)($_POST['code'] ?? ''))) !== $code) out(adm_setup($d, (t('رمز الإعداد غير صحيح') . '.')), 400);
    $pw = (string)($_POST['password'] ?? '');
    if (mb_strlen($pw) < 10) out(adm_setup($d, (t('كلمة المرور قصيرة، استخدم 10 أحرف على الأقل') . '.')), 400);
    if ($pw !== (string)($_POST['password2'] ?? '')) out(adm_setup($d, (t('كلمتا المرور غير متطابقتين') . '.')), 400);
    db_tx(function (&$db) use ($pw) { $db['admin'] = ['hash' => password_hash($pw, PASSWORD_DEFAULT), 'v' => 1]; });
    @unlink(SETUP_FILE);
    login_now();
    redirect(ADMIN_PATH);
  }
  out(adm_setup($d));
}

if ($path === ADMIN_PATH . '/login') {
  if ($method === 'POST') {
    if (rate_limited('login:' . client_ip(), 8, 900)) out(adm_login($d, (t('محاولات كثيرة. انتظر 15 دقيقة ثم حاول مجدداً') . '.')), 429);
    if (!password_verify((string)($_POST['password'] ?? ''), $d['admin']['hash'])) out(adm_login($d, (t('كلمة المرور غير صحيحة') . '.')), 401);
    // التحقق بخطوتين: الخطوة الثانية خلال 10 دقائق
    if (adm_tfa_on()) { session_regenerate_id(true); $_SESSION['adm_pre'] = time(); redirect(ADMIN_PATH . '/login/2fa'); }
    login_now();
    redirect(ADMIN_PATH);
  }
  if (logged_in()) redirect(ADMIN_PATH);
  out(adm_login($d));
}

if ($path === ADMIN_PATH . '/login/2fa') {
  $pre = (int)($_SESSION['adm_pre'] ?? 0);
  if (!$pre || time() - $pre > 600 || !adm_tfa_on()) { unset($_SESSION['adm_pre']); redirect(ADMIN_PATH . '/login'); }
  if ($method === 'POST') {
    if (rate_limited('login2fa:' . client_ip(), 8, 900)) out(adm_tfa_page($d, 'محاولات كثيرة. انتظر 15 دقيقة ثم حاول مجدداً.'), 429);
    if (!adm_tfa_verify((string)($_POST['code'] ?? ''))) out(adm_tfa_page($d, 'الرمز غير صحيح. تأكد من وقت الجوال واكتب الرمز الظاهر الآن.'), 401);
    unset($_SESSION['adm_pre']); login_now(); redirect(ADMIN_PATH);
  }
  out(adm_tfa_page($d));
}

if (!logged_in()) redirect(ADMIN_PATH . '/login');
if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(16));
if ($method === 'POST' && !csrf_ok()) { http_response_code(403); header('Content-Type: text/plain; charset=utf-8'); exit((t('انتهت صلاحية الصفحة. ارجع وأعد المحاولة') . '.')); }
$flashes = ['saved' => (t('حُفظت التغييرات') . '.'), 'pw' => (t('تم تغيير كلمة المرور') . '.'), 'del' => (t('تم الحذف') . '.')];
$flash = $flashes[$_GET['ok'] ?? ''] ?? '';
$m = [];

if ($path === ADMIN_PATH . '/logout' && $method === 'POST') {
  $_SESSION = [];
  session_destroy();
  redirect(ADMIN_PATH . '/login');
}
if ($path === ADMIN_PATH) out(adm_dashboard($d, $flash));
if ($path === ADMIN_PATH . '/requests') {
  $st = (string)($_GET['status'] ?? '');
  out(adm_requests($d, array_key_exists($st, STATUS) ? $st : '', clip($_GET['q'] ?? '', 80), $flash));
}
if ($path === ADMIN_PATH . '/export.csv') {
  $rows = [[t('رقم الطلب'), t('التاريخ'), t('الحالة'), t('المنشأة'), t('المسؤول'), t('الجوال'), t('البريد'), t('المدينة'), t('السجل'), t('الرقم الضريبي'), t('الباقة'), t('الدورة'), t('السعر'), t('بداية'), t('نهاية')]];
  foreach ($d['requests'] as $r) {
    $rows[] = [$r['ref'], date('Y-m-d', (int)$r['createdAt']), st_label($r['status'])[0], $r['company'], $r['name'], $r['phone'], $r['email'], $r['city'], $r['cr'], $r['vat'], $r['planName'], $r['cycle'] === 'yearly' ? t('سنوي') : t('شهري'), $r['price'], $r['startDate'] ?? '', $r['endDate'] ?? ''];
  }
  header('Content-Type: text/csv; charset=utf-8');
  header('Content-Disposition: attachment; filename="requests-' . today() . '.csv"');
  $fh = fopen('php://output', 'w');
  fwrite($fh, "\xEF\xBB\xBF");
  foreach ($rows as $r) fputcsv($fh, $r);
  fclose($fh);
  exit;
}

if (preg_match('#^' . preg_quote(ADMIN_PATH, '#') . '/requests/([a-f0-9]+)(?:/(status|renew|delete|account|modules))?$#', $path, $m)) {
  $id = $m[1]; $act = $m[2] ?? '';
  $r = null; foreach ($d['requests'] as $x) if ($x['id'] === $id) $r = $x;
  if (!$r) out(page_404($d), 404);
  if ($act === '') out(adm_request($d, $r, $flash));
  if ($method !== 'POST') redirect(ADMIN_PATH . '/requests/' . $id);
  if ($act === 'account') {
    if (erp_available()) {
      $res = erp_reset_owner($id);
      if ($res) $_SESSION['acct'] = ['rid' => $id, 'pw' => $res['pw'], 'email' => $res['email'], 'mail' => mail_welcome($res['email'], $r['name'], $res['pw'], $r['company'], true)];
      else $_SESSION['acct_err'] = (t('لا يوجد حساب لهذا الطلب بعد') . '.');
    }
    redirect(ADMIN_PATH . '/requests/' . $id);
  }
  if ($act === 'modules') {
    $custom = ($_POST['mode'] ?? '') === 'custom'; $sel = array_values(array_intersect(array_keys(MODULES), (array)($_POST['modules'] ?? [])));
    db_tx(function (&$db) use ($id, $custom, $sel) {
      foreach ($db['requests'] as &$x) if ($x['id'] === $id) {
        if ($custom) { $x['modOverride'] = $sel; $x['modKnown'] = array_keys(MODULES); } else unset($x['modOverride'], $x['modKnown']);
        $x['history'][] = ['at' => time(), 'status' => $x['status'], 'note' => $custom ? 'تخصيص خدمات المشترك (' . count($sel) . ' خدمة)' : 'الخدمات حسب الباقة'];
      }
    });
    redirect(ADMIN_PATH . '/requests/' . $id . '?ok=saved');
  }
  if ($act === 'delete') {
    if (erp_available()) erp_q("UPDATE tenants SET status = 'suspended' WHERE request_id = ?", [$id]);
    db_tx(function (&$db) use ($id) { $db['requests'] = array_values(array_filter($db['requests'], function ($x) use ($id) { return $x['id'] !== $id; })); });
    redirect(ADMIN_PATH . '/requests?ok=del');
  }
  $post = $_POST;
  db_tx(function (&$db) use ($id, $act, $post) {
    foreach ($db['requests'] as &$x) {
      if ($x['id'] !== $id) continue;
      if ($act === 'renew') {
        if ($x['status'] === 'active' && !empty($x['endDate'])) {
          if (empty($x['trial']) && !empty($x['offerFirstOnly'])) { $x['price'] = renew_price($x); $x['offerFirstOnly'] = false; }
          unset($x['trial']);
          $s = DateTime::createFromFormat('Y-m-d', $x['endDate']); $s->modify('+1 day');
          $x['endDate'] = add_cycle($s->format('Y-m-d'), $x['cycle']);
          $x['history'][] = ['at' => time(), 'status' => 'active', 'note' => (t('تجديد حتى') . ' ') . $x['endDate']];
        }
      } else {
        $st = array_key_exists((string)($post['status'] ?? ''), STATUS) ? $post['status'] : $x['status'];
        $note = clip($post['note'] ?? '', 500);
        if ($st === 'active') {
          foreach ($db['plans'] as $p) if ($p['id'] === ($post['plan'] ?? '')) { $x['planId'] = $p['id']; $x['planName'] = $p['name']; }
          $x['cycle'] = (($post['cycle'] ?? '') === 'yearly') ? 'yearly' : 'monthly';
          if (isset($post['price']) && $post['price'] !== '') { $np = max(0, (float)$post['price']); if (abs($np - (float)($x['price'] ?? 0)) > 0.004) $x['offerFirstOnly'] = false; $x['price'] = $np; }
          $sd = preg_match('/^\d{4}-\d{2}-\d{2}$/', $post['startDate'] ?? '') ? $post['startDate'] : today();
          if ($x['status'] !== 'active' || ($x['startDate'] ?? '') !== $sd) { $x['startDate'] = $sd; $x['endDate'] = add_cycle($sd, $x['cycle']); unset($x['trial']); }
        }
        if ($st !== $x['status'] || $note !== '') $x['history'][] = ['at' => time(), 'status' => $st, 'note' => $note !== '' ? $note : ($st === 'active' ? (t('تفعيل حتى') . ' ') . $x['endDate'] : '')];
        $x['status'] = $st;
      }
    }
    unset($x);
  });
  if (erp_available()) {
    $fresh = null;
    foreach (db()['requests'] as $x) if ($x['id'] === $id) $fresh = $x;
    if ($fresh) {
      try {
        $res = erp_sync_request($fresh);
        if (!empty($res['pw'])) $_SESSION['acct'] = ['rid' => $id, 'pw' => $res['pw'], 'email' => $res['email'], 'mail' => mail_welcome($res['email'], $fresh['name'], $res['pw'], $fresh['company'], false, $fresh['endDate'] ?? '')];
        if (!empty($res['error'])) $_SESSION['acct_err'] = $res['error'];
      } catch (Throwable $e) { $_SESSION['acct_err'] = (t('تعذر إنشاء حساب البرنامج') . ': ') . $e->getMessage(); }
    }
  }
  redirect(ADMIN_PATH . '/requests/' . $id . '?ok=saved');
}

function offer_from_post() {
  $d = function ($k) { $v = (string)($_POST[$k] ?? ''); return preg_match('/^\d{4}-\d{2}-\d{2}$/', $v) ? $v : ''; };
  return [
    'on' => !empty($_POST['offer_on']), 'type' => ($_POST['offer_type'] ?? '') === 'price' ? 'price' : 'percent',
    'percent' => max(0, min(99, round((float)($_POST['offer_percent'] ?? 0), 2))),
    'monthly' => max(0, round((float)($_POST['offer_monthly'] ?? 0), 2)), 'yearly' => max(0, round((float)($_POST['offer_yearly'] ?? 0), 2)),
    'applies' => in_array($_POST['offer_applies'] ?? '', ['monthly', 'yearly'], true) ? $_POST['offer_applies'] : 'both',
    'label' => clip($_POST['offer_label'] ?? '', 60), 'labelEn' => clip($_POST['offer_labelEn'] ?? '', 60),
    'from' => $d('offer_from'), 'to' => $d('offer_to'), 'firstOnly' => !empty($_POST['offer_firstOnly']),
  ];
}
function plan_from_post($p) {
  $num = function ($x) { return max(0, round((float)$x, 2)); };
  $feats = array_slice(array_values(array_filter(array_map('trim', preg_split('/\r?\n/', (string)($_POST['features'] ?? ''))), 'strlen')), 0, 20);
  return array_merge($p, [
    'name' => clip($_POST['name'] ?? '', 60), 'tagline' => clip($_POST['tagline'] ?? '', 120), 'users' => clip($_POST['users'] ?? '', 30) ?: '1',
    'priceMonthly' => $num($_POST['priceMonthly'] ?? 0), 'priceYearly' => $num($_POST['priceYearly'] ?? 0),
    'features' => array_map(function ($f) { return clip($f, 120); }, $feats),
    'order' => (int)($_POST['order'] ?? 0), 'active' => !empty($_POST['active']), 'highlighted' => !empty($_POST['highlighted']),
    'modules' => array_values(array_intersect(array_keys(MODULES), (array)($_POST['modules'] ?? []))), 'modulesKnown' => array_keys(MODULES),
    'offer' => offer_from_post(),
    'nameEn' => clip($_POST['nameEn'] ?? '', 60), 'taglineEn' => clip($_POST['taglineEn'] ?? '', 120),
    'featuresEn' => array_map(function ($f) { return clip($f, 120); }, array_slice(array_values(array_filter(array_map('trim', preg_split('/\r?\n/', (string)($_POST['featuresEn'] ?? ''))), 'strlen')), 0, 20)),
  ]);
}

if ($path === ADMIN_PATH . '/plans') out(adm_plans($d, $flash));
if ($path === ADMIN_PATH . '/plans/new') {
  if ($method === 'POST') {
    $pl = plan_from_post(['id' => '']);
    if ($pl['name'] === '') out(adm_plan_form($d, $pl, (t('اكتب اسم الباقة') . '.')), 400);
    $slug = trim(preg_replace('/[^\p{L}\p{N}]+/u', '-', mb_strtolower($pl['name'])), '-');
    $pl['id'] = (mb_substr($slug, 0, 24) ?: 'plan') . '-' . rid(2);
    db_tx(function (&$db) use ($pl) { $db['plans'][] = $pl; });
    redirect(ADMIN_PATH . '/plans?ok=saved');
  }
  out(adm_plan_form($d, ['active' => true, 'features' => []]));
}
if (preg_match('#^' . preg_quote(ADMIN_PATH, '#') . '/plans/([^/]+)/(edit|toggle|delete)$#', $path, $m)) {
  $pid = $m[1]; $act = $m[2];
  $pl = null; foreach ($d['plans'] as $p) if ($p['id'] === $pid) $pl = $p;
  if (!$pl) out(page_404($d), 404);
  if ($act === 'edit' && $method !== 'POST') out(adm_plan_form($d, $pl));
  if ($method !== 'POST') redirect(ADMIN_PATH . '/plans');
  if ($act === 'edit') {
    $upd = plan_from_post($pl);
    if ($upd['name'] === '') out(adm_plan_form($d, $upd, (t('اكتب اسم الباقة') . '.')), 400);
  }
  db_tx(function (&$db) use ($pid, $act, &$upd) {
    foreach ($db['plans'] as $i => $p) {
      if ($p['id'] !== $pid) continue;
      if ($act === 'edit') $db['plans'][$i] = $upd;
      elseif ($act === 'toggle') $db['plans'][$i]['active'] = empty($p['active']);
      elseif ($act === 'delete') { array_splice($db['plans'], $i, 1); break; }
    }
  });
  redirect(ADMIN_PATH . '/plans?ok=' . ($act === 'delete' ? 'del' : 'saved'));
}

sup_admin_route($path, $method, $d, $flash);
partners_admin_route($path, $method, $d, $flash);
if ($path === ADMIN_PATH . '/messages') {
  out(adm_messages($d, $flash));
}
if (preg_match('#^' . preg_quote(ADMIN_PATH, '#') . '/messages/([a-f0-9]+)/(read|delete)$#', $path, $m) && $method === 'POST') {
  $mid = $m[1]; $act = $m[2];
  db_tx(function (&$db) use ($mid, $act) {
    foreach ($db['messages'] as $i => $x) {
      if ($x['id'] !== $mid) continue;
      if ($act === 'read') $db['messages'][$i]['read'] = true; else { array_splice($db['messages'], $i, 1); break; }
    }
  });
  redirect(ADMIN_PATH . '/messages');
}

/* الأمان: التحقق بخطوتين والنسخة الاحتياطية خارج السيرفر */
if ($path === ADMIN_PATH . '/security') out(adm_security($d, $flash, isset($_SESSION['tfa_new_adm']) && !empty($_GET['setup']) ? ['secret' => $_SESSION['tfa_new_adm']] : []));
if ($path === ADMIN_PATH . '/security/2fa-start' && $method === 'POST') { $_SESSION['tfa_new_adm'] = totp_b32e(random_bytes(20)); redirect(ADMIN_PATH . '/security?setup=1#tfa'); }
if ($path === ADMIN_PATH . '/security/2fa-on' && $method === 'POST') {
  $sec = (string)($_SESSION['tfa_new_adm'] ?? '');
  if ($sec === '') redirect(ADMIN_PATH . '/security');
  $st = totp_match($sec, (string)($_POST['code'] ?? ''));
  if (!$st) out(adm_security($d, '', ['secret' => $sec, 'err' => 'الرمز غير صحيح. تأكد من وقت الجوال واكتب الرمز الظاهر الآن.']));
  $codes = totp_recovery_codes();
  db_tx(function (&$db) use ($sec, $st, $codes) { $db['admin']['totp'] = $sec; $db['admin']['totp_on'] = true; $db['admin']['totp_last'] = $st; $db['admin']['totp_rec'] = array_map(function ($c) { return password_hash($c, PASSWORD_DEFAULT); }, $codes); });
  unset($_SESSION['tfa_new_adm']);
  out(adm_security(db(), 'تفعّل التحقق بخطوتين.', ['codes' => array_map(function ($c) { return substr($c, 0, 5) . '-' . substr($c, 5); }, $codes)]));
}
if ($path === ADMIN_PATH . '/security/2fa-off' && $method === 'POST') {
  if (!password_verify((string)($_POST['password'] ?? ''), $d['admin']['hash']) || !adm_tfa_verify((string)($_POST['code'] ?? ''))) out(adm_security($d, 'كلمة المرور أو الرمز غير صحيح.'));
  db_tx(function (&$db) { unset($db['admin']['totp'], $db['admin']['totp_on'], $db['admin']['totp_last'], $db['admin']['totp_rec']); });
  redirect(ADMIN_PATH . '/security?ok=saved');
}
if ($path === ADMIN_PATH . '/security/offsite' && $method === 'POST') {
  $email = strtolower(clip($_POST['email'] ?? '', 120)); $pass = (string)($_POST['pass'] ?? ''); $on = !empty($_POST['on']);
  if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) out(adm_security($d, 'البريد غير صحيح.'));
  if ($pass !== '' && strlen($pass) < 8) out(adm_security($d, 'كلمة مرور التشفير 8 أحرف على الأقل.'));
  db_tx(function (&$db) use ($email, $pass, $on) { $o = (array)($db['settings']['offsite'] ?? []); $o['email'] = $email; if ($pass !== '') $o['pass'] = $pass; $o['on'] = $on; $o['last'] = ''; $db['settings']['offsite'] = $o; });
  redirect(ADMIN_PATH . '/security?ok=saved#offsite');
}
if ($path === ADMIN_PATH . '/security/offsite-now' && $method === 'POST') { @set_time_limit(240); session_write_close(); $r = offsite_send(true); out(adm_security(db(), '', ['sent' => $r ?: ['ok' => false, 'error' => 'لم يُرسل']])); }
if ($path === ADMIN_PATH . '/backup/now' && $method === 'POST') { @set_time_limit(240); erp_backup_now(true); redirect(ADMIN_PATH . '/system?ok=saved'); }
if ($path === ADMIN_PATH . '/backup') {
  $f = basename((string)($_GET['f'] ?? ''));
  $full = DATA_DIR . '/backups/' . $f;
  if (!preg_match('/^(erp-\d{4}-\d{2}-\d{2}\.sqlite|db-\d{4}-\d{2}-\d{2}\.json|offsite-\d{4}-\d{2}-\d{2}\.zip)$/', $f) || !is_file($full)) out(page_404($d), 404);
  header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . $f . '"'); header('Content-Length: ' . filesize($full)); header('Cache-Control: no-store');
  readfile($full); exit;
}
if ($path === ADMIN_PATH . '/system') {
  $test = null;
  if ($method === 'POST') { @set_time_limit(240); session_write_close(); $test = erp_sandbox_test(); }
  out(adm_system($d, $test));
}
if ($path === ADMIN_PATH . '/settings') {
  if ($method === 'POST') {
    $post = $_POST;
    db_tx(function (&$db) use ($post) {
      $s = &$db['settings'];
      foreach (['siteName', 'tagline', 'heroTitle', 'heroText', 'phone', 'email', 'city', 'yearlyNote'] as $k) $s[$k] = clip($post[$k] ?? '', $k === 'heroText' ? 400 : 160);
      $s['whatsapp'] = substr(preg_replace('/\D/', '', (string)($post['whatsapp'] ?? '')), 0, 15);
      if ($s['siteName'] === '') $s['siteName'] = 'erp-cube';
      $faq = [];
      foreach (preg_split('/\r?\n\s*\r?\n/', (string)($post['faq'] ?? '')) as $blk) {
        $lines = array_values(array_filter(array_map('trim', preg_split('/\r?\n/', $blk)), 'strlen'));
        if (count($lines) >= 2) $faq[] = ['q' => clip($lines[0], 200), 'a' => clip(implode(' ', array_slice($lines, 1)), 800)];
      }
      $s['faq'] = array_slice($faq, 0, 20);
    });
    redirect(ADMIN_PATH . '/settings?ok=saved');
  }
  out(adm_settings($d, $flash));
}
if ($path === ADMIN_PATH . '/pages') {
  if ($method === 'POST') {
    $post = $_POST;
    db_tx(function (&$db) use ($post) {
      $s = &$db['settings'];
      $oldDef = ['privacy' => legal_default('privacy', $s), 'terms' => legal_default('terms', $s)]; $oldS = $s;
      foreach (['legalName' => 160, 'crNumber' => 30, 'vatNumber' => 30, 'address' => 400, 'hours' => 300] as $k => $n) $s[$k] = clip($post[$k] ?? '', $n);
      foreach (['privacy', 'terms'] as $k) {
        $v = str_replace("\r\n", "\n", (string)($post[$k] ?? ''));
        $v = mb_substr($v, 0, 30000);
        if (trim($v) === trim(legal_default($k, $s)) || trim($v) === trim($oldDef[$k])) $v = '';
        if ($v !== ($s[$k] ?? '') || empty($s[$k . 'Updated'])) $s[$k . 'Updated'] = date('Y-m-d');
        $s[$k] = $v;
      }
      foreach (['privacy', 'terms'] as $k) {
        if (!isset($post[$k . 'En'])) continue;
        $v = mb_substr(str_replace("\r\n", "\n", (string)$post[$k . 'En']), 0, 30000);
        if (trim($v) === trim(legal_default_en($k, $s)) || trim($v) === trim(legal_default_en($k, $oldS))) $v = '';
        $s[$k . 'En'] = $v;
      }
    });
    redirect(ADMIN_PATH . '/pages?ok=saved');
  }
  out(adm_pages($d, $flash));
}
if ($path === ADMIN_PATH . '/settings/subs' && $method === 'POST') {
  $days = array_slice(array_values(array_unique(array_filter(array_map('intval', preg_split('/[\s,،]+/', (string)($_POST['remindDays'] ?? ''))), function ($x) { return $x >= 0 && $x <= 60; }))), 0, 6);
  rsort($days);
  db_tx(function (&$db) use ($days) { $db['settings']['subs'] = ['trialOn' => !empty($_POST['trialOn']), 'trialDays' => max(1, min(60, (int)($_POST['trialDays'] ?? 14))), 'grace' => max(0, min(30, (int)($_POST['grace'] ?? 3))), 'remindOn' => !empty($_POST['remindOn']), 'remindDays' => $days ?: [7, 3, 1]]; });
  redirect(ADMIN_PATH . '/settings?ok=saved#subs');
}
if ($path === ADMIN_PATH . '/settings/smtp' && $method === 'POST') {
  $post = $_POST;
  db_tx(function (&$db) use ($post) {
    $old = $db['settings']['smtp'] ?? [];
    $sec = in_array($post['secure'] ?? '', ['ssl', 'tls', 'none'], true) ? $post['secure'] : 'ssl';
    $db['settings']['smtp'] = ['host' => clip(preg_replace('/[^A-Za-z0-9.\-]/', '', (string)($post['host'] ?? '')), 120), 'port' => max(1, min(65535, (int)($post['port'] ?? 465))), 'secure' => $sec,
      'user' => clip($post['user'] ?? '', 160), 'pass' => ($post['pass'] ?? '') !== '' ? (string)$post['pass'] : ($old['pass'] ?? ''),
      'from' => filter_var(trim((string)($post['from'] ?? '')), FILTER_VALIDATE_EMAIL) ?: '', 'fromName' => clip($post['fromName'] ?? '', 80)];
  });
  redirect(ADMIN_PATH . '/settings?ok=saved#smtp');
}
if ($path === ADMIN_PATH . '/settings/smtp-test' && $method === 'POST') {
  $to = trim((string)($_POST['to'] ?? ''));
  $site = $d['settings']['siteName'] ?? 'erp-cube';
  $res = mail_send($to, "رسالة تجربة من $site", ('<div dir="rtl" style="font-family:Tahoma,Arial">' . t('وصلت رسالة التجربة. إعدادات البريد في') . ' ') . h($site) . (' ' . t('تعمل') . '.</div>'), "وصلت رسالة التجربة. إعدادات البريد في $site تعمل.");
  $_SESSION['smtp_test'] = $res['ok'] ? ['ok' => true, 'msg' => (t('أُرسلت رسالة التجربة إلى') . ' ') . $to . ('. ' . t('تأكد من وصولها (وافحص مجلد الرسائل غير المرغوب فيها') . ').')] : ['ok' => false, 'msg' => (t('تعذر الإرسال') . ': ') . $res['error']];
  redirect(ADMIN_PATH . '/settings#smtp');
}
if ($path === ADMIN_PATH . '/settings/en' && $method === 'POST') {
  $post = $_POST;
  db_tx(function (&$db) use ($post) {
    $s = &$db['settings'];
    foreach (['tagline' => 160, 'heroTitle' => 160, 'heroText' => 400, 'yearlyNote' => 160, 'address' => 400, 'hours' => 300] as $k => $n) $s[$k . 'En'] = clip($post[$k . 'En'] ?? '', $n);
    $faq = [];
    foreach (preg_split('/\r?\n\s*\r?\n/', (string)($post['faqEn'] ?? '')) as $blk) {
      $lines = array_values(array_filter(array_map('trim', preg_split('/\r?\n/', $blk)), 'strlen'));
      if (count($lines) >= 2) $faq[] = ['q' => clip($lines[0], 200), 'a' => clip(implode(' ', array_slice($lines, 1)), 800)];
    }
    $s['faqEn'] = array_slice($faq, 0, 20);
  });
  redirect(ADMIN_PATH . '/settings?ok=saved#en');
}
if ($path === ADMIN_PATH . '/settings/pay' && $method === 'POST') {
  $post = $_POST;
  db_tx(function (&$db) use ($post) {
    $old = $db['settings']['sitePay'] ?? [];
    $g = isset(PAY_FIELDS[$post['gateway'] ?? '']) ? $post['gateway'] : '';
    $c = ['gateway' => $g];
    if ($g !== '') foreach (PAY_FIELDS[$g] as $k => $l) { $v = trim((string)($post['sp_' . $g . '_' . $k] ?? '')); $c[$k] = $v === '' && ($old['gateway'] ?? '') === $g ? ($old[$k] ?? '') : clip($v, 400); }
    $db['settings']['sitePay'] = $c;
    $oa = $db['settings']['apps'] ?? [];
    $a = [];
    foreach (['salla_id', 'salla_secret', 'salla_hook', 'zid_id', 'zid_secret', 'foodics_id', 'foodics_secret', 'foodics_sandbox'] as $k) { $v = trim((string)($post[$k] ?? '')); $a[$k] = $v === '' && strpos($k, '_id') === false && $k !== 'foodics_sandbox' ? ($oa[$k] ?? '') : clip($v, 300); }
    $db['settings']['apps'] = $a;
  });
  redirect(ADMIN_PATH . '/settings?ok=saved#pay');
}
if ($path === ADMIN_PATH . '/password' && $method === 'POST') {
  if (!password_verify((string)($_POST['current'] ?? ''), $d['admin']['hash'])) out(adm_settings($d, '', (t('كلمة المرور الحالية غير صحيحة') . '.')), 400);
  $pw = (string)($_POST['password'] ?? '');
  if (mb_strlen($pw) < 10) out(adm_settings($d, '', (t('كلمة المرور الجديدة قصيرة، استخدم 10 أحرف على الأقل') . '.')), 400);
  if ($pw !== (string)($_POST['password2'] ?? '')) out(adm_settings($d, '', (t('كلمتا المرور غير متطابقتين') . '.')), 400);
  db_tx(function (&$db) use ($pw) { $db['admin'] = ['hash' => password_hash($pw, PASSWORD_DEFAULT), 'v' => ($db['admin']['v'] ?? 1) + 1]; });
  login_now();
  redirect(ADMIN_PATH . '/settings?ok=pw');
}

out(page_404($d), 404);
