<?php
@set_time_limit(45);
@ini_set('max_execution_time','45');
// admin/sync_mikrotik.php (FINAL+)
// - Sync PPP Secrets to MikroTik
// - Fixes API param format (=name= etc)
// - After sync, updates DB status + mt_disabled + mt_* fields + is_online
//   so dashboard won't count them "متوقف" بسبب عدم تحديث DB.

if (session_status() === PHP_SESSION_NONE) session_start();

require_once __DIR__ . '/helpers.php';
require_once __DIR__ . '/lang.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/db_bootstrap.php';
require_once __DIR__ . '/receiver_match_refresh_lib.php';
require_once __DIR__ . '/mikrotik_lib.php';
require_once __DIR__ . '/ppp_active_cache_lib.php';
require_once __DIR__ . '/subscriber_id_sequence_lib.php';

require_login('index.php?login=1');
if (function_exists('require_perm')) require_perm('can_mikrotik_sync');

if (function_exists('ensure_db')) ensure_db(true);
$conn = $GLOBALS['conn'] ?? null;
if(!$conn) die('DB connection failed');

if (!function_exists('h')) {
  function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
}

if (!function_exists('ua_mt_rows_error')) {
  function ua_mt_rows_error($rows, string &$msg = ''): bool {
    $msg = '';
    if (!is_array($rows)) return false;
    foreach ($rows as $row) {
      if (is_array($row) && (isset($row['!trap']) || isset($row['message']))) {
        $msg = (string)($row['message'] ?? $row['!trap'] ?? 'MikroTik API error');
        return true;
      }
    }
    return false;
  }
}

$page_title = 'مزامنة MikroTik';
$report = [];
$counts = ['add'=>0,'upd'=>0,'skip'=>0,'fail'=>0];
// NEW: import missing PPPoE secrets from MikroTik into DB as subscribers
$counts['import_add'] = 0;
$counts['import_skip'] = 0;
$counts['import_fail'] = 0;

$doRun = (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') || isset($_GET['run']);
if (!$doRun) {
  $report[] = ['type'=>'warn','text'=>'الصفحة جاهزة. اضغط زر بدء المزامنة لسحب/تحديث حسابات MikroTik.'];
}

/**
 * MikroTik may contain Arabic comments but some tools display them as hex bytes.
 * Try to decode hex-encoded UTF-8 (optionally after a prefix like "SUB#41 ").
 */
function ua_mt_decode_comment(string $comment): string {
  $c = trim($comment);
  if($c==='') return '';
  // strip common prefixes that might precede hex
  // examples: "SUB#41 D8B9D985..." or "SUB#41:D8B9D985..."
  $c2 = preg_replace('~^\s*SUB\s*#?\d+\s*[:\-]?\s*~i', '', $c);
  $c2 = trim((string)$c2);
  $hex = $c2;
  if(preg_match('~^(?:[0-9A-Fa-f]{2})+$~', $hex)){
    $bin = @hex2bin($hex);
    if($bin !== false){
      $txt = @mb_convert_encoding($bin, 'UTF-8', 'UTF-8');
      $txt = trim((string)$txt);
      if($txt !== ''){
        // remove control characters if present
        $txt2 = preg_replace('~[\x00-\x1F\x7F]+~', ' ', $txt);
        $txt2 = trim((string)$txt2);
        if($txt2 !== '') return $txt2;
        return $txt;
      }
    }
  }
  return $c;
}

$routerName = '';
$syncRouter = mt_get_settings($conn);

if ($doRun) {
$api = mt_api_connect($conn);

if($api){

  // ---------- MikroTik command wrapper (ensures '=name=' format) ----------
  $hasCmd  = method_exists($api,'command');
  $hasComm = method_exists($api,'comm');

  $mt = function(string $path, array $params = []) use ($api,$hasCmd,$hasComm){
    if($hasCmd){
      $arr = [$path];
      foreach($params as $k=>$v){
        if($k === '.id') $arr[] = '=.id='.$v;
        elseif(strlen((string)$k) && (($k[0] ?? '') === '?' || ($k[0] ?? '') === '~')) $arr[] = $k.'='.$v;
        else $arr[] = '='.$k.'='.$v;
      }
      return $api->command($arr);
    }
    if($hasComm){
      // بعض libs ترسل بدون '=' وتسبب مشكلة، لكن نتركها كخطة بديلة
      return $api->comm($path, $params);
    }
    return null;
  };

  // router identity
  try{
    $idn = $mt('/system/identity/print', []);
    if(is_array($idn) && isset($idn[0]['name'])) $routerName = $idn[0]['name'];
  }catch(Throwable $e){}

  // ---------- Load existing secrets (name => secret row) ----------
  $existing = [];
  try{
    $se = $mt('/ppp/secret/print', []);
    if(is_array($se)){
      foreach($se as $row){
        if(isset($row['name']) && isset($row['.id'])){
          $existing[(string)$row['name']] = $row;
        }
      }
    }
  }catch(Throwable $e){}

  // ---------- Load active sessions (name => true) ----------
  $activeMap = [];
  try{
    $ac = $mt('/ppp/active/print', []);
    if(is_array($ac)){
      foreach($ac as $row){
        if(isset($row['name'])) $activeMap[(string)$row['name']] = true;
      }
    }
  }catch(Throwable $e){}

  // ---------- Load profile rate-limits to avoid creating non-limited accounts ----------
  $profileRates = [];
  try{
    $pr = $mt('/ppp/profile/print', []);
    if(is_array($pr)){
      foreach($pr as $row){
        if(isset($row['name'])) $profileRates[(string)$row['name']] = trim((string)($row['rate-limit'] ?? ''));
      }
    }
  }catch(Throwable $e){}

  // ---------- Subscribers (non-archived) ----------
  // Build a quick map of known PPP usernames to detect "missing in DB" secrets.
  $known = [];
  $qr = $conn->query("SELECT id, username, ppp_username FROM subscribers WHERE is_deleted=0");
  if($qr){
    while($r = $qr->fetch_assoc()){
      $un = trim((string)($r['username'] ?? ''));
      $pu = trim((string)($r['ppp_username'] ?? ''));
      if($un !== '') $known[strtolower($un)] = (int)($r['id'] ?? 0);
      if($pu !== '') $known[strtolower($pu)] = (int)($r['id'] ?? 0);
    }
  }

  // ---------- IMPORT: MikroTik -> DB (add missing PPPoE secrets) ----------
  // Many operators expect "مزامنة" to bring newly-created secrets into the program.
  // Safe import: only add when not found in DB, only PPPoE, only when password exists.
  if(!empty($existing)){
    $now = date('Y-m-d H:i:s');
    ua_subscriber_ensure_id_sequence($conn);

    $ins = $conn->prepare(
      "INSERT INTO subscribers (full_name, username, password_plain, ppp_username, ppp_password, ppp_profile, is_deleted, ppp_last_sync, mt_source_type, mt_profile, mt_rate_limit, mt_comment, mt_disabled, mt_last_sync, status)\n".
      "VALUES (?,?,?,?,?,?,0,?,'pppoe',?,?,?,?,?,?)"
    );

    foreach($existing as $name=>$row){
      $u = trim((string)$name);
      if($u===''){ $counts['import_skip']++; continue; }

      $service = trim((string)($row['service'] ?? ''));
      if($service !== 'pppoe'){ $counts['import_skip']++; continue; }

      if(isset($known[strtolower($u)])){
        $counts['import_skip']++;
        continue;
      }

      $pass = trim((string)($row['password'] ?? ''));
      if($pass===''){ $counts['import_skip']++; continue; }

      $profile = trim((string)($row['profile'] ?? ''));
      if($profile==='') $profile = 'pppoe-profile';

      $rate    = trim((string)($row['rate-limit'] ?? ''));
      $comment = trim((string)($row['comment'] ?? ''));
      $disabled = 0;
      if(isset($row['disabled'])) $disabled = ((string)$row['disabled'] === 'true') ? 1 : 0;

      $full = ua_mt_decode_comment($comment);
      if($full === '') $full = $u;
      $status = ($disabled === 1) ? 'متوقف' : 'نشط';

      try{
        if($ins){
          // types: full_name(s), username(s), password_plain(s), ppp_username(s), ppp_password(s), ppp_profile(s), ppp_last_sync(s), mt_profile(s), mt_rate_limit(s), mt_comment(s), mt_disabled(i), mt_last_sync(s), status(s)
          $ins->bind_param('ssssssssssiss', $full, $u, $pass, $u, $pass, $profile, $now, $profile, $rate, $comment, $disabled, $now, $status);
          if(@$ins->execute()){
            $counts['import_add']++;
            $known[strtolower($u)] = (int)$conn->insert_id;
            $report[] = ['type'=>'ok','text'=>"تم استيراده من الراوتر ✅ (PPP: {$u})"]; 
          }else{
            $counts['import_fail']++;
            $report[] = ['type'=>'err','text'=>"فشل استيراد PPP: {$u} | DB: ".$conn->error];
          }
        } else {
          $counts['import_fail']++;
          $report[] = ['type'=>'err','text'=>'فشل تجهيز Insert لاستيراد PPP (prepare)'];
          break;
        }
      }catch(Throwable $e){
        $counts['import_fail']++;
        $report[] = ['type'=>'err','text'=>"فشل استيراد PPP: {$u} | ".$e->getMessage()];
      }
    }
    if($ins) $ins->close();
  }

  $subs = $conn->query("SELECT * FROM subscribers WHERE is_deleted=0 ORDER BY id ASC");
  if($subs){
    while($s = $subs->fetch_assoc()){

      $sid   = (int)($s['id'] ?? 0);
      $full  = trim((string)($s['full_name'] ?? ''));
      $u     = trim((string)($s['ppp_username'] ?? ''));
      if($u==='') $u = trim((string)($s['username'] ?? ''));

      $p     = trim((string)($s['ppp_password'] ?? ''));
      if($p==='') $p = trim((string)($s['password_plain'] ?? ''));

      $profile = trim((string)($s['ppp_profile'] ?? ''));
      if($profile==='') $profile = 'pppoe-profile';

      $rate   = trim((string)($s['ppp_rate_limit'] ?? ''));
      $serial = trim((string)($s['mikrotik_serial'] ?? ''));

      if($u===''){
        $counts['skip']++;
        $report[] = ['type'=>'warn','text'=>"{$full} لا يملك PPP Username (تم التخطي)"];
        continue;
      }
      if($p===''){
        $counts['skip']++;
        $report[] = ['type'=>'warn','text'=>"{$full} لا يملك PPP Password (تم التخطي)"];
        continue;
      }

      try{
        // ---- Add/Update secret ----
        if(isset($existing[$u])){
          $id = $existing[$u]['.id'];

          $effectiveRate = trim((string)$rate);
          if($effectiveRate==='' && $profile!=='') $effectiveRate = trim((string)($profileRates[$profile] ?? ''));
          if($profile==='' || (!preg_match('/UNLIMITED/i',$profile) && $effectiveRate==='')){
            $counts['fail']++;
            $report[] = ['type'=>'danger','text'=>"رفض مزامنة {$u}: البروفايل/التقييد غير صالح ولن يتم تفعيل حساب بلا قيود."];
            continue;
          }
          // Fast path: use the already-open API connection instead of reconnecting per subscriber.
          // This removes hundreds of extra TCP/API logins and makes large sync jobs much faster.
          if(!isset($profileRates[$profile]) && function_exists('mt_ppp_profile_ensure') && !mt_ppp_profile_ensure($conn, $profile, $effectiveRate)){
            $counts['fail']++;
            $report[] = ['type'=>'danger','text'=>"فشل تجهيز بروفايل {$profile} للمستخدم {$u}: ".($GLOBALS['mt_last_error'] ?? 'خطأ غير معروف')];
            continue;
          }
          $setParams = ['.id'=>$id, 'password'=>$p, 'service'=>'pppoe', 'profile'=>$profile];
          if($serial !== '') $setParams['comment'] = $serial;
          $res = $mt('/ppp/secret/set', $setParams);
          $msg = '';
          if(!ua_mt_rows_error($res, $msg)){
            $counts['upd']++;
            $report[] = ['type'=>'ok','text'=>"تم تحديثه ✅ (PPP: {$u})"];
            if(isset($existing[$u])){
              $existing[$u]['password'] = $p;
              $existing[$u]['profile'] = $profile;
              if($serial !== '') $existing[$u]['comment'] = $serial;
            }
          } else {
            $counts['fail']++;
            $report[] = ['type'=>'danger','text'=>"فشل تحديث {$u}: ".($msg ?: 'فشل MikroTik')];
            continue;
          }
        } else {
          $effectiveRate = trim((string)$rate);
          if($effectiveRate==='' && $profile!=='') $effectiveRate = trim((string)($profileRates[$profile] ?? ''));
          if($profile==='' || (!preg_match('/UNLIMITED/i',$profile) && $effectiveRate==='')){
            $counts['fail']++;
            $report[] = ['type'=>'danger','text'=>"رفض مزامنة {$u}: البروفايل/التقييد غير صالح ولن يتم إنشاء حساب بلا قيود."];
            continue;
          }
          // Fast path: add directly through the already-open API connection.
          if(!isset($profileRates[$profile]) && function_exists('mt_ppp_profile_ensure') && !mt_ppp_profile_ensure($conn, $profile, $effectiveRate)){
            $counts['fail']++;
            $report[] = ['type'=>'danger','text'=>"فشل تجهيز بروفايل {$profile} للمستخدم {$u}: ".($GLOBALS['mt_last_error'] ?? 'خطأ غير معروف')];
            continue;
          }
          $addParams = ['name'=>$u, 'password'=>$p, 'service'=>'pppoe', 'profile'=>$profile];
          if($serial !== '') $addParams['comment'] = $serial;
          $res = $mt('/ppp/secret/add', $addParams);
          $msg = '';
          if(!ua_mt_rows_error($res, $msg)){
            $counts['add']++;
            $report[] = ['type'=>'ok','text'=>"تمت إضافته ✅ (PPP: {$u})"];
            $existing[$u] = ['name'=>$u, 'password'=>$p, 'service'=>'pppoe', 'profile'=>$profile, 'disabled'=>'false', 'comment'=>$serial];
          } else {
            $counts['fail']++;
            $report[] = ['type'=>'danger','text'=>"فشل إضافة {$u}: ".($msg ?: 'فشل MikroTik')];
            continue;
          }
        }

        // Use cached/known values instead of re-printing every secret after update.
        $currentRow = $existing[$u] ?? [];
        $disabled = (isset($currentRow['disabled']) && (string)$currentRow['disabled'] === 'true') ? 1 : 0;
        $mtComment = (string)($currentRow['comment'] ?? ($serial !== '' ? $serial : ''));
        $mtProfile = (string)($currentRow['profile'] ?? $profile);
        $mtRate    = (string)($currentRow['rate-limit'] ?? ($effectiveRate ?? $rate));

        // ---- Online state from /ppp/active ----
        $isOnline = isset($activeMap[$u]) ? 1 : 0;
        $now = date('Y-m-d H:i:s');

        // ---- Decide DB status: if secret disabled => متوقف else نشط
        // ملاحظة: هيك بتصير لوحة التحكم تطابق الراوتر بدل ما تبقى "متوقف" افتراضيًا.
        $newStatus = ($disabled === 1) ? 'متوقف' : 'نشط';

        // ---- Update DB (ppp_last_sync + mt fields + status + is_online) ----
        $q = "
          UPDATE subscribers SET
            ppp_last_sync   = '".$conn->real_escape_string($now)."',
            mt_source_type  = 'pppoe',
            mt_profile      = '".$conn->real_escape_string($mtProfile)."',
            mt_rate_limit   = '".$conn->real_escape_string($mtRate)."',
            mt_comment      = '".$conn->real_escape_string($mtComment)."',
            mt_disabled     = ".(int)$disabled.",
            mt_last_sync    = '".$conn->real_escape_string($now)."',
            status          = '".$conn->real_escape_string($newStatus)."',
            is_online       = ".(int)$isOnline.",
            last_seen_at    = ".($isOnline ? "'".$conn->real_escape_string($now)."'" : "last_seen_at")."
          WHERE id = {$sid}
        ";
        @ $conn->query($q);

      } catch(Throwable $e){
        $counts['fail']++;
        $report[] = ['type'=>'err','text'=>"فشلت العملية ❌ (PPP: {$u}) | Hint: ".$e->getMessage()];
      }
    }
  } else {
    $counts['fail']++;
    $report[] = ['type'=>'err','text'=>'فشل قراءة المشتركين من قاعدة البيانات ❌'];
  }

  if(function_exists('mt_api_disconnect')) @mt_api_disconnect($api);

} else {
  $counts['fail']++;
  $routerLabel = function_exists('ua_mt_router_label') && is_array($syncRouter)
    ? ua_mt_router_label($syncRouter)
    : 'MikroTik';
  $connectError = trim((string)($GLOBALS['mt_last_error'] ?? 'تعذر الاتصال بالراوتر'));
  $report[] = ['type'=>'err','text'=>'فشل الاتصال بالراوتر '.$routerLabel.' عبر API: '.$connectError];
}

}

// Refresh unified PPP cache after sync, so subscribers page counters match immediately.
if ($doRun) try {
  if (function_exists('ua_ppp_cache_rebuild') && ($conn instanceof mysqli)) {
    ua_ppp_cache_rebuild($conn, 10, true);
  }
  if (function_exists('ua_receiver_match_rebuild_from_cache') && ($conn instanceof mysqli)) {
    ua_receiver_match_rebuild_from_cache($conn, 'mikrotik_sync');
  }
} catch (Throwable $e) {
  if (!is_dir(__DIR__ . '/logs')) @mkdir(__DIR__ . '/logs', 0777, true);
  @file_put_contents(__DIR__ . '/logs/sync.log', '['.date('Y-m-d H:i:s').'] cache refresh failed: '.$e->getMessage().PHP_EOL, FILE_APPEND);
}

// UI
$UA_FORCE_DESIGN_V2 = true;
include __DIR__ . '/header.php';
if (!(defined('UA_EMBED') && UA_EMBED)) include __DIR__ . '/sidebar.php';
?>
<div class="content-wrapper mt-sync-wrap ua-extracted-page ua-subscriber-child-page ua-sub-child-sync-mikrotik">
  <section class="content">
    <div class="ua-page-v2">
      <header class="ua-page-head">
        <div class="ua-page-title">
          <h1>مزامنة مع MikroTik</h1>
          <p>سحب حسابات الراوتر وتحديث بيانات المشتركين داخل البرنامج.</p>
        </div>
      </header>

      <section class="ua-panel-v2 mt-sync-shell">
        <div class="ua-panel-head"><h3 class="card-title">مزامنة حسابات PPPoE مع MikroTik</h3></div>
        <div class="ua-panel-body">

        <?php if(!$doRun): ?>
          <p class="mt-sync-subtitle">اضغط زر البدء لسحب حسابات الراوتر وتحديث بيانات المشتركين داخل البرنامج.</p>
          <form id="mtSyncForm" method="post" action="sync_mikrotik.php?embed=<?= (defined('UA_EMBED') && UA_EMBED) ? '1' : '0' ?>&ua_modal=<?= !empty($_GET['ua_modal']) ? '1' : '0' ?>&ua_sub_modal=<?= !empty($_GET['ua_sub_modal']) ? '1' : '0' ?>&run=1">
            <button type="submit" class="mt-sync-start">بدء المزامنة الآن</button>
            <div class="mt-progress-wrap" id="mtSyncProgress" aria-live="polite">
              <div class="mt-progress-text"><span>جاري تنفيذ المزامنة...</span><strong id="mtSyncPercent">0%</strong></div>
              <div class="mt-progress-track"><div class="mt-progress-bar" id="mtSyncBar"></div></div>
            </div>
          </form>
          <script>
            (function(){
              var form = document.getElementById('mtSyncForm');
              if(!form) return;
              form.addEventListener('submit', function(){
                var btn = form.querySelector('button');
                var wrap = document.getElementById('mtSyncProgress');
                var bar = document.getElementById('mtSyncBar');
                var pct = document.getElementById('mtSyncPercent');
                if(btn){ btn.disabled = true; btn.innerHTML = '⏳ جاري المزامنة...'; }
                if(wrap) wrap.style.display = 'block';
                var v = 0;
                var timer = setInterval(function(){
                  if(v < 70) v += 3;
                  else if(v < 88) v += 1;
                  else if(v < 96) v += 0.3;
                  if(v > 96) v = 96;
                  var n = Math.floor(v);
                  if(bar) bar.style.width = n + '%';
                  if(pct) pct.textContent = n + '%';
                }, 450);
                window.addEventListener('beforeunload', function(){ clearInterval(timer); });
              });
            })();
          </script>
        <?php endif; ?>

        <?php if($routerName): ?>
          <div class="mt-router-pill">✅ الراوتر المتصل: <?=h($routerName)?></div>
        <?php endif; ?>

        <div class="mt-summary-grid">
          <div class="mt-stat add"><span class="label">إضافة</span><span class="value"><?= (int)$counts['add'] ?></span></div>
          <div class="mt-stat upd"><span class="label">تحديث</span><span class="value"><?= (int)$counts['upd'] ?></span></div>
          <div class="mt-stat skip"><span class="label">تخطي</span><span class="value"><?= (int)$counts['skip'] ?></span></div>
          <div class="mt-stat fail"><span class="label">فشل</span><span class="value"><?= (int)$counts['fail'] ?></span></div>
        </div>

        <div class="mt-summary-grid">
          <div class="mt-stat add"><span class="label">استيراد من الراوتر</span><span class="value"><?= (int)$counts['import_add'] ?></span></div>
          <div class="mt-stat skip"><span class="label">موجود مسبقاً/متروك</span><span class="value"><?= (int)$counts['import_skip'] ?></span></div>
          <div class="mt-stat fail"><span class="label">فشل الاستيراد</span><span class="value"><?= (int)$counts['import_fail'] ?></span></div>
          <div class="mt-stat"><span class="label">إجمالي العمليات</span><span class="value"><?= (int)array_sum($counts) ?></span></div>
        </div>

        <div class="mt-report-card">
          <div class="mt-report-head">
            <span>تقرير المزامنة</span>
            <span><?= count($report) ?> عملية</span>
          </div>
          <div class="mt-report-list">
            <?php if(empty($report)): ?>
              <div class="mt-empty-report">لا توجد رسائل لعرضها حالياً.</div>
            <?php else: ?>
              <?php foreach($report as $r):
                $type = (string)($r['type'] ?? 'warn');
                $cls  = in_array($type, ['ok','warn','err','danger'], true) ? $type : 'warn';
                $icon = ($cls==='ok') ? '✅' : (($cls==='warn') ? '⚠️' : '❌');
              ?>
                <div class="mt-log-row <?=h($cls)?>"><span class="ico"><?= $icon ?></span><span><?= h($r['text'] ?? '') ?></span></div>
              <?php endforeach; ?>
            <?php endif; ?>
          </div>
        </div>

        <?php if(!(defined('UA_EMBED') && UA_EMBED)): ?>
          <div class="mt-3">
            <a class="btn btn-secondary" href="subscribers.php">رجوع</a>
          </div>
        <?php endif; ?>
        </div>
      </section>
    </div>
  </section>
</div>
<?php include __DIR__ . '/footer.php'; ?>
