# CloudKit Sync - Troubleshooting & Debugging

## Debugging Checklist

### 1. Verify CloudKit Account Status
```swift
// In AppDelegate
private func logCloudKitStatus() {
    let container = CKContainer(identifier: "iCloud.ro.xdev.USB-Meter")
    container.accountStatus { status, error in
        if let error {
            print("CloudKit error: \(error.localizedDescription)")
            return
        }
        switch status {
        case .available:
            print("✅ CloudKit available")
        case .noAccount:
            print("❌ No iCloud account")
        case .restricted:
            print("❌ CloudKit restricted (parental controls?)")
        case .couldNotDetermine:
            print("❌ Status unknown")
        case .temporarilyUnavailable:
            print("⚠️ CloudKit temporarily unavailable")
        @unknown default:
            print("⚠️ Unknown status")
        }
    }
}
```

**Expected:** `.available` or changes won't sync

---

### 2. Check Entitlements
Verify `USB Meter.entitlements` has:

```xml
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
    <string>iCloud.ro.xdev.USB-Meter</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
    <string>CloudKit</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
    <string>iCloud.ro.xdev.USB-Meter</string>
</array>
```

If missing:
- ❌ App can't access CloudKit
- ❌ No sync errors in logs (silent failure)

---

### 3. Monitor Sync Logs

Enable enhanced Core Data debugging:

**In Xcode scheme editor:**
1. Select scheme → Edit Scheme → Run → Arguments
2. Add Launch Arguments:
   ```
   -com.apple.CoreData.SQLDebug 1
   -com.apple.CoreData.ConcurrencyDebug 1
   ```

**In terminal:**
```bash
# Follow app logs
log stream --predicate 'process == "USB Meter"' --level debug
```

**Look for:**
- `NSPersistentCloudKitContainer` initialization messages
- `cloudkit:` messages (sync operations)
- `CoreData:` messages (database operations)

---

### 4. Check Persistent Store Description

```swift
// In AppDelegate.persistentContainer
if let description = container.persistentStoreDescriptions.first {
    print("Store URL: \(description.url?.path ?? "nil")")
    print("Options: \(description.options ?? [:])")
    print("CloudKit container: \(description.cloudKitContainerOptions?.containerIdentifier ?? "nil")")
}
```

**Expected:**
- URL should point to `.../Library/Application Support/CKModel.sqlite`
- CloudKit container identifier must match entitlements

---

### 5. Verify Remote Notifications Are Set Up

```swift
// In AppData
private func setupRemoteChangeNotificationObserver() {
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(remoteStoreDidChange),
        name: .NSPersistentStoreRemoteChange,
        object: nil
    )
    print("✅ Remote change observer registered")
}

@objc private func remoteStoreDidChange() {
    print("🔄 Remote CloudKit change received")
    DispatchQueue.main.async {
        self.reloadSettingsFromCloudStore()
    }
}
```

**Problem:** If `reloadSettingsFromCloudStore` is never called, check if observer is registered

---

## Common Issues & Fixes

### Issue 1: "Changes not syncing to other devices"

**Diagnosis:**
```swift
// Check if save succeeded
do {
    try appData.persistentContainer.viewContext.save()
    print("✅ Save successful")
} catch {
    print("❌ Save failed: \(error)")
}

// Check if DeviceSettings record exists
let request = NSFetchRequest<NSFetchRequestExpression>(entityName: "DeviceSettings")
let count = try? appData.persistentContainer.viewContext.count(for: request)
print("Records in store: \(count ?? 0)")
```

**Fixes:**
1. **Check Account Status** → must be `.available`
2. **Check Merge Policy** → ensure `NSMergeByPropertyStoreTrumpMergePolicy`
3. **Check Observer** → `remoteStoreDidChange` registered?
4. **Force Sync:**
   ```swift
   appData.persistentContainer.viewContext.refreshAllObjects()
   appData.reloadSettingsFromCloudStore()
   ```

### Issue 2: "Duplicates appearing in CloudKit"

**Diagnosis:**
```swift
// Check for MAC address duplicates
let request = NSFetchRequest<NSDictionary>(entityName: "DeviceSettings")
request.returnsDistinctResults = true
request.returnsObjectsAsFaults = false
request.resultType = .dictionaryResultType
request.returnsDistinctResults = true

let macs = try? appData.persistentContainer.viewContext.fetch(request)
    .compactMap { $0["macAddress"] as? String }

let duplicates = macs?.filter { mac in
    macs?.filter { $0 == mac }.count ?? 0 > 1
}
print("Duplicate MACs: \(duplicates ?? [])")
```

**Fixes:**
1. **Rebuild v3:** Ensure `cloudStoreRebuildVersion = 3` is deployed
2. **Check `rebuildCanonicalStoreIfNeeded`:**
   ```swift
   // Manually trigger
   appData.cloudStore?.rebuildCanonicalStoreIfNeeded(version: 3)
   ```
3. **Verify UserDefaults:**
   ```swift
   let rebuilt = UserDefaults.standard.bool(forKey: "cloudStoreRebuildVersion.3")
   print("Rebuild v3 completed: \(rebuilt)")
   ```

### Issue 3: "Connection status stuck as 'Connected on Device X'"

**Diagnosis:**
```swift
// Check connection record
let request = NSFetchRequest<NSManagedObject>(entityName: "DeviceSettings")
let records = try? appData.persistentContainer.viewContext.fetch(request)

for record in records ?? [] {
    if let expiry = record.value(forKey: "connectedExpiryAt") as? Date {
        let isExpired = expiry < Date.now
        print("✋ MAC \(record.value(forKey: "macAddress") ?? "?") expires at \(expiry), expired: \(isExpired)")
    }
}
```

**Fixes:**
1. **Manually clear lock:**
   ```swift
   appData.clearMeterConnection(macAddress: "aa:bb:cc:dd:ee:ff")
   ```
2. **Check TTL hardcode:** Should be 120s in `setConnection()`
3. **Verify system clock** on Device A (didn't fall asleep or drift)

### Issue 4: "Core Data save errors" with `NSError 1569`

**Symptom:**
```
NSError Domain=NSCocoaErrorDomain Code=1569
"The object's persistent store is not reachable"
```

**Causes:**
1. ❌ Old model with `uniquenessConstraint` conflicting with CloudKit
2. ❌ All attributes on DeviceSettings aren't `optional="YES"`

**Fixes:**
1. **Verify model v2+:** Check `.xccurrentversion` points to `USB_Meter 2.xcdatamodel`
2. **Check attributes:** All must be `optional="YES"` (inspect `.xcdatamodel/contents`)
3. **Reset store if persists:**
   ```swift
   // Delete local store to force rebuild from CloudKit
   let storeURL = appData.persistentContainer.persistentStoreDescriptions.first?.url
   let fileManager = FileManager.default
   try? fileManager.removeItem(at: storeURL)
   ```

---

## Monitoring CloudKit Sync in Real Time

### Dashboard View (for debugging)

```swift
struct CloudKitDebugView: View {
    @ObservedObject var appData: AppData

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("CloudKit Sync Status")
                .font(.headline)

            DebugItem(label: "Account", value: appData.cloudKitAccountStatus)
            DebugItem(label: "Device ID", value: UIDevice.current.identifierForVendor?.uuidString ?? "?")
            DebugItem(label: "Device Name", value: AppData.myDeviceName)
            DebugItem(label: "Last Sync", value: formatDate(appData.lastSyncTime))
            DebugItem(label: "Records", value: "\(appData.deviceSettingsCount)")
            DebugItem(label: "Pending", value: "\(appData.pendingSyncCount)")

            Button("Force Reload") {
                appData.reloadSettingsFromCloudStore()
            }
        }
        .padding()
        .font(.caption)
    }
}

struct DebugItem: View {
    let label: String
    let value: String

    var body: some View {
        HStack {
            Text(label).font(.caption2).foregroundColor(.secondary)
            Spacer()
            Text(value).font(.caption).monospaced()
        }
    }
}
```

---

## Network Conditions Testing

### Simulate CloudKit Unavailability
1. Xcode → Debug → Simulate Location → [Custom]
2. Instrument → Network Link Conditioner → "Very Bad Network"

### Expected Behavior:
- ✅ Local changes proceed (saved to local DB)
- ✅ Notification badge appears: "📶 Waiting for sync…"
- ✅ When network returns → auto-resumes sync

---

## CloudKit Container Inspection (Advanced)

Using CloudKit Console (requires Apple developer account):

1. Go to https://icloud.developer.apple.com/cloudkit
2. Select container: `iCloud.ro.xdev.USB-Meter`
3. Browse `DeviceSettings` records:
   - Check for duplicates by `macAddress`
   - Verify `connectedByDeviceID` matches expected device UUIDs
   - Check `updatedAt` timestamps

---

## Performance Profiling

```swift
// Measure reloadSettingsFromCloudStore() performance
let start = Date()
appData.reloadSettingsFromCloudStore()
let duration = Date().timeIntervalSince(start)
print("Reload took \(duration * 1000)ms")  // Should be <100ms for ~10 records
```

**Benchmarks:**
| Operation | Target | Actual (good) |
|-----------|--------|---------------|
| `setConnection()` | <50ms | ~10ms |
| `reloadSettingsFromCloudStore()` | <100ms | ~30ms (5 records) |
| CloudKit push (network) | <5s | varies by connection |
| Remote notification delivery | <10s | ~2-5s (typically) |

---

## Recording Logs for Bug Reports

```bash
# Save full debug logs
log collect --start '2025-03-26 10:00:00' --output /tmp/cloudkit_logs.logarchive

# Extract readable format
log show /tmp/cloudkit_logs.logarchive \
  --predicate 'process == "USB Meter"' \
  --level debug > ~/cloudkit_debug.txt
```

Include in bug report:
1. `~/cloudkit_debug.txt`
2. Reproduction steps
3. What you expected vs. what happened
4. Device names & OS versions involved
