<?php
// admin/api/subscriber_static_ip_suggest.php
// Suggest an editable PPPoE static IP that is currently free according to MikroTik + DB.

@ini_set('display_errors', '0');
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
if (!ob_get_level()) { @ob_start(); }

register_shutdown_function(function(){
  $err = error_get_last();
  if ($err && in_array($err['type'] ?? 0, [E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR], true)) {
    while (ob_get_level()) { @ob_end_clean(); }
    header('Content-Type: application/json; charset=utf-8');
    http_response_code(500);
    echo json_encode(['ok'=>false,'message'=>'خطأ داخلي أثناء اقتراح IP: '.$err['message']], JSON_UNESCAPED_UNICODE);
  }
});

if (session_status() === PHP_SESSION_NONE) { @session_start(); }
header('Content-Type: application/json; charset=utf-8');

require_once __DIR__ . '/../auth.php';
require_once __DIR__ . '/../db_bootstrap.php';
require_once __DIR__ . '/../helpers.php';
require_once __DIR__ . '/../settings_store.php';
require_once __DIR__ . '/../subscribers_lib.php';
require_once __DIR__ . '/../mikrotik_lib.php';

function ua_ip_suggest_out(array $arr, int $code=200): void {
  http_response_code($code);
  while (ob_get_level()) { @ob_end_clean(); }
  header('Content-Type: application/json; charset=utf-8');
  echo json_encode($arr, JSON_UNESCAPED_UNICODE);
  exit;
}

require_login();
if (function_exists('has_perm')) {
  if (!has_perm('can_edit_subscriber') && !has_perm('can_add_subscriber') && !has_perm('can_mikrotik_sync')) {
    ua_ip_suggest_out(['ok'=>false,'message'=>'لا تملك صلاحية اقتراح IP ثابت.'], 403);
  }
} elseif(function_exists('require_perm')) {
  @require_perm('can_edit_subscriber');
}
$conn = function_exists('ensure_db') ? ensure_db(true) : ($GLOBALS['conn'] ?? null);
if (!($conn instanceof mysqli)) ua_ip_suggest_out(['ok'=>false,'message'=>'تعذر الاتصال بقاعدة البيانات.'], 500);

function ua_sip_uint_to_ip(int $n): string {
  // long2ip works with signed/unsigned values on 64-bit PHP; this keeps it stable for private/public ranges.
  if ($n > 2147483647) $n = $n - 4294967296;
  return long2ip($n) ?: '';
}

function ua_sip_ip_to_uint(string $ip): ?int {
  if (function_exists('mt_ipv4_to_uint')) return mt_ipv4_to_uint($ip);
  if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return null;
  $v = ip2long($ip);
  if ($v === false) return null;
  return (int)sprintf('%u', $v);
}

function ua_sip_range_to_bounds(string $range): ?array {
  $range = trim($range);
  if ($range === '') return null;

  // MikroTik pool format: 10.10.10.2-10.10.10.254 ; also support CIDR for future static setting.
  if (strpos($range, '/') !== false) {
    [$ip, $bits] = array_map('trim', explode('/', $range, 2));
    $base = ua_sip_ip_to_uint($ip);
    $bits = (int)$bits;
    if ($base === null || $bits < 0 || $bits > 32) return null;
    $mask = $bits === 0 ? 0 : ((0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF);
    $start = $base & $mask;
    $end = $start | (~$mask & 0xFFFFFFFF);
    return [$start, $end];
  }

  if (strpos($range, '-') !== false) {
    [$a, $b] = array_map('trim', explode('-', $range, 2));
    $na = ua_sip_ip_to_uint($a); $nb = ua_sip_ip_to_uint($b);
    if ($na === null || $nb === null) return null;
    if ($na > $nb) { $t = $na; $na = $nb; $nb = $t; }
    return [$na, $nb];
  }

  $one = ua_sip_ip_to_uint($range);
  return $one === null ? null : [$one, $one];
}

function ua_sip_range_label(array $r): string {
  return trim((string)($r['range'] ?? ''));
}

function ua_sip_ranges_from_setting(string $raw): array {
  $out = [];
  $raw = trim($raw);
  if ($raw === '') return $out;
  foreach (preg_split('/[\r\n,;]+/', $raw) as $range) {
    $range = trim((string)$range);
    if ($range === '') continue;
    if (ua_sip_range_to_bounds($range)) {
      $out[] = ['name'=>'Static setting', 'range'=>$range, 'source'=>'setting'];
    }
  }
  return $out;
}

function ua_sip_db_used(mysqli $conn, int $currentId): array {
  $used = [];
  if (!function_exists('table_exists') || !function_exists('col_exists')) return $used;
  if (!table_exists($conn, 'subscribers') || !col_exists($conn, 'subscribers', 'ip_address')) return $used;
  $where = "TRIM(COALESCE(ip_address,''))<>''";
  if ($currentId > 0) $where .= " AND id<>".(int)$currentId;
  if (col_exists($conn, 'subscribers', 'is_deleted')) $where .= " AND COALESCE(is_deleted,0)=0";
  $rs = @ $conn->query("SELECT id, username, full_name, ppp_username, ip_address FROM subscribers WHERE $where");
  if ($rs) {
    while ($row = $rs->fetch_assoc()) {
      $ip = trim((string)($row['ip_address'] ?? ''));
      if ($ip !== '') {
        $who = trim((string)($row['ppp_username'] ?? $row['username'] ?? ''));
        $used[$ip] = 'DB'.($who !== '' ? ' / '.$who : '');
      }
    }
  }
  return $used;
}

function ua_sip_add_used_ip(array &$used, string $ip, string $source): void {
  $ip = trim($ip);
  if ($ip !== '' && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) $used[$ip] = $source;
}

$id = (int)($_GET['id'] ?? $_POST['id'] ?? 0);
if (!function_exists('table_exists') || !table_exists($conn, 'subscribers')) ua_ip_suggest_out(['ok'=>false,'message'=>'جدول المشتركين غير موجود.'], 500);

$sub = [];
if ($id > 0) {
  $where = function_exists('col_exists') && col_exists($conn,'subscribers','is_deleted') ? ' AND COALESCE(is_deleted,0)=0 ' : '';
  $rs = @ $conn->query("SELECT * FROM subscribers WHERE id=".(int)$id." $where LIMIT 1");
  if (!$rs || !$rs->num_rows) ua_ip_suggest_out(['ok'=>false,'message'=>'المشترك غير موجود.'], 404);
  $sub = $rs->fetch_assoc();
}

$currentUser = trim((string)($_GET['ppp_user'] ?? $_POST['ppp_user'] ?? ''));
if ($currentUser === '') $currentUser = trim((string)($sub['ppp_username'] ?? $sub['username'] ?? ''));
$currentProfile = trim((string)($_GET['profile'] ?? $_POST['profile'] ?? ''));
if ($currentProfile === '') $currentProfile = trim((string)($sub['ppp_profile'] ?? $sub['pppoe_profile'] ?? ''));
if ($currentProfile === '') $currentProfile = trim((string)setting_get($conn,'pppoe_default_profile',''));
$skipUser = $id > 0 ? $currentUser : '';

try {
  $api = mt_api($conn);
  if (!$api) ua_ip_suggest_out(['ok'=>false,'message'=>$GLOBALS['mt_last_error'] ?? 'تعذر الاتصال بـ MikroTik.'], 500);

  $msg = '';
  $used = ua_sip_db_used($conn, $id);

  $secretRows = $api->command(['/ppp/secret/print']);
  if (function_exists('mt_rows_has_error') && mt_rows_has_error($secretRows, $msg)) {
    ua_ip_suggest_out(['ok'=>false,'message'=>$msg ?: 'فشل قراءة PPP Secrets من MikroTik.'], 500);
  }
  foreach ((array)$secretRows as $r) {
    if (!is_array($r) || (($r['.type'] ?? '') !== '!re')) continue;
    $u = trim((string)($r['name'] ?? ''));
    if ($skipUser !== '' && $u !== '' && $u === $skipUser) continue;
    ua_sip_add_used_ip($used, (string)($r['remote-address'] ?? ''), 'PPP Secret'.($u !== '' ? ' / '.$u : ''));
  }

  $activeRows = $api->command(['/ppp/active/print']);
  if (function_exists('mt_rows_has_error') && mt_rows_has_error($activeRows, $msg)) {
    ua_ip_suggest_out(['ok'=>false,'message'=>$msg ?: 'فشل قراءة PPP Active من MikroTik.'], 500);
  }
  foreach ((array)$activeRows as $r) {
    if (!is_array($r) || (($r['.type'] ?? '') !== '!re')) continue;
    $u = trim((string)($r['name'] ?? ($r['user'] ?? '')));
    if ($skipUser !== '' && $u !== '' && $u === $skipUser) continue;
    ua_sip_add_used_ip($used, (string)($r['address'] ?? ''), 'PPP Active'.($u !== '' ? ' / '.$u : ''));
  }

  // Avoid suggesting addresses already configured on router interfaces.
  try {
    $addrRows = $api->command(['/ip/address/print']);
    if (!function_exists('mt_rows_has_error') || !mt_rows_has_error($addrRows, $msg)) {
      foreach ((array)$addrRows as $r) {
        if (!is_array($r) || (($r['.type'] ?? '') !== '!re')) continue;
        $addr = trim((string)($r['address'] ?? ''));
        if ($addr !== '' && strpos($addr, '/') !== false) $addr = trim(strtok($addr, '/'));
        ua_sip_add_used_ip($used, $addr, 'Router address');
      }
    }
  } catch (Throwable $ignore) {}

  $profilePoolNames = [];
  $profileRows = [];
  try {
    $profileRows = $api->command(['/ppp/profile/print']);
    if (!function_exists('mt_rows_has_error') || !mt_rows_has_error($profileRows, $msg)) {
      foreach ((array)$profileRows as $r) {
        if (!is_array($r) || (($r['.type'] ?? '') !== '!re')) continue;
        $name = trim((string)($r['name'] ?? ''));
        $remote = trim((string)($r['remote-address'] ?? ''));
        if ($name !== '' && $remote !== '' && $name === $currentProfile) $profilePoolNames[] = $remote;
      }
    }
  } catch (Throwable $ignore) {}

  $poolRows = $api->command(['/ip/pool/print']);
  if (function_exists('mt_rows_has_error') && mt_rows_has_error($poolRows, $msg)) {
    ua_ip_suggest_out(['ok'=>false,'message'=>$msg ?: 'فشل قراءة IP Pools من MikroTik.'], 500);
  }

  $pools = [];
  foreach ((array)$poolRows as $r) {
    if (!is_array($r) || (($r['.type'] ?? '') !== '!re')) continue;
    $name = trim((string)($r['name'] ?? ''));
    $ranges = trim((string)($r['ranges'] ?? ''));
    if ($name === '' || $ranges === '') continue;
    $items = [];
    foreach (explode(',', $ranges) as $range) {
      $range = trim($range);
      if ($range !== '' && ua_sip_range_to_bounds($range)) $items[] = $range;
    }
    if ($items) $pools[$name] = ['name'=>$name, 'ranges'=>$items];
  }

  $ranges = [];

  // Optional professional override: put a reserved static range in settings_store as pppoe_static_ip_suggest_range.
  $settingRanges = ua_sip_ranges_from_setting((string)setting_get($conn, 'pppoe_static_ip_suggest_range', ''));
  foreach ($settingRanges as $r) $ranges[] = $r;

  $settingPool = trim((string)setting_get($conn, 'pppoe_static_ip_suggest_pool', ''));
  $preferredPoolNames = [];
  if ($settingPool !== '') $preferredPoolNames[] = $settingPool;
  foreach ($profilePoolNames as $pn) $preferredPoolNames[] = $pn;
  $preferredPoolNames = array_values(array_unique(array_filter(array_map('trim', $preferredPoolNames))));

  foreach ($preferredPoolNames as $poolName) {
    if (isset($pools[$poolName])) {
      foreach ($pools[$poolName]['ranges'] as $range) {
        $ranges[] = ['name'=>$poolName, 'range'=>$range, 'source'=>'mikrotik_profile_pool'];
      }
    } elseif (ua_sip_range_to_bounds($poolName)) {
      $ranges[] = ['name'=>'Profile remote-address', 'range'=>$poolName, 'source'=>'mikrotik_profile_range'];
    }
  }

  // Fallback: PPP-like pools first, then all pools.
  if (!$ranges) {
    foreach ($pools as $pool) {
      if (preg_match('/ppp|pppoe|ppo|pool/i', $pool['name'])) {
        foreach ($pool['ranges'] as $range) $ranges[] = ['name'=>$pool['name'], 'range'=>$range, 'source'=>'mikrotik_pool'];
      }
    }
  }
  if (!$ranges) {
    foreach ($pools as $pool) {
      foreach ($pool['ranges'] as $range) $ranges[] = ['name'=>$pool['name'], 'range'=>$range, 'source'=>'mikrotik_pool'];
    }
  }

  if (!$ranges) {
    ua_ip_suggest_out(['ok'=>false,'message'=>'لم أجد IP Pool أو رينج صالح يمكن اقتراح IP منه على MikroTik.'], 404);
  }

  $checked = 0;
  $maxScanPerRange = 65536;
  $warnings = [];
  $lastReject = '';

  foreach ($ranges as $rangeRow) {
    $range = ua_sip_range_label($rangeRow);
    $bounds = ua_sip_range_to_bounds($range);
    if (!$bounds) continue;
    [$start, $end] = $bounds;
    $count = ($end - $start) + 1;
    if ($count <= 0) continue;

    // Prefer high addresses first; they are less likely to be taken by dynamic sessions in many MikroTik pools.
    $scanStart = $end;
    $scanEnd = $start;
    $scannedThisRange = 0;

    for ($n = $scanStart; $n >= $scanEnd; $n--) {
      $scannedThisRange++;
      $checked++;
      if ($scannedThisRange > $maxScanPerRange) {
        $warnings[] = 'تم فحص آخر '.number_format($maxScanPerRange).' عنوان فقط من الرينج الكبير '.$range.'.';
        break;
      }

      $ip = ua_sip_uint_to_ip($n);
      if ($ip === '') continue;
      if (isset($used[$ip])) { $lastReject = $ip.' مستخدم: '.$used[$ip]; continue; }

      $vErr = function_exists('subs_static_ip_validate') ? subs_static_ip_validate($ip) : '';
      if ($vErr !== '') { $lastReject = $ip.' مرفوض: '.$vErr; continue; }

      if (function_exists('subs_static_ip_db_conflict') && subs_static_ip_db_conflict($conn, $ip, $id)) {
        $lastReject = $ip.' مستخدم في قاعدة البيانات';
        continue;
      }

      $sourceLabel = trim((string)($rangeRow['name'] ?? ''));
      if ($sourceLabel === '') $sourceLabel = 'MikroTik';
      if (($rangeRow['source'] ?? '') !== 'setting') {
        $warnings[] = 'الاقتراح مأخوذ من رينج MikroTik: '.$sourceLabel.' ['.$range.']. إذا كان هذا الرينج Pool ديناميكي، الأفضل حجز جزء منه للـ Static IP لاحقاً.';
      }

      try { if (is_object($api) && method_exists($api, 'disconnect')) @$api->disconnect(); } catch (Throwable $ignore) {}
      ua_ip_suggest_out([
        'ok'=>true,
        'ip'=>$ip,
        'message'=>'تم اقتراح IP متاح حالياً: '.$ip,
        'source'=>$sourceLabel,
        'range'=>$range,
        'profile'=>$currentProfile,
        'checked'=>$checked,
        'warnings'=>array_values(array_unique($warnings)),
      ]);
    }
  }

  try { if (is_object($api) && method_exists($api, 'disconnect')) @$api->disconnect(); } catch (Throwable $ignore) {}
  ua_ip_suggest_out([
    'ok'=>false,
    'message'=>'لم يتم العثور على IP متاح ضمن الرينجات المقروءة من MikroTik.'.($lastReject !== '' ? ' آخر فحص: '.$lastReject : ''),
    'checked'=>$checked,
    'warnings'=>array_values(array_unique($warnings)),
  ], 404);

} catch (Throwable $e) {
  ua_ip_suggest_out(['ok'=>false,'message'=>$e->getMessage()], 500);
}
