# 🛠️ دليل حل المشاكل — API Folder
> **ملف للرجوع إليه عند تكرار أي مشكلة. يُحدَّث عند كل مشكلة جديدة.**

---

## 🐛 المشكلة #1 — فشل الاتصال بقاعدة البيانات رغم صحة البيانات

### 📅 التاريخ
`2026-08-16`

### 📁 الملف المتأثر
`api/setup.php`

---

### 🔴 أعراض المشكلة

```
❌ Auto setup.php call error: Exception: خطأ في الاتصال بقاعدة البيانات `iteg_Wajid`
قاعدة البيانات 'iteg_Wajid' غير موجودة ولم يتمكن المستخدم من إنشائها.
القواعد المتاحة لهذا المستخدم هي: [... iteg_Wajid ...]
```

**الأمر المريب:** قاعدة البيانات `iteg_Wajid` ظاهرة بوضوح في قائمة `SHOW DATABASES`، لكن الكود يقول "غير موجودة"!

**debug_log يُظهر:**
```
Accessible databases: ... iteg_Wajid ...
No matching database found in SHOW DATABASES list.   ← فاشل رغم الوجود الواضح!
Attempting to CREATE DATABASE `iteg_Wajid`
Create database failed: SQLSTATE[42000]: Access denied for user...
```

---

### 🔍 السبب الجذري

السيرفر يعمل على **Linux + MySQL**، وعلى Linux:

1. **أسماء قواعد البيانات case-sensitive** على مستوى الـ filesystem.
2. عند استخدام `SHOW DATABASES` عبر PDO، قد يُرجع MySQL أسماء القواعد بـ:
   - **أحرف unicode خفية** مثل `\xC2\xA0` (non-breaking space) بدلاً من مسافة عادية.
   - **encoding مختلف** عن الـ string المُرسَلة من التطبيق.
3. دالة `strcasecmp()` في PHP **حساسة للـ encoding** — قد تفشل رغم أن الاسمين يبدوان متطابقَين بصرياً.

**النتيجة:** المطابقة تفشل → الكود يحاول إنشاء DB جديدة → يفشل لعدم الصلاحية → خطأ.

---

### ❌ الكود القديم (المُسبِّب للمشكلة)

```php
// في setup.php - السطر ~40
$stmt = $rootPdo->query("SHOW DATABASES");
$accessibleDbs = $stmt->fetchAll(PDO::FETCH_COLUMN);

$matchedDb = null;
foreach ($accessibleDbs as $availDb) {
    $trimmedAvail = trim($availDb);
    $trimmedTarget = trim($db_name);
    if (strcasecmp($trimmedAvail, $trimmedTarget) === 0) {  // ← تفشل مع encoding خفي
        $matchedDb = $trimmedAvail;
        break;
    }
}
```

---

### ✅ الحل المُطبَّق — 4 مستويات مطابقة

```php
$matchedDb = null;
$cleanTarget = trim($db_name);

// المستوى 1: SHOW DATABASES LIKE (مطابقة مباشرة على السيرفر - الأقوى)
try {
    $likeStmt = $rootPdo->prepare("SHOW DATABASES LIKE ?");
    $likeStmt->execute([$cleanTarget]);
    $likeResults = $likeStmt->fetchAll(PDO::FETCH_COLUMN);
    if (!empty($likeResults)) {
        $matchedDb = trim($likeResults[0]);
        $debug_log[] = "[Level-1] SHOW DATABASES LIKE matched: '$matchedDb'";
    }
} catch (PDOException $eLike) {
    $debug_log[] = "[Level-1] SHOW DATABASES LIKE failed: " . $eLike->getMessage();
}

// المستوى 2: مقارنة strcasecmp مع trim
if ($matchedDb === null) {
    foreach ($accessibleDbs as $availDb) {
        if (strcasecmp(trim($availDb), $cleanTarget) === 0) {
            $matchedDb = trim($availDb);
            $debug_log[] = "[Level-2] strcasecmp match: '$matchedDb'";
            break;
        }
    }
}

// المستوى 3: إزالة أحرف unicode خفية ثم مقارنة
if ($matchedDb === null) {
    foreach ($accessibleDbs as $availDb) {
        $cleanAvail   = preg_replace('/[\x00-\x1F\x7F\xC2\xA0]/u', '', trim($availDb));
        $cleanTarget2 = preg_replace('/[\x00-\x1F\x7F\xC2\xA0]/u', '', $cleanTarget);
        if (strcasecmp($cleanAvail, $cleanTarget2) === 0) {
            $matchedDb = trim($availDb);
            $debug_log[] = "[Level-3] Cleaned-char match: '$matchedDb'";
            break;
        }
    }
}

// المستوى 4: normalize (أحرف أبجدية وأرقام و underscore فقط)
if ($matchedDb === null) {
    foreach ($accessibleDbs as $availDb) {
        $normAvail  = strtolower(preg_replace('/[^a-zA-Z0-9_]/', '', $availDb));
        $normTarget = strtolower(preg_replace('/[^a-zA-Z0-9_]/', '', $cleanTarget));
        if ($normAvail === $normTarget) {
            $matchedDb = trim($availDb);
            $debug_log[] = "[Level-4] Normalized match: '$matchedDb'";
            break;
        }
    }
}

// إذا فشلت الأربعة → اطبع hex لكل اسم (للتشخيص الدقيق)
if ($matchedDb === null) {
    $debug_log[] = "All match levels failed. Target hex: " . bin2hex($cleanTarget);
    foreach ($accessibleDbs as $availDb) {
        $debug_log[] = "  Available hex: " . bin2hex(trim($availDb)) . " => '" . trim($availDb) . "'";
    }
}
```

---

### 💡 لماذا Level-1 هو الحل الأقوى؟

`SHOW DATABASES LIKE ?` ينفَّذ **على السيرفر نفسه**، فيستخدم MySQL collation الخاصة به للمطابقة — متجاوزاً أي مشكلة encoding بين PHP والـ response.

---

### 🚀 خطوات التطبيق عند تكرار المشكلة

1. تأكد أن الكود في `setup.php` يستخدم نهج الـ 4 مستويات أعلاه.
2. ارفع الملف على السيرفر.
3. جرب مرة ثانية — في الغالب Level-1 يحل المشكلة فوراً.
4. إذا استمرت المشكلة، راجع الـ `debug_log` وابحث عن `Available hex:` لمقارنة الـ bytes يدوياً.

---

### 🌐 بيئة العمل التي ظهرت فيها المشكلة

| العنصر | القيمة |
|--------|--------|
| السيرفر | Linux (cPanel / Shared Hosting) |
| قاعدة البيانات | MySQL |
| API URL | `https://wajid.4it-eg.com/` |
| DB Name | `iteg_Wajid` |
| DB User | `iteg_mohamed` |

---

> 📌 **ملاحظة:** هذه المشكلة لا تظهر على Windows لأن MySQL على Windows يتعامل مع أسماء القواعد بشكل case-insensitive افتراضياً. تظهر حصراً على **Linux servers**.

---

## 🐛 المشكلة #2 — TimeoutException في أول تشغيل رغم نجاح إنشاء قاعدة البيانات

### 📅 التاريخ
`2026-08-16`

### 📁 الملف المتأثر
`lib/core/services/app_service.dart` — دالة `_ensureServerSetup()`

---

### 🔴 أعراض المشكلة

```
❌ Auto setup.php call error: TimeoutException after 0:00:25.000000: Future not completed
! LicenseManager.isActivated online check failed: TimeoutException after 0:00:04.000000
```

**في الـ UI:** يظهر شريط أحمر في الأسفل:
```
خطأ: TimeoutException after 0:00:25.000000: Future not completed
```

**الغريب:** عند إعادة التشغيل **يشتغل التطبيق بشكل طبيعي** بدون أي مشكلة!

---

### 🔍 السبب الجذري

```
Flutter HTTP timeout (25 ثانية)  <  وقت إنشاء الجداول على shared hosting (قد يصل 30-60 ثانية)
```

**السيناريو الكامل:**

| الخطوة | ما يحدث |
|--------|---------|
| 1 | `setup.php` يستقبل الطلب ويبدأ إنشاء 20+ جدول |
| 2 | Flutter ينتظر 25 ثانية فقط ثم يرمي `TimeoutException` |
| 3 | `setup.php` **يكمل بنجاح** بعد انتهاء الـ timeout! |
| 4 | Flutter يرمي الخطأ ويوقف التطبيق |
| 5 | عند إعادة التشغيل: الجداول موجودة → `setup.php` يرجع سريع (<5 ثوانٍ) → يشتغل |

**الخطأ الحقيقي:** `TimeoutException` ≠ فشل الإنشاء. كان الكود يعامله كخطأ فادح ويُوقف كل شيء.

---

### ❌ الكود القديم (المُسبِّب للمشكلة)

```dart
// في app_service.dart
final response = await http.post(setupUri, ...)
    .timeout(const Duration(seconds: 25));  // ← 25 ثانية قليلة جداً

// ...
} catch (e) {
  debugPrint('❌ Auto setup.php call error: $e');
  rethrow;  // ← يرمي الخطأ حتى لو كان timeout فقط!
}
```

---

### ✅ الحل المُطبَّق

```dart
// في app_service.dart - _ensureServerSetup()

const maxAttempts = 3;
const timeoutDuration = Duration(seconds: 90);  // رُفع من 25 → 90 ثانية
Exception? lastException;

for (int attempt = 1; attempt <= maxAttempts; attempt++) {
  try {
    final response = await http.post(setupUri, body: body)
        .timeout(timeoutDuration);
    
    final res = jsonDecode(response.body);
    if (res['success'] == true) {
      return; // نجح → خروج فوري
    }
    lastException = Exception(res['error']);
    
  } on TimeoutException catch (e) {
    // ⚠️ Timeout ≠ فشل → السيرفر قد يكون أكمل الإنشاء
    debugPrint('⏱️ Timeout on attempt $attempt: $e');
    if (attempt == maxAttempts) {
      return; // نكمل بدل ما نوقف التطبيق — الإعادة ستثبت النجاح
    }
    await Future.delayed(const Duration(seconds: 3));
    continue;
    
  } catch (e) {
    // أخطاء حقيقية (مش timeout) → retry
    lastException = Exception(e.toString());
    if (attempt < maxAttempts) {
      await Future.delayed(const Duration(seconds: 3));
    }
  }
}

// 3 محاولات فشلت بأخطاء حقيقية → ارمِ الخطأ
if (lastException != null) throw lastException!;
```

---

### 💡 المبدأ الأساسي

> **`TimeoutException` في Flutter ≠ فشل السيرفر**
>
> السيرفر يعمل باستقلالية — إذا انقطع اتصال Flutter، السيرفر يكمل عمله.
> الفرق: **Connection timeout** (فشل الاتصال) vs **Response timeout** (الاتصال ناجح لكن الرد أبطأ).

---

### 🚀 خطوات التطبيق عند تكرار المشكلة

1. تأكد أن timeout في `_ensureServerSetup` ≥ **90 ثانية**
2. تأكد أن `TimeoutException` يُعالَج بـ `return` وليس `rethrow`
3. إذا أردت تحقيقاً أكثر: أضف `await Future.delayed(Duration(seconds: 5))` بعد الـ timeout ثم استدعِ `setup.php` مجدداً للتحقق من النجاح

---

### 🌐 بيئة العمل التي ظهرت فيها المشكلة

| العنصر | القيمة |
|--------|--------|
| السيرفر | Linux (cPanel / Shared Hosting) |
| عدد الجداول المُنشأة | ~20+ جدول في طلب واحد |
| الـ timeout القديم | 25 ثانية |
| الـ timeout الجديد | 90 ثانية + 3 محاولات |
