<?php
// Shared source for subscriber bulk-action helpers.
// This file intentionally contains only reusable helpers; page/API response handling stays in its caller.

if (!function_exists('bulk_col_exists')) {
  function bulk_col_exists(mysqli $conn, string $table, string $col): bool {
    static $cache = [];
    $key = $table . '|' . $col;
    if (array_key_exists($key, $cache)) return (bool)$cache[$key];
    $t = $conn->real_escape_string($table);
    $c = $conn->real_escape_string($col);
    $rs = @$conn->query("SHOW COLUMNS FROM `$t` LIKE '$c'");
    return $cache[$key] = ($rs && $rs->num_rows > 0);
  }
}

if (!function_exists('bulk_table_exists')) {
  function bulk_table_exists(mysqli $conn, string $table): bool {
    static $cache = [];
    if (array_key_exists($table, $cache)) return (bool)$cache[$table];
    $t = $conn->real_escape_string($table);
    $rs = @$conn->query("SHOW TABLES LIKE '$t'");
    return $cache[$table] = ($rs && $rs->num_rows > 0);
  }
}

if (!function_exists('bulk_pending_clean_ids')) {
  function bulk_pending_clean_ids($ids): array {
    return array_values(array_unique(array_filter(array_map('intval', (array)$ids), function($v){ return $v > 0; })));
  }
}

if (!function_exists('bulk_parse_ids_value')) {
  function bulk_parse_ids_value($raw): array {
    if (is_array($raw)) return bulk_pending_clean_ids($raw);
    return bulk_pending_clean_ids(preg_split('/[^0-9]+/', (string)$raw));
  }
}

if (!function_exists('bulk_build_run_go')) {
  function bulk_build_run_go(string $action, array $ids): string {
    $ids = bulk_pending_clean_ids($ids);
    return 'subscribers_bulk_action.php?run=1&action=' . rawurlencode($action) . '&ids=' . rawurlencode(implode(',', $ids));
  }
}

if (!function_exists('bulk_log')) {
  function bulk_log(string $msg): void {
    $dir = __DIR__ . '/logs';
    if (!is_dir($dir)) @mkdir($dir, 0777, true);
    @file_put_contents($dir . '/subscribers_bulk_action.log', '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL, FILE_APPEND);
  }
}

if (!function_exists('bulk_api_cmd')) {
  function bulk_api_cmd($api, string $path, array $params = []) {
    if (!$api) return [];
    try {
      if (method_exists($api, 'command')) {
        $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 (method_exists($api, 'comm')) return $api->comm($path, $params);
    } catch (Throwable $e) {
      return [['.type' => '!trap', 'message' => $e->getMessage()]];
    }
    return [];
  }
}

if (!function_exists('bulk_rows_error')) {
  function bulk_rows_error($rows, string &$msg = ''): bool {
    if (!is_array($rows)) return false;
    foreach ($rows as $row) {
      if (!is_array($row)) continue;
      $t = (string)($row['.type'] ?? '');
      if ($t === '!trap' || $t === '!fatal') {
        $msg = (string)($row['message'] ?? $row['category'] ?? 'RouterOS error');
        return true;
      }
    }
    return false;
  }
}

if (!function_exists('bulk_find_first_id_by_name')) {
  function bulk_find_first_id_by_name($api, string $path, string $name): string {
    $name = trim($name);
    if ($name === '') return '';
    $rows = bulk_api_cmd($api, $path, ['?name' => $name]);
    $msg = '';
    if (bulk_rows_error($rows, $msg)) throw new RuntimeException($msg);
    if (!is_array($rows) || empty($rows)) $rows = bulk_api_cmd($api, $path, []);
    if (!is_array($rows)) return '';
    foreach ($rows as $row) {
      if (!is_array($row)) continue;
      if (isset($row['.type']) && $row['.type'] !== '!re') continue;
      if (trim((string)($row['name'] ?? '')) === $name && trim((string)($row['.id'] ?? '')) !== '') return trim((string)$row['.id']);
    }
    return '';
  }
}

if (!function_exists('bulk_disconnect_ppp')) {
  function bulk_disconnect_ppp($api, string $pppUser): void {
    $aid = bulk_find_first_id_by_name($api, '/ppp/active/print', $pppUser);
    if ($aid !== '') {
      $rows = bulk_api_cmd($api, '/ppp/active/remove', ['.id' => $aid]);
      $msg = '';
      if (bulk_rows_error($rows, $msg)) throw new RuntimeException('فشل فصل الجلسة ' . $pppUser . ': ' . $msg);
    }
  }
}

if (!function_exists('bulk_set_secret_disabled')) {
  function bulk_set_secret_disabled($api, string $pppUser, bool $disabled): void {
    $sid = bulk_find_first_id_by_name($api, '/ppp/secret/print', $pppUser);
    if ($sid === '') throw new RuntimeException('PPPoE secret غير موجود: ' . $pppUser);
    $rows = bulk_api_cmd($api, '/ppp/secret/set', ['.id' => $sid, 'disabled' => ($disabled ? 'yes' : 'no')]);
    $msg = '';
    if (bulk_rows_error($rows, $msg)) throw new RuntimeException('فشل تعديل حالة ' . $pppUser . ': ' . $msg);
  }
}

if (!function_exists('bulk_remove_secret')) {
  function bulk_remove_secret($api, string $pppUser): bool {
    $sid = bulk_find_first_id_by_name($api, '/ppp/secret/print', $pppUser);
    if ($sid === '') return false;
    $rows = bulk_api_cmd($api, '/ppp/secret/remove', ['.id' => $sid]);
    $msg = '';
    if (bulk_rows_error($rows, $msg)) throw new RuntimeException('فشل حذف ' . $pppUser . ': ' . $msg);
    return true;
  }
}

if (!function_exists('bulk_set_open_speed')) {
  function bulk_set_open_speed($api, string $pppUser): void {
    $sid = bulk_find_first_id_by_name($api, '/ppp/secret/print', $pppUser);
    if ($sid === '') throw new RuntimeException('لم يتم العثور على secret للحساب ' . $pppUser);
    $rows = bulk_api_cmd($api, '/ppp/secret/set', ['.id' => $sid, 'profile' => 'OPEN-ALL-UNLIMITED', 'rate-limit' => '']);
    $msg = '';
    if (bulk_rows_error($rows, $msg)) throw new RuntimeException('فشل فتح السرعة ' . $pppUser . ': ' . $msg);
  }
}

// Stage 8: one shared executor for all subscriber bulk actions.
// UI page and JSON API now call this function instead of duplicating RouterOS/DB logic.
if (!function_exists('bulk_action_label')) {
  function bulk_action_label(string $action): string {
    return ['cut'=>'فصل','restore'=>'استعادة','cancel'=>'إلغاء اشتراك','open_speed'=>'سرعة مفتوحة'][$action] ?? $action;
  }
}

if (!function_exists('bulk_action_allowed')) {
  function bulk_action_allowed(string $action): bool {
    return in_array(strtolower(trim($action)), ['cut','restore','cancel','open_speed'], true);
  }
}

if (!function_exists('bulk_ppp_username_from_row')) {
  function bulk_ppp_username_from_row(array $sub): string {
    $pppUser = trim((string)($sub['ppp_username'] ?? ''));
    if ($pppUser === '') $pppUser = trim((string)($sub['username'] ?? ''));
    return $pppUser;
  }
}

if (!function_exists('bulk_fetch_subscribers_by_ids')) {
  function bulk_fetch_subscribers_by_ids(mysqli $conn, array $ids): array {
    $ids = bulk_pending_clean_ids($ids);
    if (!$ids) return [];
    $in = implode(',', array_map('intval', $ids));
    $rows = [];
    $rs = @$conn->query('SELECT * FROM subscribers WHERE id IN (' . $in . ')');
    if ($rs) {
      while ($r = $rs->fetch_assoc()) $rows[(int)$r['id']] = $r;
    }
    return $rows;
  }
}

if (!function_exists('bulk_runtime_cache_purge')) {
  function bulk_runtime_cache_purge(): void {
    $runtime = __DIR__ . '/cache/runtime';
    foreach (['unsafe_speed_count.json','ppp_disabled_state_map.json','ppp_secret_reality_map.json','ppp_active.json'] as $name) {
      $path = $runtime . '/' . $name;
      if (is_file($path)) @unlink($path);
    }
  }
}


if (!function_exists('bulk_execute_one_with_api')) {
  /**
   * Single authoritative executor for one subscriber action.
   * Used by page bulk actions, JSON bulk API, queue worker, and direct single-action pages.
   */
  function bulk_execute_one_with_api(mysqli $conn, $api, string $action, array $sub, array $options = []): array {
    $action = strtolower(trim($action));
    if (!bulk_action_allowed($action)) throw new RuntimeException('إجراء غير معروف.');

    $source = (string)($options['source'] ?? 'shared');
    $id = (int)($sub['id'] ?? 0);
    if ($id <= 0) throw new RuntimeException('رقم المشترك غير صالح.');

    $pppUser = bulk_ppp_username_from_row($sub);
    if ($pppUser === '') throw new RuntimeException('لا يوجد اسم PPPoE للمشترك رقم ' . $id);

    $now = (string)($options['now'] ?? date('Y-m-d H:i:s'));

    $hasPppDisabled = bulk_col_exists($conn, 'subscribers', 'ppp_is_disabled');
    $hasMtDisabled  = bulk_col_exists($conn, 'subscribers', 'mt_disabled');
    $hasLastCut     = bulk_col_exists($conn, 'subscribers', 'ppp_last_cut_at');
    $hasSpeedState  = bulk_col_exists($conn, 'subscribers', 'speed_state');
    $hasPppProfile  = bulk_col_exists($conn, 'subscribers', 'ppp_profile');
    $hasPppRateLimit= bulk_col_exists($conn, 'subscribers', 'ppp_rate_limit');
    $hasDeleted     = bulk_col_exists($conn, 'subscribers', 'is_deleted');
    $hasDeletedAt   = bulk_col_exists($conn, 'subscribers', 'deleted_at');

    if ($action === 'cut') {
      bulk_set_secret_disabled($api, $pppUser, true);
      bulk_disconnect_ppp($api, $pppUser);

      $sets = [];
      if ($hasPppDisabled) $sets[] = 'ppp_is_disabled=1';
      if ($hasMtDisabled)  $sets[] = 'mt_disabled=1';
      if ($hasSpeedState)  $sets[] = "speed_state='CUT'";
      if ($hasLastCut)     $sets[] = "ppp_last_cut_at='".$conn->real_escape_string($now)."'";
      if ($sets) @$conn->query('UPDATE subscribers SET '.implode(',', $sets).' WHERE id='.$id);

      if (function_exists('sh_log_event')) {
        if ($source === 'queue') {
          @sh_log_event($conn, $id, 'MANUAL_CUT', 'فصل يدوي جماعي', 'تم فصل المشترك عبر Queue', $now, $pppUser);
        } elseif ($source === 'single_page') {
          @sh_log_event($conn, $id, 'MANUAL_CUT', 'فصل يدوي', 'تم فصل المشترك من واجهة الإدارة', $now, $pppUser);
        } elseif ($source === 'api_single') {
          @sh_log_event($conn, $id, 'MANUAL_CUT', 'فصل يدوي', 'تم فصل المشترك من واجهة الإدارة/API', $now, $pppUser);
        } elseif ($source === 'service_pause') {
          @sh_log_event($conn, $id, 'SERVICE_TEMPORARY_PAUSE', 'إيقاف مؤقت', 'تم تعطيل حساب الشبكة ضمن الإيقاف المؤقت للمشترك مع حفظ أرصدته وذممه', $now, $pppUser);
        } else {
          $text = ($source === 'page') ? 'تم فصل المشترك من الإدارة الجماعية بنفس سياسة زر الفصل' : 'تم فصل المشترك من الإدارة الجماعية';
          @sh_log_event($conn, $id, 'MANUAL_CUT', 'فصل يدوي جماعي', $text, $now, $pppUser);
        }
      }
    } elseif ($action === 'restore') {
      bulk_set_secret_disabled($api, $pppUser, false);

      $sets = [];
      if ($hasPppDisabled) $sets[] = 'ppp_is_disabled=0';
      if ($hasMtDisabled)  $sets[] = 'mt_disabled=0';
      if ($hasSpeedState)  $sets[] = "speed_state='ORIGINAL'";
      if ($sets) @$conn->query('UPDATE subscribers SET '.implode(',', $sets).' WHERE id='.$id);

      if (function_exists('sh_log_event')) {
        if ($source === 'queue') {
          @sh_log_event($conn, $id, 'MANUAL_RESTORE', 'استعادة يدوية جماعية', 'تمت الاستعادة عبر Queue', $now, $pppUser);
        } elseif ($source === 'single_page') {
          @sh_log_event($conn, $id, 'MANUAL_RESTORE', 'استعادة يدوية', 'تمت استعادة المشترك من واجهة الإدارة', $now, $pppUser);
        } elseif ($source === 'api_single') {
          @sh_log_event($conn, $id, 'MANUAL_RESTORE', 'استعادة يدوية', 'تمت استعادة المشترك من واجهة الإدارة/API', $now, $pppUser);
        } elseif ($source === 'service_resume') {
          @sh_log_event($conn, $id, 'SERVICE_TEMPORARY_RESUME', 'إعادة تفعيل', 'تمت إعادة تفعيل حساب الشبكة واستئناف دورة المشترك', $now, $pppUser);
        } else {
          $text = ($source === 'page') ? 'تمت استعادة المشترك من الإدارة الجماعية بنفس سياسة زر الاستعادة' : 'تمت استعادة المشترك من الإدارة الجماعية';
          @sh_log_event($conn, $id, 'MANUAL_RESTORE', 'استعادة يدوية جماعية', $text, $now, $pppUser);
        }
      }
    } elseif ($action === 'cancel') {
      bulk_disconnect_ppp($api, $pppUser);
      if (!bulk_remove_secret($api, $pppUser)) {
        throw new RuntimeException('لم يتم العثور/حذف secret من MikroTik للحساب '.$pppUser.'؛ لم تتم الأرشفة.');
      }

      if ($hasDeleted) {
        $sets = ['is_deleted=1'];
        if ($hasDeletedAt) $sets[] = "deleted_at='".$conn->real_escape_string($now)."'";
        @$conn->query('UPDATE subscribers SET '.implode(',', $sets).' WHERE id='.$id);
      }
      if (bulk_table_exists($conn, 'billing_action_requests')) {
        @$conn->query("UPDATE billing_action_requests SET status='resolved', resolved_at=NOW() WHERE subscriber_id=".$id." AND LOWER(COALESCE(status,''))='pending'");
      }

      if ($source === 'queue') {
        if (function_exists('sh_log_event')) @sh_log_event($conn, $id, 'SUBSCRIBER_CANCEL_QUEUE', 'إلغاء اشتراك عبر Queue', 'تم إلغاء الاشتراك وحذف PPP Secret عبر Queue: ppp_user='.$pppUser, $now, $pppUser);
        if (function_exists('audit_log')) @audit_log($conn, 'SUBSCRIBER_CANCEL_QUEUE', 'subscribers', $id, 'ppp_user='.$pppUser.'; removed_from_mikrotik=1');
      } elseif ($source === 'single_page') {
        if (function_exists('audit_log')) @audit_log($conn, 'SUBSCRIBER_CANCEL', 'subscribers', $id, 'ppp_user='.$pppUser.'; mk= وحُذف من MikroTik');
      } else {
        if ($source === 'page' && function_exists('sh_log_event')) {
          @sh_log_event($conn, $id, 'SUBSCRIBER_CANCEL_BULK', 'إلغاء اشتراك جماعي', 'تم إلغاء الاشتراك وحذف PPP Secret من الإدارة الجماعية: ppp_user='.$pppUser, $now, $pppUser);
        }
        if (function_exists('audit_log')) {
          $extra = ($source === 'api') ? '; removed_from_mikrotik=1' : '';
          @audit_log($conn, 'SUBSCRIBER_CANCEL_BULK', 'subscribers', $id, 'ppp_user='.$pppUser.$extra);
        }
      }
    } elseif ($action === 'open_speed') {
      bulk_set_open_speed($api, $pppUser);
      bulk_disconnect_ppp($api, $pppUser);

      $sets = [];
      if ($hasSpeedState)   $sets[] = "speed_state='OPEN_SPEED'";
      if ($hasPppProfile)   $sets[] = "ppp_profile='OPEN-ALL-UNLIMITED'";
      if ($hasPppRateLimit) $sets[] = "ppp_rate_limit=''";
      if ($sets) @$conn->query('UPDATE subscribers SET '.implode(',', $sets).' WHERE id='.$id);

      bulk_runtime_cache_purge();
      if (function_exists('sh_log_event')) {
        if ($source === 'queue') {
          @sh_log_event($conn, $id, 'OPEN_SPEED', 'سرعة مفتوحة', 'تم تفعيل السرعة المفتوحة عبر Queue', $now, $pppUser);
        } elseif ($source === 'page') {
          @sh_log_event($conn, $id, 'OPEN_SPEED', 'سرعة مفتوحة', 'تم تفعيل السرعة المفتوحة من الإدارة الجماعية', $now, $pppUser);
        }
      }
    }

    bulk_runtime_cache_purge();
    return ['id'=>$id, 'ppp_username'=>$pppUser, 'ok'=>true];
  }
}

if (!function_exists('bulk_execute_actions')) {
  /**
   * Executes subscriber actions from one authoritative source.
   *
   * Response shape is intentionally UI-neutral so callers can either set flash messages
   * or return JSON without duplicating the operation logic.
   */
  function bulk_execute_actions(mysqli $conn, string $action, array $ids, array $options = []): array {
    $action = strtolower(trim($action));
    $ids = bulk_pending_clean_ids($ids);
    $source = (string)($options['source'] ?? 'shared'); // page|api|queue|shared
    $maxErrors = (int)($options['max_errors'] ?? 8);
    if ($maxErrors < 1) $maxErrors = 1;

    if (!$ids || !bulk_action_allowed($action)) {
      throw new InvalidArgumentException('طلب غير صالح');
    }

    $maxIds = (int)($options['max_ids'] ?? 0);
    if ($maxIds > 0 && count($ids) > $maxIds) {
      throw new InvalidArgumentException('عدد المشتركين كبير جداً للعملية الواحدة. نفّذ على دفعات أقل من ' . $maxIds . '.');
    }

    if (empty($options['router_checked']) && function_exists('mt_require_router_online_or_throw')) {
      mt_require_router_online_or_throw($conn, (string)($options['router_context'] ?? 'تنفيذ جماعي للمشتركين'));
    }

    $now = date('Y-m-d H:i:s');
    $changed = 0;
    $failed = 0;
    $errors = [];
    $details = [];
    $api = null;

    $hasPppDisabled = bulk_col_exists($conn, 'subscribers', 'ppp_is_disabled');
    $hasMtDisabled  = bulk_col_exists($conn, 'subscribers', 'mt_disabled');
    $hasLastCut     = bulk_col_exists($conn, 'subscribers', 'ppp_last_cut_at');
    $hasSpeedState  = bulk_col_exists($conn, 'subscribers', 'speed_state');
    $hasPppProfile  = bulk_col_exists($conn, 'subscribers', 'ppp_profile');
    $hasPppRateLimit= bulk_col_exists($conn, 'subscribers', 'ppp_rate_limit');
    $hasDeleted     = bulk_col_exists($conn, 'subscribers', 'is_deleted');
    $hasDeletedAt   = bulk_col_exists($conn, 'subscribers', 'deleted_at');

    try {
      $rowsById = bulk_fetch_subscribers_by_ids($conn, $ids);

      if (!function_exists('mt_api')) {
        throw new RuntimeException('دالة الاتصال بالمايكروتك غير متاحة.');
      }
      $api = mt_api($conn);
      if (!$api) throw new RuntimeException((string)($GLOBALS['mt_last_error'] ?? 'فشل الاتصال بالمايكروتك.'));

      if ($action === 'open_speed') {
        if (function_exists('mt_ppp_profile_ensure') && !mt_ppp_profile_ensure($conn, 'OPEN-ALL-UNLIMITED', '')) {
          throw new RuntimeException((string)($GLOBALS['mt_last_error'] ?? 'تعذر تجهيز بروفايل السرعة المفتوحة.'));
        }
      }

      foreach ($ids as $id) {
        try {
          $id = (int)$id;
          $sub = $rowsById[$id] ?? null;
          if (!$sub) throw new RuntimeException('المشترك غير موجود في قاعدة البيانات.');

          $pppUser = bulk_ppp_username_from_row($sub);
          if ($pppUser === '') throw new RuntimeException('لا يوجد اسم PPPoE للمشترك رقم '.$id);

          $details[] = bulk_execute_one_with_api($conn, $api, $action, $sub, [
            'source' => $source,
            'now' => $now,
          ]);
          $changed++;
        } catch (Throwable $e) {
          $failed++;
          $err = '#'.(int)$id.': '.$e->getMessage();
          if (count($errors) < $maxErrors) $errors[] = $err;
          $details[] = ['id'=>(int)$id,'ok'=>false,'message'=>$e->getMessage()];
          bulk_log('item failed source='.$source.' action='.$action.' '.$err);
        }
      }
    } finally {
      try { if ($api && method_exists($api, 'disconnect')) $api->disconnect(); } catch (Throwable $e) {}
    }

    if (!empty($options['refresh_cache'])) {
      try {
        if (function_exists('ua_ppp_cache_rebuild')) ua_ppp_cache_rebuild($conn, 0, true);
      } catch (Throwable $e) {
        bulk_log('cache refresh after bulk failed: '.$e->getMessage());
      }
    }

    $label = bulk_action_label($action);
    $messagePrefix = (string)($options['message_prefix'] ?? 'تم تنفيذ ');
    $message = $messagePrefix . $label . ' على ' . $changed . ' مشترك';
    if ($failed > 0) $message .= '، وفشل '.$failed;
    if ($errors) $message .= ' — '.implode(' | ', $errors);

    $result = [
      'ok' => ($changed > 0 || $failed === 0),
      'action' => $action,
      'label' => $label,
      'message' => $message,
      'changed' => $changed,
      'failed' => $failed,
      'errors' => $errors,
      'details' => $details,
    ];
    bulk_log('done source='.$source.' action='.$action.' changed='.$changed.' failed='.$failed.' ids='.implode(',', $ids));
    return $result;
  }
}
