import 'dart:async';

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import '../storage/secure_storage.dart';
import '../storage/storage_keys.dart';
import '../errors/exceptions.dart';
import '../config/app_config.dart';
import '../services/auth_event_service.dart';

/// API 客户端
///
/// 封装 Dio HTTP 客户端，提供:
/// - 自动 Token 注入
/// - 错误处理
/// - 请求/响应日志
/// - Token 自动刷新（带并发锁）
class ApiClient {
  final Dio _dio;
  final SecureStorage _secureStorage;

  /// Token 刷新锁，防止并发刷新导致的竞态条件
  /// 当多个请求同时收到 401 时，只有第一个会执行刷新，其他请求等待结果
  Completer<bool>? _refreshCompleter;

  /// 标记是否已经通知过 token 过期，避免重复通知
  bool _hasNotifiedTokenExpired = false;

  ApiClient({
    required SecureStorage secureStorage,
    String? baseUrl,
  })  : _secureStorage = secureStorage,
        _dio = Dio(BaseOptions(
          baseUrl: baseUrl ?? _defaultBaseUrl,
          connectTimeout: const Duration(seconds: 30),
          receiveTimeout: const Duration(seconds: 30),
          headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
          },
        )) {
    _setupInterceptors();
  }

  // 从配置获取 API 地址
  static String get _defaultBaseUrl => AppConfig.instance.apiBaseUrl;

  /// 设置拦截器
  void _setupInterceptors() {
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: _onRequest,
      onResponse: _onResponse,
      onError: _onError,
    ));

    // Debug 模式下添加日志
    if (kDebugMode) {
      _dio.interceptors.add(LogInterceptor(
        requestBody: true,
        responseBody: true,
        error: true,
      ));
    }
  }

  /// 请求拦截器 - 自动注入 Token
  Future<void> _onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    // 不需要 Token 的接口
    final publicPaths = [
      '/user/auto-create',
      '/user/auto-login',  // 刷新 token 接口不需要带旧 token
      '/user/login',
      '/user/register',
      '/user/send-sms-code',
      '/user/recover-by-mnemonic',
      '/user/recover-by-phone',
    ];

    final isPublic = publicPaths.any((path) => options.path.contains(path));

    if (!isPublic) {
      final token = await _secureStorage.read(key: StorageKeys.accessToken);
      if (token != null && token.isNotEmpty) {
        options.headers['Authorization'] = 'Bearer $token';
      }
    }

    handler.next(options);
  }

  /// 响应拦截器
  void _onResponse(
    Response response,
    ResponseInterceptorHandler handler,
  ) {
    handler.next(response);
  }

  /// 错误拦截器
  Future<void> _onError(
    DioException error,
    ErrorInterceptorHandler handler,
  ) async {
    // 401 错误尝试刷新 Token
    // 排除 auto-login 本身的 401 错误，避免递归
    final isAutoLogin = error.requestOptions.path.contains('/user/auto-login');
    if (error.response?.statusCode == 401 && !isAutoLogin) {
      debugPrint(
          '[ApiClient] 401 error on ${error.requestOptions.path}, attempting token refresh...');
      try {
        final refreshed = await _tryRefreshTokenWithLock();
        if (refreshed) {
          debugPrint(
              '[ApiClient] Token refresh successful, retrying: ${error.requestOptions.path}');
          // 重试原请求
          final response = await _retryRequest(error.requestOptions);
          return handler.resolve(response);
        } else {
          // refresh token 不存在或刷新失败，需要重新登录
          debugPrint(
              '[ApiClient] Token refresh failed, no valid refresh token');
          _notifyTokenExpiredOnce();
        }
      } catch (e) {
        debugPrint('[ApiClient] Token refresh exception: $e');
        // refresh token 刷新失败（可能过期），需要重新登录
        if (e is DioException && e.response?.statusCode == 401) {
          debugPrint(
              '[ApiClient] Refresh token expired (401), notifying token expired');
          _notifyTokenExpiredOnce();
        }
        // 其他异常（网络错误等）不触发重新登录，让请求正常失败
      }
    }

    handler.next(error);
  }

  /// 通知 token 过期事件（仅通知一次，避免重复弹窗）
  void _notifyTokenExpiredOnce() {
    if (_hasNotifiedTokenExpired) {
      debugPrint('[ApiClient] Token expired already notified, skipping');
      return;
    }
    _hasNotifiedTokenExpired = true;
    debugPrint('[ApiClient] Emitting token expired event');
    AuthEventService().emitTokenExpired(message: '登录已过期，请重新登录');
  }

  /// 重置 token 过期通知标记（在用户重新登录后调用）
  void resetTokenExpiredFlag() {
    _hasNotifiedTokenExpired = false;
    debugPrint('[ApiClient] Token expired flag reset');
  }

  /// 带锁的 Token 刷新，防止并发刷新导致的竞态条件
  ///
  /// 问题场景：
  /// 1. 多个请求同时收到 401
  /// 2. 都调用 refresh token 接口
  /// 3. 第一个成功后，后端撤销了旧 refresh token
  /// 4. 其他请求使用旧 refresh token 失败
  ///
  /// 解决方案：
  /// 使用 Completer 作为锁，确保同一时刻只有一个刷新请求
  /// 其他请求等待第一个刷新完成后复用结果
  Future<bool> _tryRefreshTokenWithLock() async {
    // 如果已有刷新在进行中，等待其结果
    if (_refreshCompleter != null) {
      debugPrint(
          '[ApiClient] Token refresh already in progress, waiting for result...');
      return _refreshCompleter!.future;
    }

    // 创建新的 Completer 作为锁
    _refreshCompleter = Completer<bool>();
    debugPrint('[ApiClient] Starting token refresh (acquired lock)');

    try {
      final result = await _doRefreshToken();
      _refreshCompleter!.complete(result);
      debugPrint('[ApiClient] Token refresh completed with result: $result');
      return result;
    } catch (e) {
      debugPrint('[ApiClient] Token refresh failed with exception: $e');
      _refreshCompleter!.completeError(e);
      rethrow;
    } finally {
      // 释放锁
      _refreshCompleter = null;
      debugPrint('[ApiClient] Token refresh lock released');
    }
  }

  /// 执行实际的 Token 刷新
  Future<bool> _doRefreshToken() async {
    final refreshToken =
        await _secureStorage.read(key: StorageKeys.refreshToken);
    if (refreshToken == null || refreshToken.isEmpty) {
      debugPrint('[ApiClient] No refresh token available');
      return false;
    }

    final deviceId = await _secureStorage.read(key: StorageKeys.deviceId);
    if (deviceId == null || deviceId.isEmpty) {
      debugPrint('[ApiClient] No device ID available');
      return false;
    }

    debugPrint('[ApiClient] Calling /user/auto-login with deviceId: $deviceId');

    final response = await _dio.post(
      '/user/auto-login',
      data: {
        'refreshToken': refreshToken,
        'deviceId': deviceId,
      },
      options: Options(headers: {'Authorization': ''}), // 不带旧 Token
    );

    // auto-login 成功可能返回 200 或 201
    if (response.statusCode == 200 || response.statusCode == 201) {
      final responseData = response.data;
      // API 返回格式: { success: true, data: { accessToken, refreshToken, ... } }
      final data = responseData['data'] as Map<String, dynamic>;

      final newAccessToken = data['accessToken'] as String?;
      final newRefreshToken = data['refreshToken'] as String?;

      if (newAccessToken == null || newRefreshToken == null) {
        debugPrint('[ApiClient] Invalid response: missing tokens');
        return false;
      }

      await _secureStorage.write(
        key: StorageKeys.accessToken,
        value: newAccessToken,
      );
      await _secureStorage.write(
        key: StorageKeys.refreshToken,
        value: newRefreshToken,
      );

      // 刷新成功，重置过期通知标记
      _hasNotifiedTokenExpired = false;

      debugPrint('[ApiClient] Token refreshed successfully, new tokens saved');
      return true;
    }

    debugPrint(
        '[ApiClient] Unexpected response status: ${response.statusCode}');
    return false;
  }

  /// 重试请求
  Future<Response> _retryRequest(RequestOptions options) async {
    final token = await _secureStorage.read(key: StorageKeys.accessToken);
    options.headers['Authorization'] = 'Bearer $token';
    return _dio.fetch(options);
  }

  /// GET 请求
  ///
  /// [cancelToken] 用于取消请求
  /// [onReceiveProgress] 下载进度回调，参数为 (已接收字节数, 总字节数)
  Future<Response<T>> get<T>(
    String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    void Function(int, int)? onReceiveProgress,
  }) async {
    try {
      return await _dio.get<T>(
        path,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onReceiveProgress: onReceiveProgress,
      );
    } on DioException catch (e) {
      throw _handleDioError(e);
    }
  }

  /// POST 请求
  Future<Response<T>> post<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
  }) async {
    try {
      return await _dio.post<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
      );
    } on DioException catch (e) {
      throw _handleDioError(e);
    }
  }

  /// PUT 请求
  Future<Response<T>> put<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
  }) async {
    try {
      return await _dio.put<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
      );
    } on DioException catch (e) {
      throw _handleDioError(e);
    }
  }

  /// DELETE 请求
  Future<Response<T>> delete<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
  }) async {
    try {
      return await _dio.delete<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
      );
    } on DioException catch (e) {
      throw _handleDioError(e);
    }
  }

  /// 上传文件
  Future<Response<T>> uploadFile<T>(
    String path,
    List<int> fileBytes,
    String fileName, {
    String fieldName = 'file',
    Map<String, dynamic>? extraFields,
    Options? options,
  }) async {
    try {
      final formData = FormData.fromMap({
        fieldName: MultipartFile.fromBytes(
          fileBytes,
          filename: fileName,
        ),
        ...?extraFields,
      });

      return await _dio.post<T>(
        path,
        data: formData,
        options: options ?? Options(
          contentType: 'multipart/form-data',
        ),
      );
    } on DioException catch (e) {
      throw _handleDioError(e);
    }
  }

  /// 处理 Dio 错误
  Exception _handleDioError(DioException error) {
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        return NetworkException('网络连接超时，请稍后重试');
      case DioExceptionType.badResponse:
        final statusCode = error.response?.statusCode;
        final data = error.response?.data;
        debugPrint('[ApiClient] ▼▼▼ badResponse: status=$statusCode, data=$data, dataType=${data.runtimeType}');
        final rawMsg = data is Map ? data['message'] : null;
        debugPrint('[ApiClient] ▼▼▼ rawMsg=$rawMsg, rawMsgType=${rawMsg.runtimeType}');
        // NestJS ValidationPipe 校验失败时 message 字段为 List<String>（每条字段错误一个元素），
        // 例如：{ message: ["provinceCode should not be empty", "portionCount must be an integer"] }
        // 而业务异常的 message 为 String，因此必须区分处理，否则 Dart 运行时会抛出
        // "List<dynamic> is not a subtype of String" 类型错误。
        final message = rawMsg is List
            ? (rawMsg as List).join(', ')
            : (rawMsg?.toString() ?? '请求失败');

        switch (statusCode) {
          case 400:
            return ApiException(message, statusCode: 400);
          case 401:
            return UnauthorizedException(message);
          case 403:
            return ForbiddenException(message);
          case 404:
            return NotFoundException(message);
          case 500:
          case 502:
          case 503:
            return ServerException(message: '服务器错误，请稍后重试', statusCode: statusCode);
          default:
            return ApiException(message, statusCode: statusCode);
        }
      case DioExceptionType.cancel:
        return ApiException('请求已取消');
      case DioExceptionType.connectionError:
        return NetworkException('网络连接失败，请检查网络设置');
      default:
        return NetworkException('网络错误: ${error.message}');
    }
  }
}
