import 'package:dio/dio.dart';
import '../constants.dart';
import '../event_bus.dart';
import 'exceptions/app_exception.dart';
import 'interceptors/error_interceptor.dart';
import 'interceptors/request_interceptor.dart';
import 'interceptors/response_interceptor.dart';
import '../storage/storage_service.dart';
import 'response_model.dart';

class DioRequest {
  static final DioRequest instance = DioRequest._internal();
  factory DioRequest() => instance;
  DioRequest._internal();

  late final Dio _dio;
  final Map<String, CancelToken> _cancelTokens = {};
  late final StorageService _storageService;

  void init(StorageService storageService) {
    _storageService = storageService;
    _dio = Dio(BaseOptions(
      baseUrl: Constants.baseUrl,
      connectTimeout: const Duration(milliseconds: Constants.connectTimeout),
      receiveTimeout: const Duration(milliseconds: Constants.receiveTimeout),
      sendTimeout: const Duration(milliseconds: Constants.sendTimeout),
    ));

    _dio.interceptors.addAll([
      RequestInterceptor(_storageService),
      ResponseInterceptor(),
      ErrorInterceptor(),
    ]);
  }

  Future<ResponseModel<T>> get<T>(
    String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    T Function(dynamic)? fromJsonT,
  }) async {
    return _request<T>(
      path,
      method: 'GET',
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      fromJsonT: fromJsonT,
    );
  }

  Future<ResponseModel<T>> post<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    T Function(dynamic)? fromJsonT,
  }) async {
    return _request<T>(
      path,
      method: 'POST',
      data: data,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      fromJsonT: fromJsonT,
    );
  }

  Future<ResponseModel<T>> put<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    T Function(dynamic)? fromJsonT,
  }) async {
    return _request<T>(
      path,
      method: 'PUT',
      data: data,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      fromJsonT: fromJsonT,
    );
  }

  Future<ResponseModel<T>> delete<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    T Function(dynamic)? fromJsonT,
  }) async {
    return _request<T>(
      path,
      method: 'DELETE',
      data: data,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      fromJsonT: fromJsonT,
    );
  }

  Future<ResponseModel<T>> patch<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    T Function(dynamic)? fromJsonT,
  }) async {
    return _request<T>(
      path,
      method: 'PATCH',
      data: data,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      fromJsonT: fromJsonT,
    );
  }

  Future<ResponseModel<T>> uploadFile<T>(
    String path,
    String filePath, {
    String fieldName = 'file',
    Map<String, dynamic>? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    T Function(dynamic)? fromJsonT,
  }) async {
    final formData = FormData.fromMap({
      if (data != null) ...data,
      fieldName: await MultipartFile.fromFile(filePath),
    });

    return _request<T>(
      path,
      method: 'POST',
      data: formData,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      fromJsonT: fromJsonT,
      onSendProgress: onSendProgress,
    );
  }

  Future<ResponseModel<T>> uploadFiles<T>(
    String path,
    List<String> filePaths, {
    String fieldName = 'files',
    Map<String, dynamic>? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    T Function(dynamic)? fromJsonT,
  }) async {
    final formData = FormData.fromMap({
      if (data != null) ...data,
      fieldName: await Future.wait(
        filePaths.map((path) => MultipartFile.fromFile(path)),
      ),
    });

    return _request<T>(
      path,
      method: 'POST',
      data: formData,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      fromJsonT: fromJsonT,
      onSendProgress: onSendProgress,
    );
  }

  Future<ResponseModel<T>> uploadFormData<T>(
    String path,
    FormData formData, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    T Function(dynamic)? fromJsonT,
  }) async {
    return _request<T>(
      path,
      method: 'POST',
      data: formData,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      fromJsonT: fromJsonT,
      onSendProgress: onSendProgress,
    );
  }

  Future<ResponseModel<String>> downloadFile(
    String url,
    String savePath, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onReceiveProgress,
  }) async {
    final token = cancelToken ?? CancelToken();
    final uuid = _generateUuid();
    _cancelTokens[uuid] = token;

    try {
      await _dio.download(
        url,
        savePath,
        queryParameters: queryParameters,
        options: options,
        cancelToken: token,
        onReceiveProgress: onReceiveProgress,
      );

      return ResponseModel(
        code: 200,
        msg: '下载成功',
        data: savePath,
        uuid: uuid,
        success: true,
        timestamp: DateTime.now().millisecondsSinceEpoch,
      );
    } catch (e) {
      return _handleError<String>(e, uuid);
    } finally {
      _cancelTokens.remove(uuid);
    }
  }

  Future<ResponseModel<List<int>>> downloadBytes(
    String url, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onReceiveProgress,
  }) async {
    final token = cancelToken ?? CancelToken();
    final uuid = _generateUuid();
    _cancelTokens[uuid] = token;

    try {
      final response = await _dio.get<List<int>>(
        url,
        queryParameters: queryParameters,
        options: (options ?? Options()).copyWith(
          responseType: ResponseType.bytes,
        ),
        cancelToken: token,
        onReceiveProgress: onReceiveProgress,
      );

      return ResponseModel<List<int>>(
        code: 200,
        msg: '下载成功',
        data: response.data,
        uuid: uuid,
        success: true,
        timestamp: DateTime.now().millisecondsSinceEpoch,
      );
    } catch (e) {
      return _handleError<List<int>>(e, uuid);
    } finally {
      _cancelTokens.remove(uuid);
    }
  }

  void cancelRequest(String uuid) {
    final token = _cancelTokens[uuid];
    if (token != null && !token.isCancelled) {
      token.cancel('Request cancelled by user');
      _cancelTokens.remove(uuid);
    }
  }

  void cancelAll() {
    _cancelTokens.forEach((uuid, token) {
      if (!token.isCancelled) {
        token.cancel('All requests cancelled');
      }
    });
    _cancelTokens.clear();
  }

  Future<ResponseModel<T>> _request<T>(
    String path, {
    required String method,
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    T Function(dynamic)? fromJsonT,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    final token = cancelToken ?? CancelToken();
    final uuid = _generateUuid();
    _cancelTokens[uuid] = token;

    try {
      final response = await _dio.request(
        path,
        data: data,
        queryParameters: queryParameters,
        options: (options ?? Options()).copyWith(
          method: method,
        ),
        cancelToken: token,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );

      Map<String, dynamic> jsonData;
      if (response.data is Map<String, dynamic>) {
        jsonData = Map<String, dynamic>.from(response.data);
        // 如果响应中有token但没有data，把token放到data里
        // if (jsonData.containsKey('token') && !jsonData.containsKey('data')) {
        //   jsonData['data'] = {'token': jsonData['token']};
        // }
      } else {
        jsonData = {
          'code': response.statusCode ?? -1,
          'msg': '请求成功',
          'data': response.data
        };
      }

      final result = ResponseModel<T>.fromJson(
        jsonData,
        uuid: uuid,
        fromJsonT: fromJsonT,
      );

      // 当后端返回 HTTP 200 但 body 中 code 为 401 时，
      // 表示 token 已在后端失效，触发 token 过期事件
      if (result.code == 401) {
        eventBus.emit(TokenExpiredEvent());
      }

      return result;
    } catch (e) {
      return _handleError<T>(e, uuid);
    } finally {
      _cancelTokens.remove(uuid);
    }
  }

  ResponseModel<T> _handleError<T>(dynamic error, String uuid) {
    if (error is DioException && error.error is AppException) {
      final appException = error.error as AppException;
      return ResponseModel<T>.error(
        uuid: appException.uuid ?? uuid,
        msg: appException.message,
        code: appException.code ?? -1,
      );
    }

    return ResponseModel<T>.error(
      uuid: uuid,
      msg: error.toString(),
    );
  }

  String _generateUuid() {
    return '${DateTime.now().millisecondsSinceEpoch}_${_randomString(8)}';
  }

  String _randomString(int length) {
    const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
    return List.generate(length, (index) => chars[index % chars.length]).join();
  }
}
