import 'dart:convert'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../models/cycle_entry.dart'; import '../models/teaching_plan.dart'; import '../models/prayer_request.dart'; class SyncService { // Use 192.168.1.81 for the user's specific setup as they are testing across devices. // In a real app we'd allow this to be configured. // static const String _baseUrl = 'http://localhost:8090'; // Dynamic Base URL static String get _baseUrl { if (kIsWeb) { // On web, use the current window's hostname (whether localhost or IP) // and target port 8090. final host = Uri.base.host; // If host is empty (some web views), fallback. if (host.isNotEmpty) { return 'http://$host:8090'; } } // Mobile / Desktop App Fallback // Use the specific local IP for this user's physical device testing. return 'http://192.168.1.81:8090'; } // Preview Partner Link (Check without linking) Future> previewPartnerId(String targetId) async { try { final url = Uri.parse('$_baseUrl/sync/preview'); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'targetId': targetId, }), ); if (response.statusCode == 200) { return jsonDecode(response.body); } else { throw Exception('Failed to preview: ${response.body}'); } } catch (e) { debugPrint('Preview Error: $e'); rethrow; } } // Link Partner Future> verifyPartnerId( String userId, String targetId) async { try { final url = Uri.parse('$_baseUrl/sync/link'); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'userId': userId, 'targetId': targetId, }), ); if (response.statusCode == 200) { return jsonDecode(response.body); } else { throw Exception('Failed to link: ${response.body}'); } } catch (e) { debugPrint('Link Error: $e'); rethrow; } } // Push data to backend Future pushSyncData({ required String userId, required List entries, required List teachingPlans, required List prayerRequests, Map? userDetails, }) async { try { final url = Uri.parse('$_baseUrl/sync/push'); final payload = { 'userId': userId, 'entries': entries.map((e) => _cycleEntryToJson(e)).toList(), 'teachingPlans': teachingPlans.map((p) => _teachingPlanToJson(p)).toList(), 'prayerRequests': prayerRequests.map((r) => _prayerRequestToJson(r)).toList(), if (userDetails != null) 'userDetails': userDetails, }; final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode(payload), ); if (response.statusCode == 200) { debugPrint('Sync Push Successful'); } else { debugPrint('Sync Push Failed: ${response.body}'); } } catch (e) { debugPrint('Sync Push Error: $e'); } } // Pull data from backend Future> pullSyncData(String userId, {String? partnerId}) async { try { var urlStr = '$_baseUrl/sync/pull?userId=$userId'; if (partnerId != null && partnerId.isNotEmpty) { urlStr += '&partnerId=$partnerId'; } final url = Uri.parse(urlStr); final response = await http.get(url); if (response.statusCode == 200) { final data = jsonDecode(response.body); // entries final List entriesJson = data['entries'] ?? []; final entries = entriesJson.map((json) => _jsonToCycleEntry(json)).toList(); // teaching plans final List plansJson = data['teachingPlans'] ?? []; final plans = plansJson.map((json) => _jsonToTeachingPlan(json)).toList(); // prayer requests final List prayersJson = data['prayerRequests'] ?? []; final prayers = prayersJson.map((json) => _jsonToPrayerRequest(json)).toList(); return { 'entries': entries, 'teachingPlans': plans, 'prayerRequests': prayers, if (data['userProfile'] != null) 'userProfile': data['userProfile'] as Map, }; } } catch (e) { debugPrint('Sync Pull Error: $e'); } return { 'entries': [], 'teachingPlans': [], 'prayerRequests': [], }; } // --- Adapters --- Map _cycleEntryToJson(CycleEntry entry) { // Convert boolean symptoms to list of strings final symptomsList = []; if (entry.hasHeadache) symptomsList.add('headache'); if (entry.hasBloating) symptomsList.add('bloating'); if (entry.hasBreastTenderness) symptomsList.add('breastTenderness'); if (entry.hasFatigue) symptomsList.add('fatigue'); if (entry.hasAcne) symptomsList.add('acne'); if (entry.hasLowerBackPain) symptomsList.add('lowerBackPain'); if (entry.hasConstipation) symptomsList.add('constipation'); if (entry.hasDiarrhea) symptomsList.add('diarrhea'); if (entry.hasInsomnia) symptomsList.add('insomnia'); return { 'id': entry.id, 'date': entry.date.toIso8601String(), 'flowIntensity': entry.flowIntensity?.name, 'isPeriodDay': entry.isPeriodDay, 'symptoms': jsonEncode(symptomsList), 'moods': jsonEncode(entry.mood != null ? [entry.mood!.name] : []), 'notes': entry.notes, 'createdAt': entry.createdAt.toIso8601String(), 'updatedAt': entry.updatedAt.toIso8601String(), }; } CycleEntry _jsonToCycleEntry(Map json) { // FlowIntensity enum parsing FlowIntensity? flow; if (json['flowIntensity'] != null) { flow = FlowIntensity.values.firstWhere( (e) => e.name == json['flowIntensity'], orElse: () => FlowIntensity.medium, ); } // Mood parsing MoodLevel? mood; final moodsList = _parseList(json['moods']); if (moodsList.isNotEmpty) { try { mood = MoodLevel.values.firstWhere((e) => e.name == moodsList.first); } catch (_) {} } final symptoms = _parseList(json['symptoms']); return CycleEntry( id: json['id'], date: DateTime.parse(json['date']), flowIntensity: flow, isPeriodDay: json['isPeriodDay'] == true, mood: mood, hasHeadache: symptoms.contains('headache'), hasBloating: symptoms.contains('bloating'), hasBreastTenderness: symptoms.contains('breastTenderness'), hasFatigue: symptoms.contains('fatigue'), hasAcne: symptoms.contains('acne'), hasLowerBackPain: symptoms.contains('lowerBackPain'), hasConstipation: symptoms.contains('constipation'), hasDiarrhea: symptoms.contains('diarrhea'), hasInsomnia: symptoms.contains('insomnia'), notes: json['notes'], createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt']) : DateTime.now(), updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt']) : DateTime.now(), ); } // Teaching Plan Map _teachingPlanToJson(TeachingPlan plan) { return { 'id': plan.id, 'date': plan.date.toIso8601String(), 'topic': plan.topic, 'scriptureReference': plan.scriptureReference, 'notes': plan.notes, 'isCompleted': plan.isCompleted, }; } TeachingPlan _jsonToTeachingPlan(Map json) { return TeachingPlan( id: json['id'], date: DateTime.parse(json['date']), topic: json['topic'], scriptureReference: json['scriptureReference'], notes: json['notes'], isCompleted: json['isCompleted'] == true, ); } // Prayer Request Map _prayerRequestToJson(PrayerRequest request) { return { 'id': request.id, 'request': request.request, 'isAnswered': request.isAnswered, 'createdAt': request.createdAt.toIso8601String(), 'updatedAt': request.updatedAt.toIso8601String(), }; } PrayerRequest _jsonToPrayerRequest(Map json) { return PrayerRequest( id: json['id'], request: json['request'], isAnswered: json['isAnswered'] == true, createdAt: DateTime.parse(json['createdAt']), updatedAt: DateTime.parse(json['updatedAt']), ); } List _parseList(dynamic jsonVal) { if (jsonVal == null) return []; if (jsonVal is String) { try { return List.from(jsonDecode(jsonVal)); } catch (_) { return []; } } return List.from(jsonVal); } }