import 'package:flutter/material.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:insparkspokalen_ui/models/teamModel.dart'; import 'package:insparkspokalen_ui/models/userModel.dart'; /// A service to synchronize team and user data with improved synchronization class TeamUserSyncService with ChangeNotifier { // Singleton pattern static final TeamUserSyncService _instance = TeamUserSyncService._internal(); factory TeamUserSyncService() => _instance; TeamUserSyncService._internal(); // Base URLs final String baseUrlTeam = 'https://group-10-15.pvt.dsv.su.se/team'; final String baseUrlUser = 'https://group-10-15.pvt.dsv.su.se/user'; // Current data TeamModel? _currentTeam; UserModel? _currentUser; List _allTeams = []; // Flag to track ongoing operations to prevent redundant requests bool _isSyncing = false; // Getters TeamModel? get currentTeam => _currentTeam; UserModel? get currentUser => _currentUser; List get allTeams => _allTeams; bool get isSyncing => _isSyncing; // Set current user with improved team synchronization Future setCurrentUser(UserModel user) async { _currentUser = user; notifyListeners(); // When user is set, attempt to fetch their team if (user != null) { await syncUserTeamData(user.email); } } // Comprehensive synchronization of user team data Future syncUserTeamData(String email) async { if (_isSyncing) return; _isSyncing = true; try { // 1. Get the user's team ID final teamId = await fetchUserTeam(email); // 2. If teamId exists, fetch the complete team data if (teamId != null) { await fetchTeamById(teamId); } else { // Clear current team if user doesn't belong to any team _currentTeam = null; notifyListeners(); } // 3. Always fetch all teams to keep the leaderboard updated await fetchAllTeams(); } finally { _isSyncing = false; } } // Fetch the current user's team ID Future fetchUserTeam(String email) async { final url = Uri.parse('$baseUrlUser/get-user-team/$email'); try { final response = await http.get(url); if (response.statusCode == 200 && response.body.isNotEmpty) { // Handle potential JSON format from the API var responseData = response.body; // Check if the response is JSON try { if (responseData.startsWith('{') || responseData.startsWith('[')) { var jsonData = jsonDecode(responseData); if (jsonData is Map && jsonData.containsKey('teamId')) { return jsonData['teamId'] as int; } } } catch (_) { // Not JSON, continue with normal parsing } // Try parsing as a simple integer try { return int.parse(responseData); } catch (e) { print('Error parsing team ID: $e'); return null; } } } catch (e) { print('Error fetching user team: $e'); } return null; } // Fetch a team by ID with improved error handling Future fetchTeamById(int teamId) async { final url = Uri.parse('$baseUrlTeam/get-team/$teamId'); try { final response = await http.get(url); if (response.statusCode == 200 && response.body.isNotEmpty) { final teamData = jsonDecode(response.body); _currentTeam = TeamModel.fromJson(teamData); notifyListeners(); return _currentTeam; } else if (response.statusCode == 404) { // Team not found, clear current team _currentTeam = null; notifyListeners(); } } catch (e) { print('Error fetching team: $e'); } return null; } // Fetch all teams with improved sorting Future> fetchAllTeams() async { final url = Uri.parse('$baseUrlTeam/all'); try { final response = await http.get(url); if (response.statusCode == 200) { final List data = jsonDecode(response.body); _allTeams = data.map((json) => TeamModel.fromJson(json)).toList(); // Sort teams by score (descending) for leaderboard display _allTeams.sort((a, b) => b.score.compareTo(a.score)); notifyListeners(); return _allTeams; } } catch (e) { print('Error fetching all teams: $e'); } return []; } // Join a team with no restriction checks - modified for testing Future joinTeam(int teamId) async { if (_currentUser == null || _isSyncing) return false; // REMOVED: Check if user is already in a team - for testing purposes _isSyncing = true; try { // 1. Update UserMS (update user's teamId) final userUrl = Uri.parse('$baseUrlUser/join-team'); final userResponse = await http.post( userUrl, headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'email': _currentUser!.email, 'teamId': teamId, }), ); if (userResponse.statusCode != 200 && userResponse.statusCode != 201) { print('Failed to update user team: ${userResponse.statusCode} - ${userResponse.body}'); return false; } // 2. Update TeamMS (add user to team's userEmails list) final teamUrl = Uri.parse('$baseUrlTeam/add-user-to-team'); final teamResponse = await http.post( teamUrl, headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'email': _currentUser!.email, 'teamId': teamId, }), ); if (teamResponse.statusCode != 200 && teamResponse.statusCode != 201) { print('Failed to add user to team: ${teamResponse.statusCode} - ${teamResponse.body}'); return false; } // 3. If both successful, update local data await syncUserTeamData(_currentUser!.email); return true; } catch (e) { print('Error joining team: $e'); return false; } finally { _isSyncing = false; } } // Leave a team with improved synchronization Future leaveTeam() async { if (_currentUser == null || _isSyncing) return false; // REMOVED: Check if user is in a team - for testing purposes // Get current team ID from user data instead final userTeamId = await fetchUserTeam(_currentUser!.email); if (userTeamId == null) return false; _isSyncing = true; try { // 1. Remove user from team in TeamMS final teamUrl = Uri.parse('$baseUrlTeam/remove-user-from-team/${_currentUser!.email}/$userTeamId'); final teamResponse = await http.delete(teamUrl); if (teamResponse.statusCode != 200) { print('Failed to remove user from team: ${teamResponse.statusCode} - ${teamResponse.body}'); } // 2. Update UserMS by setting teamId to null final userUrl = Uri.parse('$baseUrlUser/join-team'); final userResponse = await http.post( userUrl, headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'email': _currentUser!.email, 'teamId': null, }), ); if (userResponse.statusCode != 200 && userResponse.statusCode != 201) { print('Failed to update user record: ${userResponse.statusCode} - ${userResponse.body}'); return false; } // 3. Update local data _currentTeam = null; // Refresh all teams to reflect the changes await fetchAllTeams(); notifyListeners(); return true; } catch (e) { print('Error leaving team: $e'); return false; } finally { _isSyncing = false; } } // Check if the current user is in a team bool isUserInTeam() { return _currentTeam != null; } // Get team members (emails) for the current team List getCurrentTeamMembers() { return _currentTeam?.userEmails ?? []; } // Clear current data (e.g., on logout) void clearData() { _currentTeam = null; _currentUser = null; _allTeams = []; notifyListeners(); } }