import 'dart:convert'; import 'package:http/http.dart' as http; import '../models/schedule.dart'; import '../models/navigation.dart'; import 'auth_service.dart'; import 'exceptions.dart'; /// Schedule/Route Service class ScheduleService { // TODO: Implement API connector - configure base URL from env/config // Transit API runs on port 5001 static const String _apiBaseUrl = 'http://192.168.2.66:5001'; final _authService = AuthService(); /// Get public schedule overview for a specific date. Future> getSchedules(DateTime date, {int? lineId}) async { try { final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; final queryParameters = {'date': dateStr}; if (lineId != null) { queryParameters['lineId'] = lineId.toString(); } final uri = Uri.parse('$_apiBaseUrl/schedules/overview').replace(queryParameters: queryParameters); final response = await http.get(uri); if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; return (data['schedules'] as List? ?? []) .map((schedule) => schedule as Map) .map(ServiceSchedule.fromJson) .toList(); } throw Exception('Failed to load schedules: ${response.statusCode}'); } catch (e) { throw Exception('Schedule error: $e'); } } /// Get all recurring schedule templates, independent of a specific date. Future> getRecurringSchedules({int? lineId}) async { try { final queryParameters = {}; if (lineId != null) { queryParameters['lineId'] = lineId.toString(); } final uri = Uri.parse('$_apiBaseUrl/schedules/recurring').replace(queryParameters: queryParameters); final response = await http.get(uri); if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; return (data['schedules'] as List? ?? []) .map((schedule) => schedule as Map) .map(ServiceSchedule.fromJson) .toList(); } throw Exception('Failed to load recurring schedules: ${response.statusCode}'); } catch (e) { throw Exception('Recurring schedule error: $e'); } } /// Get stop list for a specific schedule. Future> getScheduleStops(int scheduleId) async { try { final response = await http.get(Uri.parse('$_apiBaseUrl/schedules/$scheduleId/stops')); if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; return (data['stops'] as List? ?? []) .map((stop) => stop as Map) .map(ScheduleStop.fromJson) .toList(); } throw Exception('Failed to load schedule stops: ${response.statusCode}'); } catch (e) { throw Exception('Schedule stops error: $e'); } } /// Get daily schedule for a specific date (includes recurring schedules) Future getDailySchedule(DateTime date) async { try { final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; // Call /schedules/overview which includes both one-time and recurring schedules for the date final uri = Uri.parse('$_apiBaseUrl/schedules/overview').replace( queryParameters: {'date': dateStr} ); final response = await http.get(uri); if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; final schedules = (data['schedules'] as List? ?? []) .map((s) => s as Map) .map(ServiceSchedule.fromJson) .toList(); // Convert ServiceSchedule to Route objects for display final routes = schedules.map((schedule) { return Route( id: schedule.id, busLine: BusLine( id: schedule.busLineId, lineNumber: schedule.lineNumber, startStop: '', endStop: '', estimatedDuration: 60, description: '', ), busType: BusType( id: schedule.busId, name: schedule.busName, seatCapacity: 0, licensePlate: '', ), startTime: schedule.startTime, endTime: schedule.endTime, status: schedule.status, ); }).toList(); return DailySchedule( routes: routes, date: date, ); } else { throw Exception('Failed to load schedule: ${response.statusCode}'); } } catch (e) { throw Exception('Schedule error: $e'); } } /// Get today's schedule Future getTodaySchedule() async { return getDailySchedule(DateTime.now()); } /// Get route details Future getRouteDetails(int routeId) async { try { final headers = await _authService.getAuthHeaders(); // Transit API exposes route details at /routes/:routeId final response = await http.get( Uri.parse('$_apiBaseUrl/routes/$routeId'), headers: headers, ); if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; return Route.fromJson(data); } else if (response.statusCode == 401 || response.statusCode == 403) { await _authService.logout(); throw UnauthorizedException('Session expired - Please login again'); } else { throw Exception('Failed to load route: ${response.statusCode}'); } } catch (e) { if (e is UnauthorizedException) rethrow; throw Exception('Route error: $e'); } } /// Update route status Future updateRouteStatus(int routeId, String status) async { try { final headers = await _authService.getAuthHeaders(); // Transit API updates status at /routes/:routeId/status final response = await http.patch( Uri.parse('$_apiBaseUrl/routes/$routeId/status'), headers: headers, body: jsonEncode({'status': status}), ); if (response.statusCode == 401 || response.statusCode == 403) { await _authService.logout(); throw UnauthorizedException('Session expired - Please login again'); } else if (response.statusCode != 200) { throw Exception('Failed to update status: ${response.statusCode}'); } } catch (e) { if (e is UnauthorizedException) rethrow; throw Exception('Status update error: $e'); } } /// Update schedule status for driver-facing schedule workflows. Future updateScheduleStatus(int scheduleId, String status) async { try { final headers = await _authService.getAuthHeaders(); final response = await http.patch( Uri.parse('$_apiBaseUrl/driver/schedules/$scheduleId/status'), headers: headers, body: jsonEncode({'status': status}), ); if (response.statusCode == 401 || response.statusCode == 403) { await _authService.logout(); throw UnauthorizedException('Session expired - Please login again'); } else if (response.statusCode != 200) { throw Exception('Failed to update schedule status: ${response.statusCode}'); } } catch (e) { if (e is UnauthorizedException) rethrow; throw Exception('Schedule status update error: $e'); } } /// Get stops for a bus line Future> getStops(int busLineId) async { try { final headers = await _authService.getAuthHeaders(); final response = await http.get( Uri.parse('$_apiBaseUrl/bus-lines/$busLineId/stops'), headers: headers, ); if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; final stops = (data['stops'] as List?) ?.map((s) => s as Map) .map((s) { final lat = (s['latitude'] ?? s['lat']) as num?; final lon = (s['longitude'] ?? s['lon']) as num?; final name = (s['name'] ?? s['stop_name'] ?? s['stopName']) as String?; if (lat == null || lon == null) return null; return NavigationPoint( latitude: lat.toDouble(), longitude: lon.toDouble(), name: name, ); }) .whereType() .toList() ?? []; return stops; } else if (response.statusCode == 401 || response.statusCode == 403) { await _authService.logout(); throw UnauthorizedException('Session expired - Please login again'); } else { throw Exception('Failed to load stops: ${response.statusCode}'); } } catch (e) { if (e is UnauthorizedException) rethrow; throw Exception('Get stops error: $e'); } } /// Mark a schedule stop as passed by the driver. Future markScheduleStopPassed({ required int scheduleId, required int stopOrder, DateTime? actualPassedAt, }) async { try { final headers = await _authService.getAuthHeaders(); final response = await http.post( Uri.parse('$_apiBaseUrl/driver/schedules/$scheduleId/stops/$stopOrder/passed'), headers: headers, body: jsonEncode({ if (actualPassedAt != null) 'actualPassedAt': actualPassedAt.toIso8601String(), }), ); if (response.statusCode == 401 || response.statusCode == 403) { await _authService.logout(); throw UnauthorizedException('Session expired - Please login again'); } if (response.statusCode < 200 || response.statusCode >= 300) { throw Exception('Failed to mark stop as passed: ${response.statusCode}'); } } catch (e) { if (e is UnauthorizedException) rethrow; throw Exception('Mark stop passed error: $e'); } } }