import 'dart:math';

import 'package:dio/dio.dart';
import 'package:networks_sdk/src/data/datasources/http/token_refresh_manager.dart';
import 'package:networks_sdk/src/presentation/wiring/api_config.dart';
import 'package:networks_sdk/src/presentation/wiring/network_callbacks.dart';

/// 重试拦截器
///
/// 两层重试机制：
///
/// 1. **业务错误码处理**（onResponse）
///    所有非 0 业务码经 [ApiConfig.onBusinessError] 回调，
///    App 层返回 [BusinessErrorAction] 枚举告知 SDK 该怎么做：
///    - refreshToken → 刷新 token 后重试
///    - forceLogout  → 中断请求
///    - handled      → App 已处理，不在 decodeResponse 中抛错
///    - unhandled    → 透传给调用方，decodeResponse 会抛 ApiError
///
/// 2. **瞬态错误重试**（onError）
///    5xx / 超时 / 连接失败 → 指数退避 + jitter → 自动重试
///    由 [ApiConfig.maxRetries] 控制（默认 0 = 不启用）
///
/// 两层独立运作，可叠加。
class RetryInterceptor extends Interceptor {
  final ApiConfig config;
  final Dio dio;
  final TokenRefreshManager _tokenManager;

  final _random = Random();

  RetryInterceptor({required this.config, required this.dio})
    : _tokenManager = TokenRefreshManager(
        onTokenRefresh: config.onTokenRefresh,
        onLog: config.onLog,
        timeout: config.tokenRefreshTimeout,
        reuseWindow: config.tokenReuseWindow,
        onGetTokenExpiry: config.onGetTokenExpiry,
        proactiveRefreshThreshold: config.proactiveRefreshThreshold,
      );

  // ── 响应处理（所有非 0 业务码统一走 onBusinessError）──────────────────

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    if (response.data is! Map<String, dynamic>) {
      handler.next(response);
      return;
    }

    final data = response.data as Map<String, dynamic>;
    final code = _parseCode(data['code']);
    if (code == 0) {
      handler.next(response);
      return;
    }

    final message = data['message'] as String? ?? '';
    final requestPath = response.requestOptions.path;

    // 未注册 onBusinessError 时直接放行，由 decodeResponse 抛 ApiError 给调用方
    if (config.onBusinessError == null) {
      handler.next(response);
      return;
    }

    final action = config.onBusinessError!(code, message, requestPath);
    switch (action) {
      case BusinessErrorAction.refreshToken:
        // 跳过已标记为 token 重试的请求，防止递归
        if (response.requestOptions.extra['_isTokenRetry'] == true) {
          handler.next(response);
          return;
        }
        config.onLog?.call(
          'Token expired (code: $code), refreshing...',
          tag: 'Network',
        );
        _handleTokenExpired(response, handler);

      case BusinessErrorAction.forceLogout:
        config.onLog?.call(
          'Force logout (code: $code)',
          tag: 'Network',
        );
        handler.reject(
          DioException(
            requestOptions: response.requestOptions,
            response: response,
            message: 'Force logout (code: $code)',
          ),
        );

      case BusinessErrorAction.handled:
        // App 层已处理（弹窗 / Toast）→ 标记，让 decodeResponse 跳过二次抛错
        response.requestOptions.extra['_businessErrorHandled'] = true;
        handler.next(response);

      case BusinessErrorAction.unhandled:
        // 未处理，decodeResponse 会抛 ApiError 给调用方
        handler.next(response);
    }
  }

  /// 处理 Token 过期：刷新 + 重试
  Future<void> _handleTokenExpired(
    Response response,
    ResponseInterceptorHandler handler,
  ) async {
    final newToken = await _tokenManager.refreshIfNeeded();

    if (newToken == null) {
      config.onLog?.call('Token refresh failed', tag: 'Network');
      handler.reject(
        DioException(
          requestOptions: response.requestOptions,
          response: response,
          message: 'Token refresh failed',
        ),
      );
      return;
    }

    // 刷新成功，更新 config 并用新 token 重试原请求
    config.updateToken(newToken);
    config.onLog?.call('Token refreshed, retrying...', tag: 'Network');
    try {
      final options = response.requestOptions;
      options.headers['token'] = newToken;
      // 标记为 token 重试请求，防止重试后再次进入 _handleTokenExpired 造成递归
      options.extra['_isTokenRetry'] = true;
      // 通知 EncryptionInterceptor：token 变了，需要用新 token 上下文重新加密
      // 旧的加密 headers（如 X-Token、X-Signature）可能包含过期 token 信息
      options.extra['_needsReEncryption'] = true;

      final retryResponse = await dio.fetch(options);
      handler.resolve(retryResponse);
    } on DioException catch (e) {
      handler.reject(e);
    }
  }

  // ── 瞬态错误重试（指数退避 + jitter）────────────────────────────────────

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    if (config.maxRetries <= 0 || !_isRetryable(err)) {
      handler.next(err);
      return;
    }

    final options = err.requestOptions;
    final attempt = (options.extra['_retryAttempt'] as int?) ?? 0;

    if (attempt >= config.maxRetries) {
      handler.next(err);
      return;
    }

    options.extra['_retryAttempt'] = attempt + 1;

    final delayMs = _backoffDelay(attempt);
    config.onLog?.call(
      'Transient error, retry ${attempt + 1}/${config.maxRetries} '
      'in ${delayMs}ms: ${options.path}',
      tag: 'Retry',
    );

    await Future<void>.delayed(Duration(milliseconds: delayMs));

    try {
      handler.resolve(await dio.fetch(options));
    } on DioException catch (e) {
      handler.reject(e);
    }
  }

  /// 判断是否可重试的瞬态错误
  bool _isRetryable(DioException err) {
    switch (err.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.receiveTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.connectionError:
        return true;
      case DioExceptionType.badResponse:
        // 5xx 服务端错误可重试
        final statusCode = err.response?.statusCode;
        return statusCode != null && statusCode >= 500;
      default:
        return false;
    }
  }

  /// 指数退避 + jitter
  ///
  /// delay = min(baseDelay * 2^attempt, 30s) + random(0, delay * 25%)
  int _backoffDelay(int attempt) {
    final baseMs = config.retryBaseDelay.inMilliseconds;
    final exponentialMs = min(baseMs * pow(2, attempt).toInt(), 30000);
    final jitterMs = _random.nextInt(
      (exponentialMs * 0.25).toInt().clamp(1, 7500),
    );
    return exponentialMs + jitterMs;
  }

  int _parseCode(dynamic code) {
    if (code is int) return code;
    if (code is String) return int.tryParse(code) ?? 0;
    return 0;
  }
}
