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

import '../../local_peference/local_preference.dart';
import '../api_urls/api_urls.dart';

class ApiService {
  static const String _baseUrl = 'https://your-api-base-url.com/api';

  static final ApiService _instance = ApiService._internal();

  late Dio _dio;
  late Dio _tokenDio; // ✅ Separate Dio for token refresh (no interceptors)

  factory ApiService() => _instance;

  ApiService._internal() {
    // ================= MAIN DIO =================
    _dio = Dio(
      BaseOptions(
        baseUrl: _baseUrl,
        connectTimeout: const Duration(seconds: 60),
        receiveTimeout: const Duration(seconds: 60),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
      ),
    );

    // ================= TOKEN DIO (No interceptors — used only for refresh) =================
    _tokenDio = Dio(
      BaseOptions(
        baseUrl: _baseUrl,
        connectTimeout: const Duration(seconds: 30),
        receiveTimeout: const Duration(seconds: 30),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
      ),
    );

    // ================= 1. RETRY INTERCEPTOR =================
    // ✅ Added FIRST so it only retries network errors
    _dio.interceptors.add(
      InterceptorsWrapper(
        onError: (err, handler) async {
          final options = err.requestOptions;
          const maxRetries = 2;
          final currentRetry = options.extra['retry'] as int? ?? 0;

          final shouldRetry =
              currentRetry < maxRetries &&
                  (err.type == DioExceptionType.connectionTimeout ||
                      err.type == DioExceptionType.sendTimeout ||
                      err.type == DioExceptionType.receiveTimeout);

          if (shouldRetry) {
            if (kDebugMode) {
              print(
                '🔁 Retrying request (${currentRetry + 1}/$maxRetries) => ${options.uri}',
              );
            }
            options.extra['retry'] = currentRetry + 1;
            try {
              final response = await _dio.fetch(options);
              return handler.resolve(response);
            } on DioException catch (e) {
              return handler.reject(e);
            }
          }

          return handler.next(err);
        },
      ),
    );

    // ================= 2. MAIN INTERCEPTOR (Auth + Token Refresh) =================
    // ✅ Added SECOND — handles auth and token refresh
    _dio.interceptors.add(
      QueuedInterceptorsWrapper(
        onRequest: (options, handler) async {
          final token = await LocalPreference.getAccessToken();
          if (token != null && token.isNotEmpty) {
            options.headers['Authorization'] = 'Bearer $token';
          }
          handler.next(options);
        },

        onResponse: (response, handler) {
          handler.next(response);
        },

        onError: (error, handler) async {
          if (error.response?.statusCode == 401) {
            final requestOptions = error.requestOptions;

            try {
              final refreshed = await _refreshToken();

              if (refreshed) {
                final newToken = await LocalPreference.getAccessToken();
                requestOptions.headers['Authorization'] = 'Bearer $newToken';

                // ✅ Retry original request with new token
                final response = await _dio.fetch(requestOptions);
                return handler.resolve(response);
              } else {
                await _forceLogout();
                return handler.reject(error);
              }
            } catch (e) {
              if (kDebugMode) {
                print('❌ Error during token refresh flow: $e');
              }
              await _forceLogout();
              return handler.reject(error);
            }
          }

          handler.next(error);
        },
      ),
    );

    // ================= 3. LOGGING INTERCEPTOR =================
    // ✅ Added LAST so it captures the final state of all requests/responses
    if (kDebugMode) {
      _dio.interceptors.add(
        LogInterceptor(
          request: true,
          requestHeader: true,
          requestBody: true,
          responseBody: true,
          responseHeader: false,
          error: true,
          logPrint: (log) => print('📡 $log'),
        ),
      );
    }
  }

  // ================= GET =================
  Future<Response> get(
      String endpoint, {
        Map<String, dynamic>? queryParameters,
        Options? options,
        CancelToken? cancelToken,
      }) async {
    try {
      return await _dio.get(
        endpoint,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }

  // ================= POST =================
  Future<Response> post(
      String endpoint, {
        dynamic data,
        Map<String, dynamic>? queryParameters,
        Options? options,
        CancelToken? cancelToken,
        ProgressCallback? onSendProgress,
      }) async {
    try {
      return await _dio.post(
        endpoint,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
      );
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }

  // ================= PUT =================
  Future<Response> put(
      String endpoint, {
        dynamic data,
        Map<String, dynamic>? queryParameters,
        Options? options,
        CancelToken? cancelToken,
        ProgressCallback? onSendProgress,
      }) async {
    try {
      return await _dio.put(
        endpoint,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
      );
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }

  // ================= DELETE =================
  Future<Response> delete(
      String endpoint, {
        dynamic data,
        Map<String, dynamic>? queryParameters,
        Options? options,
        CancelToken? cancelToken,
      }) async {
    try {
      return await _dio.delete(
        endpoint,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }

  // ================= REFRESH TOKEN =================
  // ✅ Uses _tokenDio (no interceptors) to avoid QueuedInterceptor deadlock
  Future<bool> _refreshToken() async {
    try {
      final refreshToken = await LocalPreference.getRefreshToken();

      if (kDebugMode) print('🔍 Refresh token from storage: $refreshToken');

      if (refreshToken == null || refreshToken.isEmpty) {
        if (kDebugMode) print('❌ No refresh token found');
        return false;
      }

      if (kDebugMode) print('🔄 Attempting token refresh...');

      final response = await _tokenDio.post(
        ApiUrls.refreshToken,
        data: '',  // ✅ Empty body — server reads token from Cookie header
        options: Options(
          headers: {
            // ✅ Manually inject refresh token as cookie header
            'Cookie': 'partner_refresh_token=$refreshToken',
          },
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      if (kDebugMode) {
        print("🔄 Refresh response status: ${response.statusCode}");
        print("✅ REFRESH RESPONSE => ${response.data}");
      }

      if (response.statusCode != 200 && response.statusCode != 201) {
        if (kDebugMode) {
          print('❌ Refresh failed with status: ${response.statusCode}');
        }
        return false;
      }

      final newAccessToken = response.data['accessToken'];

      if (newAccessToken == null || (newAccessToken as String).isEmpty) {
        if (kDebugMode) print('❌ Access token missing in refresh response');
        return false;
      }

      await LocalPreference.setAccessToken(newAccessToken);
      if (kDebugMode) print('✅ Token refreshed successfully');
      return true;

    } on DioException catch (e) {
      if (kDebugMode) print('❌ Refresh token DioException: ${e.message}');
      return false;
    } catch (e) {
      if (kDebugMode) print('❌ Refresh token unexpected error: $e');
      return false;
    }
  }

  // ================= FORCE LOGOUT =================
  Future<void> _forceLogout() async {
    if (kDebugMode) print('🚪 Force logout triggered');
    await LocalPreference.clearAll();
    await LocalPreference.setLogin(false);
  }

  // ================= ERROR HANDLER =================
  String _handleError(DioException error) {
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
        return "Connection timeout. Please try again.";
      case DioExceptionType.sendTimeout:
        return "Send timeout. Please try again.";
      case DioExceptionType.receiveTimeout:
        return "Receive timeout. Please try again.";
      case DioExceptionType.badCertificate:
        return "Bad certificate.";
      case DioExceptionType.badResponse:
        try {
          final responseData = error.response?.data;
          if (responseData is Map<String, dynamic>) {
            return responseData['message'] ??
                responseData['error'] ??
                "Invalid status code: ${error.response?.statusCode}";
          }
          if (responseData is String && responseData.isNotEmpty) {
            return responseData;
          }
          return "Invalid status code: ${error.response?.statusCode}";
        } catch (_) {
          return "Invalid status code: ${error.response?.statusCode}";
        }
      case DioExceptionType.cancel:
        return "Request was cancelled.";
      case DioExceptionType.connectionError:
        return "No internet connection.";
      case DioExceptionType.unknown:
        return "Something went wrong. Please try again.";
    }
  }

  // ================= UPDATE HEADERS =================
  void updateHeaders(Map<String, dynamic> headers) {
    _dio.options.headers.addAll(headers);
  }
}