import 'package:dio/dio.dart'; import 'package:finlytic_app/core/network/api_client.dart'; import 'package:finlytic_app/features/admin/models/admin_user_model.dart'; import 'package:finlytic_app/features/admin/models/admin_create_user_request_dto.dart'; import 'package:finlytic_app/features/admin/models/admin_update_user_request_dto.dart'; import 'package:finlytic_app/features/admin/models/evaluation_history_enums.dart'; import 'package:finlytic_app/features/admin/models/evaluation_history_response_model.dart'; import 'package:finlytic_app/features/admin/models/recent_setup_model.dart'; import 'package:finlytic_app/features/admin/models/service_setting_dto.dart'; import 'package:finlytic_app/features/admin/models/watchlist_entry_model.dart'; class AdminRepository { final ApiClient apiClient; const AdminRepository({required this.apiClient}); Future> fetchUsers() async { try { final res = await apiClient.get('/api/v1/admin/users'); if (res.statusCode == 200 && res.data != null) { final List data = res.data; return data.map((json) => AdminUserModel.fromJson(json)).toList(); } return []; } catch (e) { throw Exception('Nutzer konnten nicht geladen werden: $e'); } } Future createUser(AdminCreateUserRequestDto dto) async { final res = await apiClient.post('/api/v1/admin/users', data: dto.toJson()); if (res.statusCode != 200 && res.statusCode != 201) { throw Exception('Erstellen fehlgeschlagen'); } } Future updateUser(String id, AdminUpdateUserRequestDto dto) async { final res = await apiClient.put('/api/v1/admin/users/$id', data: dto.toJson()); if (res.statusCode != 200) { throw Exception('Aktualisieren fehlgeschlagen'); } } Future>> fetchSettings() async { final res = await apiClient.get('/api/v1/admin/settings'); if (res.statusCode == 200 && res.data != null && res.data is Map) { final map = res.data as Map; final result = >{}; map.forEach((k, v) { if (v is List) { result[k] = v .whereType>() .map((item) => ServiceSettingDto.fromJson(item)) .toList(); } }); return result; } return {}; } Future updateServiceSettings(String serviceName, Map settings) async { final res = await apiClient.put('/api/v1/admin/settings/$serviceName', data: settings); if (res.statusCode != 200 && res.statusCode != 204) { throw Exception('Einstellungen konnten nicht gespeichert werden'); } } /// Fetches a filtered, paginated page of the evaluation history plus its /// accompanying summary from `GET /api/v1/admin/evaluations` /// (`AdminEvaluationHistoryController`). All filter parameters are optional — /// omitting one means "do not filter on this field", mirroring the server /// contract exactly (`GetEvaluationHistoryRequest`). /// /// `[Authorize(Roles = "Admin")]` on the server means a non-admin caller (or an /// expired/invalid token) gets a `401`/`403`, which `ApiClient`'s interceptor /// already turns into an auto-logout (Rules.md §8) before this method's /// `catch` even runs — this method only has to turn the remaining /// error responses (engine unreachable `503`, RPC timeout `502`, unexpected /// `500` — all `ProblemDetails` bodies per the controller) into a readable /// message instead of letting a raw `DioException` reach the UI. Future fetchEvaluationHistory({ DateTime? fromUtc, DateTime? toUtc, OutcomeReason? outcome, TriggerSource? triggerSource, String? search, int page = 1, int pageSize = 50, }) async { final query = { 'page': page, 'pageSize': pageSize, }; if (fromUtc != null) query['fromUtc'] = fromUtc.toUtc().toIso8601String(); if (toUtc != null) query['toUtc'] = toUtc.toUtc().toIso8601String(); if (outcome != null) query['outcome'] = outcome.toApiValue(); if (triggerSource != null) query['triggerSource'] = triggerSource.toApiValue(); if (search != null && search.trim().isNotEmpty) query['search'] = search.trim(); try { final res = await apiClient.get('/api/v1/admin/evaluations', queryParameters: query); if (res.data is Map) { return EvaluationHistoryResponseModel.fromJson(res.data as Map); } return EvaluationHistoryResponseModel.empty(); } on DioException catch (e) { final data = e.response?.data; final title = (data is Map && data['title'] is String) ? data['title'] as String : null; throw Exception(title ?? 'Evaluierungs-Historie konnte nicht geladen werden.'); } } /// Fetches FinlyticTechnicals' current scan universe ("watchlist") from /// `GET /api/v1/admin/evaluations/watchlist` — the assets actually being /// evaluated every cycle in the background, independent of the (filtered) /// evaluation history above. Future> fetchWatchlist() async { try { final res = await apiClient.get('/api/v1/admin/evaluations/watchlist'); if (res.data is List) { return (res.data as List).whereType>().map(WatchlistEntryModel.fromJson).toList(); } return []; } on DioException catch (e) { final data = e.response?.data; final title = (data is Map && data['title'] is String) ? data['title'] as String : null; throw Exception(title ?? 'Watchlist konnte nicht geladen werden.'); } } /// Fetches the last [limit] technical-analysis setups computed for [isin] /// (most recent first) from `GET /api/v1/admin/evaluations/watchlist/{isin}/history`. Future> fetchWatchlistEntryHistory(String isin, {int limit = 8}) async { try { final res = await apiClient.get( '/api/v1/admin/evaluations/watchlist/${Uri.encodeComponent(isin)}/history', queryParameters: {'limit': limit}, ); if (res.data is List) { return (res.data as List).whereType>().map(RecentSetupModel.fromJson).toList(); } return []; } on DioException catch (e) { final data = e.response?.data; final title = (data is Map && data['title'] is String) ? data['title'] as String : null; throw Exception(title ?? 'Score-Verlauf konnte nicht geladen werden.'); } } }