import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';

/// Dio interceptor that automatically retries failed requests with
/// exponential backoff for transient/recoverable errors.
///
/// IMPORTANT: onError must NOT be async (Dio calls it as void, so any
/// unhandled future would crash the app). We use a fire-and-forget
/// closure with full try/catch instead.
class RetryInterceptor extends Interceptor {
  final Dio dio;
  final int maxRetries;
  final Duration baseDelay;

  RetryInterceptor({
    required this.dio,
    this.maxRetries = 3,
    this.baseDelay = const Duration(milliseconds: 800),
  });

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    if (!_shouldRetry(err)) {
      return handler.next(err);
    }

    final retryCount = err.requestOptions.extra['_retryCount'] as int? ?? 0;
    if (retryCount >= maxRetries) {
      return handler.next(err);
    }

    // Fire-and-forget with full error containment — never leaks futures.
    _retry(err, handler, retryCount);
  }

  Future<void> _retry(
    DioException err,
    ErrorInterceptorHandler handler,
    int retryCount,
  ) async {
    try {
      // Exponential backoff with jitter
      final delay = baseDelay * pow(2, retryCount).toInt();
      final jitter = Duration(milliseconds: Random().nextInt(500));
      await Future.delayed(delay + jitter);

      err.requestOptions.extra['_retryCount'] = retryCount + 1;

      final response = await dio.fetch(err.requestOptions);
      handler.resolve(response);
    } on DioException catch (e) {
      handler.next(e);
    } catch (e) {
      // Catch absolutely everything — never let an exception escape.
      if (kDebugMode) debugPrint('[RetryInterceptor] unexpected: $e');
      handler.next(err);
    }
  }

  bool _shouldRetry(DioException err) {
    // Connection-level errors (connection reset, DNS failure, refused)
    if (err.type == DioExceptionType.connectionError ||
        err.type == DioExceptionType.connectionTimeout) {
      return true;
    }

    // DO NOT retry send/receive timeouts — server already got the request,
    // retrying just doubles the wait and server load.

    // Unknown errors (e.g. "Connection reset by peer")
    if (err.type == DioExceptionType.unknown && err.error != null) {
      return true;
    }

    // Server errors that are typically transient
    final statusCode = err.response?.statusCode;
    if (statusCode != null) {
      return statusCode == 429 || // Too Many Requests
          statusCode == 502 || // Bad Gateway
          statusCode == 503 || // Service Unavailable
          statusCode == 504; // Gateway Timeout
    }

    return false;
  }
}
