// Author: fengshengxiong
// Date: 2024/5/9
// Description: dio封装

import 'dart:io';

import 'package:dio/dio.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/data/api/music_api.dart';
import 'package:tone_snap/data/network/base_error.dart';
import 'package:tone_snap/data/network/dio_interceptor.dart';
import 'package:tone_snap/utils/log_util.dart';

class DioClient {
  static final DioClient _instance = DioClient._internal();

  factory DioClient({String? baseUrl}) => getInstance(baseUrl: baseUrl);
  late Dio _dio;

  static DioClient getInstance({String? baseUrl}) {
    if (baseUrl == null) {
      return _instance._defaultBaseUrl();
    } else {
      return _instance._newBaseUrl(baseUrl);
    }
  }

  /// 默认域名
  DioClient _defaultBaseUrl() {
    if (_dio.options.baseUrl != MusicApi.baseUrl) {
      _dio.options.baseUrl = MusicApi.baseUrl;
    }
    return _instance;
  }

  /// 用于指定特定域名
  DioClient _newBaseUrl(String baseUrl) {
    _dio.options.baseUrl = baseUrl;
    return _instance;
  }

  DioClient._internal() {
    _dio = Dio();
    final baseOptions = BaseOptions(
      baseUrl: MusicApi.baseUrl,
      connectTimeout: const Duration(seconds: 30),
      receiveTimeout: const Duration(seconds: 30),
    );
    _dio.options = baseOptions;
    _dio.interceptors.add(DioInterceptor());
    _dio.interceptors.add(PrettyDioLogger(
      requestHeader: false,
      requestBody: true,
      responseBody: false,
      compact: false,
      maxWidth: 160,
    ));
  }

  /// 请求
  Future request<T>(
    String path, {
    bool showLoading = false,
    bool showToast = false,
    required RequestMethod requestMethod,
    dynamic data,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    Options? options,
    bool isFormData = false,
    T Function(Map<String, dynamic>)? formJson,
    required Function(T? result) success,
    Function(BaseError baseError)? fail,
  }) async {
    try {
      BaseEasyLoading.loading(show: showLoading);
      _dio.options.method = requestMethod.method;
      Response response = await _dio.request(
        path,
        data: isFormData ? FormData.fromMap(data) : data,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
        options: options,
      );
      BaseEasyLoading.dismiss(dismiss: showLoading);
      if (response.statusCode == HttpStatus.ok) {
        if (response.data != null && formJson != null) {
          success(formJson(response.data));
        } else {
          success(response.data);
        }
      }
    } catch (e) {
      BaseError baseError = getError(e);
      if (fail != null) fail(baseError);
      BaseEasyLoading.toast(baseError.message, show: showToast);
      LogUtil.e(e.toString());
    }
  }

  /// 下载
  Future<void> download(
    String urlPath,
    dynamic savePath, {
    bool showToast = false,
    ProgressCallback? onReceiveProgress,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    data,
    Options? options,
    required Function() success,
    Function(BaseError? err)? fail,
  }) async {
    try {
      await _dio.download(
        urlPath,
        savePath,
        onReceiveProgress: onReceiveProgress,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
        data: data,
        options: Options(receiveTimeout: const Duration(seconds: 0)),
      );
      success();
    } catch (e) {
      BaseError baseError = getError(e);
      if (fail != null) fail(baseError);
      BaseEasyLoading.toast(baseError.message, show: showToast);
      LogUtil.e(baseError.message);
    }
  }

  BaseError getError(Object e) {
    if (e.runtimeType == DioException) {
      switch ((e as DioException).type) {
        case DioExceptionType.connectionTimeout:
          return OtherError(statusCode: DioExceptionType.connectionTimeout.index, statusMessage: 'Connection timed out');
        case DioExceptionType.sendTimeout:
          return OtherError(statusCode: DioExceptionType.sendTimeout.index, statusMessage: 'Send timeout');
        case DioExceptionType.receiveTimeout:
          return OtherError(statusCode: DioExceptionType.receiveTimeout.index, statusMessage: 'Receive timeout');
        case DioExceptionType.badCertificate:
          return OtherError(statusCode: DioExceptionType.badCertificate.index, statusMessage: 'Certificate error');
        case DioExceptionType.cancel:
          return OtherError(statusCode: DioExceptionType.cancel.index, statusMessage: 'Request canceled');
        case DioExceptionType.connectionError:
          return OtherError(statusCode: DioExceptionType.connectionError.index, statusMessage: 'Connection error');
        case DioExceptionType.unknown:
          return OtherError(statusCode: DioExceptionType.unknown.index, statusMessage: 'Unknown error');
        case DioExceptionType.badResponse:
          final response = e.response;
          if (response!.statusCode == 401) {
            return NeedLogin();
          } else if (response.statusCode == 403) {
            return NeedAuth();
          } else {
            return OtherError(
              statusCode: response.statusCode,
              statusMessage: response.statusMessage,
            );
          }
      }
    }
    return OtherError(statusCode: -1, statusMessage: 'Unknown error');
  }
}

enum RequestMethod {
  get('GET'),
  post('POST'),
  put('PUT'),
  head('HEAD'),
  delete('DELETE'),
  patch('PATCH');

  const RequestMethod(
    this.method,
  );

  final String method;
}
