import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../configs/api_paths.dart';

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

class RequestManager {
  static final RequestManager _instance = RequestManager._internal();
  factory RequestManager() => _instance;
  late Dio _dio;

  bool _isErrorDialogShown = false;
  final List<Future Function()> _pendingRetries = [];

  RequestManager._internal() {
    BaseOptions options = BaseOptions(
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
      responseType: ResponseType.json,
      headers: {
        'Content-Type': 'application/json',
      },
    );
    _dio = Dio(options);

    // interceptor handle errors
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        // Log request
        print("REQUEST[${options.method}] => ${options.baseUrl}${options.path}");
        return handler.next(options);
      },
      onResponse: (response, handler) {
        // Log response
        print("RESPONSE[${response.statusCode}] => ${response.data}");
        return handler.next(response);
      },
      onError: (DioException error, handler) async {
        if (error.response?.statusCode == 401 ||
            error.type == DioExceptionType.connectionTimeout ||
            error.type == DioExceptionType.receiveTimeout ||
            error.error is SocketException) {
          _pendingRetries.add(() async {
            return await _retryRequest(error.requestOptions);
          });

          if (!_isErrorDialogShown) {
            _isErrorDialogShown = true;
            bool retry = await _showNetworkErrorAlert();
            if (retry) {
              await _retryPendingRequests();
            } else {
              _pendingRetries.clear();
            }
            _isErrorDialogShown = false;
          }
        }
        return handler.next(error);
      },
    ));
  }

  // retry request từ RequestOptions
  Future<Response<T>> _retryRequest<T>(RequestOptions options) async {
    try {
      Response<T> response = await _dio.request<T>(
        options.path,
        data: options.data,
        queryParameters: options.queryParameters,
        options: Options(
          method: options.method,
          headers: options.headers,
          extra: options.extra,
        ),
      );
      return response;
    } catch (e) {
      rethrow;
    }
  }

  // retry all request errors
  Future<void> _retryPendingRequests() async {
    List<Future Function()> retries = List.from(_pendingRetries);
    _pendingRetries.clear();
    await Future.wait(retries.map((f) => f()));
  }

  Future<bool> _showNetworkErrorAlert() async {
    BuildContext? context = navigatorKey.currentState?.overlay?.context;
    if (context == null) return false;
    return showDialog<bool>(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog(
        title: Text("Lỗi mạng"),
        content: Text("Đã xảy ra lỗi mạng hoặc xác thực. Bạn có muốn thử lại các request lỗi không?"),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(false),
            child: Text("Hủy"),
          ),
          TextButton(
            onPressed: () => Navigator.of(context).pop(true),
            child: Text("Thử lại"),
          ),
        ],
      ),
    ).then((value) => value ?? false);
  }

  /// [cancelToken]: Token hủy request.
  /// [converter]: Hàm chuyển đổi từ Map<String, dynamic> sang model T (nếu T kế thừa BaseModel).
  Future<T> request<T>({
    String? host,
    required String path,
    required String method,
    Map<String, dynamic>? queryParameters,
    dynamic data,
    Map<String, dynamic>? headers,
    CancelToken? cancelToken,
    T Function(Map<String, dynamic> json)? converter,
  }) async {
    _dio.options.baseUrl = host ?? APIPaths.baseUrl;
    if (headers != null) {
      _dio.options.headers.addAll(headers);
    }
    try {
      Response response = await _dio.request(
        path,
        data: data,
        queryParameters: queryParameters,
        options: Options(method: method),
        cancelToken: cancelToken,
      );
      if (converter != null && response.data is Map<String, dynamic>) {
        return converter(response.data as Map<String, dynamic>);
      }
      return response.data as T;
    } on DioException catch (e) {
      throw Exception("Request error: ${e.message}");
    }
  }
}
