import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:async/async.dart';
import 'package:be_happy/data/exceptions/auth_block_exception.dart';
import 'package:be_happy/data/exceptions/auth_exception.dart';
import 'package:be_happy/data/exceptions/route_history_not_found_exception.dart';
import 'package:be_happy/data/exceptions/scooter_not_found_exception.dart';
import 'package:be_happy/data/exceptions/unauthorized_exception.dart';
import 'package:be_happy/data/exceptions/wrong_zone_exception.dart';
import 'package:be_happy/domain/entities/active_scooter_order.dart';
import 'package:be_happy/domain/entities/scooter.dart';
import 'package:be_happy/domain/service/security_service.dart';
import 'package:dio/dio.dart';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:intl/intl.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart';
import 'package:flutter_client_sse/constants/sse_request_type_enum.dart';
import 'package:flutter_client_sse/flutter_client_sse.dart';

import '../../domain/entities/client_subscription.dart';
import '../../domain/entities/point.dart';
import '../../domain/entities/user_profile.dart';
import '../../domain/entities/payment_card.dart';
import '../../domain/entities/scooter_order.dart';
import '../../domain/entities/subscription.dart';
import '../../domain/entities/user_check_flags.dart';
import '../../domain/entities/certificate.dart';
import '../models/scooters_response.dart';
import '../models/tariffs_response.dart';
import '../models/zones_response.dart';
import '../models/subscriptions_response.dart';
import '../models/user_check_response_dto.dart';

class ApiService {
  static const String baseUrl = "https://sharing-api.sparkit.by/api/v1";
  static const String fileBaseUrl = "https://sharing-api.sparkit.by";

  final SecurityService _securityService;
  final Dio _dio;

  ApiService(this._securityService, this._dio);

  Future<Options> _getAuthOptions() async {
    final accessToken = await _securityService.getAccessToken();
    return Options(headers: {"Authorization": "Bearer $accessToken"});
  }

  Future<String?> sendPhone(String phone, String model, String systemId) async {
    try {
      final response = await _dio.post(
        "$baseUrl/auth/client/login",
        data: {"phone": phone, "model": model, "systemId": systemId},
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return response.data["token"];
      }
      return null;
    } catch (e) {
      return null;
    }
  }

  Future<Map<String, String>?> verifyCode(String code, String token) async {
    try {
      final response = await _dio.post(
        "$baseUrl/auth/client/verify",
        data: {"code": code, "token": token},
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return {
          "accessToken": response.data["accessToken"],
          "refreshToken": response.data["refreshToken"],
        };
      }
      return null;
    } on DioException catch (e) {
      if (e.response?.statusCode == 400) {
        final attempts =
            int.tryParse(e.response?.data["message"]?.toString() ?? "0") ?? 0;
        if (attempts == 0) throw AuthBlockException();
        throw AuthException("Ошибка. Неверный код.", attempts);
      } else if (e.response?.statusCode == 403) {
        throw AuthBlockException();
      }
      rethrow;
    }
  }

  Future<Map<String, String>?> refresh() async {
    final refreshToken = await _securityService.getRefreshToken();
    try {
      final response = await _dio.get(
        "$baseUrl/auth/client/refresh",
        options: Options(headers: {"Authorization": "Bearer $refreshToken"}),
      );

      if (response.statusCode == 200) {
        return {
          "accessToken": response.data["accessToken"],
          "refreshToken": response.data["refreshToken"],
        };
      }
      return null;
    } catch (e) {
      return null;
    }
  }

  Future<UserProfile?> getProfile() async {
    try {
      final response = await _dio.get(
        "$baseUrl/client/me",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200) {
        final data = response.data;
        final profileData = data["profile"];
        if (profileData == null) return null;

        String? parsedDate;
        if (profileData["dob"] != null) {
          try {
            parsedDate = DateFormat(
              'dd.MM.yyyy',
            ).format(DateTime.parse(profileData["dob"]));
          } catch (_) {}
        }

        final int? avatarId = profileData["avatarId"];
        String? avatarUrl;

        if (avatarId != null && profileData["avatar"] != null) {
          final String? avatarPath = profileData["avatar"]["path"];
          if (avatarPath != null && avatarPath.isNotEmpty) {
            avatarUrl = Uri.parse(fileBaseUrl).resolve(avatarPath).toString();
          }
        }

        dynamic balanceRaw = profileData["balance"];
        int? balance;
        if (balanceRaw is int) {
          balance = balanceRaw;
        } else if (balanceRaw is String) {
          balance = int.tryParse(balanceRaw);
        }

        return UserProfile(
          name: profileData["name"] ?? "",
          birthDate: parsedDate ?? "",
          phone: data["phone"] ?? "",
          balance: balance,
          email: profileData["email"] ?? "",
          avatarId: avatarId,
          avatarUrl: avatarUrl,
        );
      }
      return null;
    } catch (e) {
      return null;
    }
  }

  Future<List<PaymentCard>> getPaymentCards() async {
    final response = await _dio.get(
      "$baseUrl/client/me",
      options: await _getAuthOptions(),
    );
    if (response.statusCode == 200) {
      final List<dynamic> cardsJson = response.data["clientCards"] ?? [];
      return Future.wait(
        cardsJson.map((cardJson) async {
          return PaymentCard(
            id: cardJson['id'],
            clientId: cardJson['clientId'],
            expirationMonth: cardJson['expirationMonth'],
            expirationYear: cardJson['expirationYear'],
            cardHolder: cardJson['cardHolder'],
            cardLastNumber: cardJson['cardLastNumber'],
            isMain: cardJson['isMain'],
            type: cardJson['cardType'],
          );
        }),
      );
    }
    return [];
  }

  Future<UserProfile?> updateProfile(UserProfile profile) async {
    const url = "$baseUrl/client/me";
    final accessToken = await _securityService.getAccessToken();

    final Map<String, dynamic> body = {
      if (profile.email != null && profile.email!.isNotEmpty)
        "email": profile.email,
      if (profile.name != null && profile.name!.isNotEmpty)
        "name": profile.name,
      if (profile.birthDate.isNotEmpty) "dob": profile.birthDate,
      if (profile.avatarId != null) "avatarId": profile.avatarId,
    };

    if (body.isEmpty) return null;

    try {
      final response = await _dio.patch(
        url,
        data: body,
        options: Options(headers: {"Authorization": "Bearer $accessToken"}),
      );

      if (response.statusCode == 200) {
        final data = response.data;
        final profileData = data["profile"];
        if (profileData == null) return null;

        String? parsedDate;
        if (profileData["dob"] != null) {
          try {
            parsedDate = DateFormat(
              'dd.MM.yyyy',
            ).format(DateTime.parse(profileData["dob"]));
          } catch (_) {}
        }

        final int? avatarId = profileData["avatarId"];
        String? avatarUrl;

        if (avatarId != null && profileData["avatar"] != null) {
          final String? avatarPath = profileData["avatar"]["path"];
          if (avatarPath != null && avatarPath.isNotEmpty) {
            avatarUrl = "$fileBaseUrl/${avatarPath.replaceFirst('/', '')}";
          }
        }

        dynamic balanceRaw = data["balance"];
        int? balance;
        if (balanceRaw is int) {
          balance = balanceRaw;
        } else if (balanceRaw is String) {
          balance = int.tryParse(balanceRaw);
        }

        return UserProfile(
          name: profileData["name"] ?? "",
          birthDate: parsedDate ?? "",
          phone: data["phone"] ?? "",
          balance: balance,
          email: profileData["email"] ?? "",
          avatarId: avatarId,
          avatarUrl: avatarUrl,
        );
      }
      return null;
    } on DioException catch (e) {
      print(
        "ERROR: updateProfile failed: ${e.response?.statusCode} - ${e.message}",
      );
      rethrow;
    }
  }

  Future<UserCheckFlags?> checkUser() async {
    const url = "$baseUrl/scooterorder/check";
    final accessToken = await _securityService.getAccessToken();

    try {
      final response = await _dio.post(
        url,
        options: Options(headers: {"Authorization": "Bearer $accessToken"}),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        final dto = UserCheckResponseDto.fromJson(response.data);
        return UserCheckFlags(
          hasFine: dto.hasFine,
          hasUnpaidOrder: dto.hasUnpaidOrder,
          hasCard: dto.hasCard,
        );
      }
      return null;
    } catch (e) {
      print("ERROR: checkUser failed: $e");
      return null;
    }
  }

  Future<int?> uploadPhoto(File imageFile) async {
    try {
      final mimeType = lookupMimeType(imageFile.path) ?? 'image/jpeg';
      final formData = FormData.fromMap({
        'file': await MultipartFile.fromFile(
          imageFile.path,
          filename: basename(imageFile.path),
          contentType: MediaType.parse(mimeType),
        ),
      });

      final response = await _dio.post(
        "$baseUrl/client/upload",
        data: formData,
        options: await _getAuthOptions(),
      );

      return response.data['id'];
    } catch (e) {
      rethrow;
    }
  }

  Future<List<int>> uploadScooterPhotos(List<File> images) async {
    try {
      final formData = FormData();
      for (var file in images) {
        final mimeType = lookupMimeType(file.path) ?? 'image/jpeg';
        formData.files.add(
          MapEntry(
            'files',
            await MultipartFile.fromFile(
              file.path,
              filename: basename(file.path),
              contentType: MediaType.parse(mimeType),
            ),
          ),
        );
      }

      final response = await _dio.post(
        "$baseUrl/scooterorder/upload",
        data: formData,
        options: await _getAuthOptions(),
      );

      final List<dynamic> list = response.data['data'] ?? [];
      return list.map((item) => item['id'] as int).toList();
    } catch (e) {
      rethrow;
    }
  }

  Future<ScootersResponse?> getScooters({
    required List<double> area,
    int page = 1,
    int pageSize = 500,
  }) async {
    const path = "$baseUrl/scooter/available";

    final queryParams = {
      'readAll': 'true',
      'area': area.map((coord) => coord.toString()).toList(),
      'page': page,
      'pageSize': pageSize,
    };

    final accessToken = await _securityService.getAccessToken();
    if (accessToken == null) return null;

    try {
      final response = await _dio.get(
        path,
        queryParameters: queryParams,
        options: Options(headers: {"Authorization": "Bearer $accessToken"}),
      );

      if (response.statusCode == 200) {
        return ScootersResponse.fromJson(response.data);
      }
      return null;
    } catch (e) {
      print("ERROR: getScooters failed: $e");
      return null;
    }
  }

  Future<Scooter?> getScooterById({required int id}) async {
    try {
      final response = await _dio.get(
        "$baseUrl/scooter/$id/client",
        options: await _getAuthOptions(),
      );
      if (response.statusCode == 200 || response.statusCode == 201) {
        return Scooter.fromJson(response.data);
      }
    } on DioException catch (e) {
      if (e.response?.statusCode == 401) throw UnauthorizedException();
      if (e.response?.statusCode == 403) throw AuthBlockException();
    }
    return null;
  }

  Future<Scooter?> getScooterByTitle({required String title}) async {
    final url = "$baseUrl/scooter/$title/code";

    try {
      final response = await _dio.get(url, options: await _getAuthOptions());

      if (response.statusCode == 200 || response.statusCode == 201) {
        return Scooter.fromJson(response.data);
      }

      if (response.statusCode == 404) {
        throw ScooterNotFoundException(message: "Самокат не найден");
      }

      return null;
    } on DioException catch (e) {
      if (e.response?.statusCode == 401) throw UnauthorizedException();
      if (e.response?.statusCode == 403) throw AuthBlockException();
      if (e.response?.statusCode == 404) throw ScooterNotFoundException();
      return null;
    }
  }

  Future<ZonesResponse?> getZones({
    required List<double> area,
    int page = 1,
    int pageSize = 500,
  }) async {
    const path = "$baseUrl/zone/available";

    final queryParams = {
      'readAll': 'true',
      'area': area.map((coord) => coord.toString()).toList(),
      'page': page,
      'pageSize': pageSize,
    };

    try {
      final response = await _dio.get(
        path,
        queryParameters: queryParams,
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200) {
        return ZonesResponse.fromJson(response.data);
      }
      return null;
    } catch (e) {
      print("ERROR: getZones failed: $e");
      return null;
    }
  }

  Future<TariffsResponse?> getAvailableTariffs({required int scooterId}) async {
    final url = "$baseUrl/scooterplan/$scooterId/available";

    try {
      final response = await _dio.get(url, options: await _getAuthOptions());

      if (response.statusCode == 200) {
        return TariffsResponse.fromJson(response.data);
      }
      return null;
    } catch (e) {
      print("ERROR: getAvailableTariffs failed: $e");
      return null;
    }
  }

  Future<List<ClientSubscription>> getClientSubscriptions() async {
    const url = "$baseUrl/scootersubscription/client";

    try {
      final response = await _dio.get(url, options: await _getAuthOptions());

      if (response.statusCode == 200) {
        final Map<String, dynamic> responseData = response.data;
        final List<dynamic> items = responseData['data'] ?? [];

        return items.map((item) {
          return ClientSubscription.fromJson(item);
        }).toList();
      }
      return [];
    } catch (e) {
      print("APISERVICE (getClientSubscriptions) Error: $e");
      return [];
    }
  }

  Future<SubscriptionsResponse?> getAvailableSubscriptions() async {
    const url = "$baseUrl/scootersubscription/available";

    try {
      final response = await _dio.get(url, options: await _getAuthOptions());

      if (response.statusCode == 200) {
        return SubscriptionsResponse.fromJson(response.data);
      }
      return null;
    } catch (e) {
      print("APISERVICE (getAvailableSubscriptions) Error: $e");
      return null;
    }
  }

  Future<Subscription?> getSubscriptionById({required int id}) async {
    final url = "$baseUrl/scootersubscription/$id/client";

    try {
      final response = await _dio.get(url, options: await _getAuthOptions());

      if (response.statusCode == 200 || response.statusCode == 201) {
        return Subscription.fromJson(response.data);
      }
      return null;
    } on DioException catch (e) {
      if (e.response?.statusCode == 401) throw UnauthorizedException();
      if (e.response?.statusCode == 403) throw AuthBlockException();
      print("APISERVICE (getSubscriptionById) Error: $e");
      return null;
    }
  }

  Future<bool> activateSubscription({required int optionId}) async {
    const url = "$baseUrl/scootersubscription/client";

    try {
      final response = await _dio.post(
        url,
        data: {"optionId": optionId},
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 201) {
        return response.data["success"] == true;
      }
      return false;
    } on DioException catch (e) {
      if (e.response?.statusCode == 401) throw UnauthorizedException();
      if (e.response?.statusCode == 403) throw AuthBlockException();
      print("APISERVICE (activateSubscription) Error: $e");
      return false;
    }
  }

  Future<void> addPaymentCard({
    required String cardNumber,
    required String cardHolder,
    required int expirationMonth,
    required int expirationYear,
    required String cvv,
  }) async {
    const url = "$baseUrl/client/card";
    final cleanCardNumber = cardNumber.replaceAll(' ', '');

    try {
      final response = await _dio.post(
        url,
        data: {
          "cardNumber": cleanCardNumber,
          "cardHolder": cardHolder,
          "expirationMonth": expirationMonth,
          "expirationYear": expirationYear,
          "cvv": cvv,
        },
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return;
      }

      throw AuthException('Непредвиденный статус: ${response.statusCode}', 0);
    } on DioException catch (e) {
      final data = e.response?.data;
      final statusCode = e.response?.statusCode;

      if (statusCode == 400) {
        final message = _parseErrorMessage(data);
        throw AuthException(message ?? 'Неверные данные карты', 0);
      } else if (statusCode == 401) {
        throw UnauthorizedException();
      } else if (statusCode == 403) {
        throw AuthBlockException();
      } else if (statusCode == 404) {
        throw AuthException('Пользователь не найден', 0);
      }
      throw AuthException('Ошибка сервера: $statusCode', 0);
    }
  }

  Future<void> setMainPaymentCard(int cardId) async {
    final url = "$baseUrl/client/card/$cardId";

    try {
      await _dio.put(
        url,
        data: {"isMain": true},
        options: await _getAuthOptions(),
      );
    } on DioException catch (e) {
      final statusCode = e.response?.statusCode;

      if (statusCode == 400) {
        final message = _parseErrorMessage(e.response?.data);
        throw AuthException(
          message ?? 'Ошибка при установке основной карты',
          0,
        );
      } else if (statusCode == 401) {
        throw UnauthorizedException();
      } else if (statusCode == 403) {
        throw AuthBlockException();
      } else if (statusCode == 404) {
        throw AuthException('Карта не найдена', 0);
      }
      throw AuthException('Ошибка сервера: $statusCode', 0);
    }
  }

  Future<void> removePaymentCard(int cardId) async {
    final url = "$baseUrl/client/card/$cardId";

    try {
      await _dio.delete(url, options: await _getAuthOptions());
    } on DioException catch (e) {
      final statusCode = e.response?.statusCode;

      if (statusCode == 400) {
        final message = _parseErrorMessage(e.response?.data);
        throw AuthException(message ?? 'Ошибка при удалении карты', 0);
      } else if (statusCode == 401) {
        throw UnauthorizedException();
      } else if (statusCode == 403) {
        throw AuthBlockException();
      } else if (statusCode == 404) {
        throw AuthException('Карта не найдена', 0);
      }
      throw AuthException('Ошибка сервера: $statusCode', 0);
    }
  }

  Future<ScooterOrder?> bookScooter({
    required int scooterId,
    required int planId,
    int? subscriptionId,
    int? cardId,
    required bool isBalance,
    required bool isInsurance,
  }) async {
    try {
      final response = await _dio.post(
        "$baseUrl/scooterorder/booking",
        data: {
          "scooterId": scooterId,
          "planId": planId,
          "subscriptionId": subscriptionId,
          "cardId": cardId,
          "isBalance": isBalance,
          "isInsurance": isInsurance,
        },
        options: await _getAuthOptions(),
      );

      return ScooterOrder.fromJson(response.data);
    } on DioException catch (e) {
      if (e.response?.statusCode == 400) {
        final data = e.response?.data;

        if (data is Map && data['message'] is List) {
          final firstError = data['message'][0]['message'].toString();

          if (firstError.contains("Wrong start zone")) {
            throw WrongZoneException(
              message: "Некорректная зона для начала поездки.",
            );
          }
        }

        if (data is Map && data['message'] is String) {}
      } else {
        _handleDioError(e);
      }
      rethrow;
    }
  }

  Future<ScooterOrder?> startRide(int orderId) async {
    try {
      final response = await _dio.put(
        "$baseUrl/scooterorder/$orderId/start",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return ScooterOrder.fromJson(response.data);
      }
      return null;
    } on DioException catch (e) {
      if (e.response?.statusCode == 400) {
        final data = e.response?.data;

        if (data is Map && data['message'] is List) {
          final firstError = data['message'][0]['message'].toString();

          if (firstError.contains("Wrong start zone")) {
            throw WrongZoneException(
              message: "Некорректная зона для начала поездки.",
            );
          }
        }

        if (data is Map && data['message'] is String) {}
      } else {
        _handleDioError(e);
      }
      rethrow;
    }
  }

  Future<ScooterOrder?> cancelRide(int orderId) async {
    try {
      final response = await _dio.put(
        "$baseUrl/scooterorder/$orderId/cancel",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return ScooterOrder.fromJson(response.data);
      }
      return null;
    } on DioException catch (e) {
      _handleDioError(e);
      return null;
    }
  }

  Future<ScooterOrder?> pauseRide(int orderId) async {
    try {
      final response = await _dio.put(
        "$baseUrl/scooterorder/$orderId/pause",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return ScooterOrder.fromJson(response.data);
      }
      return null;
    } on DioException catch (e) {
      _handleDioError(e);
      return null;
    }
  }

  Future<ScooterOrder?> resumeRide(int orderId) async {
    try {
      final response = await _dio.put(
        "$baseUrl/scooterorder/$orderId/resume",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return ScooterOrder.fromJson(response.data);
      }
      return null;
    } on DioException catch (e) {
      _handleDioError(e);
      return null;
    }
  }

  Future<ScooterOrder?> finishRide({
    required int orderId,
    required List<int> filesId,
  }) async {
    try {
      final response = await _dio.put(
        "$baseUrl/scooterorder/$orderId/finish",
        data: {"files": filesId},
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return ScooterOrder.fromJson(response.data);
      }
      return null;
    } on DioException catch (e) {
      if (e.response?.statusCode == 400) {
        final data = e.response?.data;

        if (data is Map && data['message'] is List) {
          final firstError = data['message'][0]['message'].toString();

          if (firstError.contains("Wrong start zone")) {
            throw WrongZoneException(
              message: "Некорректная зона для завершения поездки.",
            );
          }
        }

        if (data is Map && data['message'] is String) {}
      } else {
        _handleDioError(e);
      }
      rethrow;
    }
  }

  Future<void> payRide(int orderId) async {
    try {
      final response = await _dio.put(
        "$baseUrl/scooterorder/$orderId/pay",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        // return ScooterOrder.fromJson(response.data);
        return;
      }
    } on DioException catch (e) {
      _handleDioError(e);
      return;
    }
  }

  Future<List<ScooterOrder>> getClientOrders() async {
    try {
      final response = await _dio.get(
        "$baseUrl/scooterorder/active",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200) {
        final data = response.data;
        if (data is Map<String, dynamic> && data['data'] is List) {
          final List ordersList = data['data'];
          return ordersList.map((json) => ScooterOrder.fromJson(json)).toList();
        }
      }
      return [];
    } on DioException catch (e) {
      _handleDioError(e);
      return [];
    }
  }

  Future<ActiveScooterOrder?> updateScooterOrderData({
    required int orderId,
    Map<String, dynamic>? data,
  }) async {
    try {
      final response = await _dio.get(
        "$baseUrl/scooterorder/$orderId/data",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return ActiveScooterOrder.fromJson(response.data);
      }
      return null;
    } on DioException catch (e) {
      _handleDioError(e);
      return null;
    }
  }

  Future<ScooterOrder?> getScooterOrderById({required int id}) async {
    try {
      final response = await _dio.get(
        "$baseUrl/scooterorder/$id/client",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return ScooterOrder.fromJson(response.data);
      }
      return null;
    } on DioException catch (e) {
      _handleDioError(e);
      return null;
    }
  }

  Future<void> payScooterOrderWithPhotos({
    required int orderId,
    required int? cardId,
    required bool isBalance,
  }) async {
    try {
      final response = await _dio.put(
        "$baseUrl/scooterorder/$orderId/pay",
        data: {"cardId": cardId, "isBalance": isBalance},
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return;
      }
    } on DioException catch (e) {
      _handleDioError(e);
    }
  }

  Future<Map<String, dynamic>> getScooterOrderHistory({
    int page = 1,
    int pageSize = 20,
  }) async {
    try {
      final response = await _dio.get(
        "$baseUrl/scooterorder/history",
        queryParameters: {"page": page, "pageSize": pageSize},
        options: await _getAuthOptions(),
      );

      return response.data;
    } on DioException catch (e) {
      _handleDioError(e);
      return {};
    }
  }

  Future<List<Point>> getScooterOrderRouteHistory({required int id}) async {
    try {
      final response = await _dio.get(
        "$baseUrl/scooterorder/$id/routehistory",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200) {
        final String routeString = response.data['route'] ?? '[]';
        final List<dynamic> routeList = json.decode(routeString);

        return routeList
            .map(
              (item) => Point(
                (item[1] as num).toDouble(),
                (item[0] as num).toDouble(),
              ),
            )
            .toList();
      }

      throw RouteHistoryNotFoundException(
        message: "История маршрута не найдена",
      );
    } on DioException catch (e) {
      if (e.response?.statusCode == 401) throw UnauthorizedException();
      if (e.response?.statusCode == 403) throw AuthBlockException();
      if (e.response?.statusCode == 404) throw RouteHistoryNotFoundException();
      rethrow;
    }
  }

  Future<Map<String, dynamic>> getNews() async {
    try {
      final response = await _dio.get(
        "$baseUrl/news",
        options: await _getAuthOptions(),
      );
      return response.data;
    } on DioException catch (e) {
      _handleDioError(e);
      return {};
    }
  }

  Future<Map<String, dynamic>?> getNewsById(int newsId) async {
    try {
      final response = await _dio.get(
        '$baseUrl/news/$newsId',
        options: await _getAuthOptions(),
      );
      return response.data;
    } on DioException catch (e) {
      _handleDioError(e);
      return null;
    }
  }

  Future<Map<String, dynamic>?> cancelNotification(int id) async {
    try {
      final response = await _dio.put(
        "$baseUrl/notification/client/$id/cancel",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200 || response.statusCode == 201) {
        return response.data;
      }
      return null;
    } on DioException catch (e) {
      _handleDioError(e);
      return null;
    }
  }

  Stream<Map<String, dynamic>> getNotificationsStream() {
    final url = "$baseUrl/notification/client/stream";

    final controller = StreamController<Map<String, dynamic>>();

    _securityService.getAccessToken().then((accessToken) {
      if (accessToken == null) {
        controller.addError(UnauthorizedException());
        return;
      }

      print("[SSE] Subscribing via flutter_client_sse...");

      SSEClient.subscribeToSSE(
        method: SSERequestType.GET,
        url: url,
        header: {
          "Authorization": "Bearer $accessToken",
          "Accept": "text/event-stream",
          "Cache-Control": "no-cache",
        },
      ).listen(
        (event) {
          print("Received line [SSE]: id=${event.id}, event=${event.event}");
          print("Data [SSE]: ${event.data}");

          if (event.data != null && event.data!.isNotEmpty) {
            try {
              final Map<String, dynamic> jsonData = jsonDecode(event.data!);
              print("✅ [SSE] Parsed JSON: $jsonData");
              controller.add(jsonData);
            } catch (e) {
              print("❌ [SSE] JSON Parse Error: $e | Raw: ${event.data}");
            }
          }
        },
        onError: (error) {
          print(" [SSE] Library Error: $error");
          controller.addError(error);
        },
        onDone: () {
          print(" [SSE] Library Stream Closed");
          controller.close();
        },
      );
    });

    return controller.stream;
  }

  Future<List<Map<String, dynamic>>> getNotifications() async {
    final url = Uri.parse('$baseUrl/notification/client');

    final accessToken = await _securityService.getAccessToken();
    if (accessToken == null) {
      print("APISERVICE Error: Access token is null.");
      throw UnauthorizedException();
    }

    print("GET NOTIFICATIONS REQUEST:");
    print("URL: $url");

    final response = await http.get(
      url,
      headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer $accessToken",
      },
    );

    print("GET NOTIFICATIONS RESPONSE:");
    print("STATUS: ${response.statusCode}");
    print("BODY: ${response.body}");

    if (response.statusCode == 200) {
      final data = jsonDecode(utf8.decode(response.bodyBytes));

      // ✅ Проверяем, является ли ответ массивом или объектом с data[]
      if (data is List) {
        return data.cast<Map<String, dynamic>>();
      } else if (data is Map<String, dynamic>) {
        final list = data['data'];
        if (list is List) {
          return list.cast<Map<String, dynamic>>();
        } else {
          throw Exception(
            'Expected a List under "data" but got ${list.runtimeType}',
          );
        }
      } else {
        throw Exception('Expected a List or Map but got ${data.runtimeType}');
      }
    } else if (response.statusCode == 401) {
      throw UnauthorizedException();
    } else if (response.statusCode == 403) {
      throw AuthBlockException();
    }

    throw Exception('Ошибка сервера: ${response.statusCode}');
  }

  Future<List<Certificate>> getCertificates() async {
    try {
      final response = await _dio.get(
        "$baseUrl/certificate",
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 200) {
        final data = response.data;
        final dataList = data['data'] as List<dynamic>;
        return dataList.map((json) => Certificate.fromJson(json)).toList();
      }
      return [];
    } on DioException catch (e) {
      _handleDioError(e);
      return [];
    }
  }

  Future<Map<String, dynamic>?> purchaseCertificate({
    required int certificateId,
    required int cardId,
  }) async {
    try {
      final response = await _dio.post(
        "$baseUrl/certificate/$certificateId",
        data: {"cardId": cardId},
        options: await _getAuthOptions(),
      );

      if (response.statusCode == 201) {
        return response.data;
      }
      return null;
    } on DioException catch (e) {
      _handleDioError(e);
      return null;
    }
  }

  void _handleDioError(DioException e) {
    if (e.response?.statusCode == 401) throw UnauthorizedException();
    if (e.response?.statusCode == 403) throw AuthBlockException();

    final message = _parseErrorMessage(e.response?.data);
    throw AuthException(message ?? 'Ошибка сервера', 0);
  }

  String? _parseErrorMessage(dynamic data) {
    if (data is Map<String, dynamic>) {
      final messages = data['message'] as List?;
      if (messages != null && messages.isNotEmpty) {
        final firstError = messages.first as Map<String, dynamic>?;
        return firstError?['message'] as String?;
      }
      return data['message'] as String?;
    }
    return null;
  }

  Future<Map<String, dynamic>> applyPromoCode(String code) async {
    final token = await _securityService.getAccessToken();
    if (token == null) throw Exception('No access token');

    final response = await _dio.post(
      '$baseUrl/promocode/apply',
      data: {'code': code},
      options: Options(
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer $token',
        },
      ),
    );

    if (response.statusCode == 201 || response.statusCode == 200) {
      return response.data;
    } else if (response.statusCode == 404) {
      throw PromoCodeNotFoundException();
    } else {
      throw Exception('API Error: ${response.statusCode}');
    }
  }
}

// ✅ Добавь исключение (можно в отдельный файл)
class PromoCodeNotFoundException implements Exception {}
