/// Dio HTTP client configuration for the Worker app
///
/// Provides a configured Dio instance with interceptors for:
/// - Authentication
/// - Logging
/// - Error handling
/// - Caching
/// - Retry logic
library;

import 'dart:developer' as developer;

import 'package:dio/dio.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import 'package:worker/core/constants/api_constants.dart';
import 'package:worker/core/network/api_interceptor.dart';
import 'package:worker/core/network/network_info.dart';

part 'dio_client.g.dart';

// ============================================================================
// Dio Client Configuration
// ============================================================================

/// HTTP client wrapper around Dio with interceptors and configuration
class DioClient {
  DioClient(this._dio, this._cacheStore);

  final Dio _dio;
  final CacheStore? _cacheStore;

  /// Get the underlying Dio instance
  Dio get dio => _dio;

  /// Get the cache store
  CacheStore? get cacheStore => _cacheStore;

  // ============================================================================
  // HTTP Methods
  // ============================================================================

  /// Perform GET request
  Future<Response<T>> get<T>(
    String path, {
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onReceiveProgress,
  }) async {
    try {
      return await _dio.get<T>(
        path,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onReceiveProgress: onReceiveProgress,
      );
    } catch (e) {
      rethrow;
    }
  }

  /// Perform POST request
  Future<Response<T>> post<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    try {
      return await _dio.post<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );
    } catch (e) {
      rethrow;
    }
  }

  /// Perform PUT request
  Future<Response<T>> put<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    try {
      return await _dio.put<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );
    } catch (e) {
      rethrow;
    }
  }

  /// Perform PATCH request
  Future<Response<T>> patch<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    try {
      return await _dio.patch<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );
    } catch (e) {
      rethrow;
    }
  }

  /// Perform DELETE request
  Future<Response<T>> delete<T>(
    String path, {
    dynamic data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    try {
      return await _dio.delete<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );
    } catch (e) {
      rethrow;
    }
  }

  /// Upload file with multipart/form-data
  Future<Response<T>> uploadFile<T>(
    String path, {
    required FormData formData,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
  }) async {
    try {
      return await _dio.post<T>(
        path,
        data: formData,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
      );
    } catch (e) {
      rethrow;
    }
  }

  /// Download file
  Future<Response<dynamic>> downloadFile(
    String urlPath,
    String savePath, {
    ProgressCallback? onReceiveProgress,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    bool deleteOnError = true,
    String lengthHeader = Headers.contentLengthHeader,
    Options? options,
  }) async {
    try {
      return await _dio.download(
        urlPath,
        savePath,
        onReceiveProgress: onReceiveProgress,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
        deleteOnError: deleteOnError,
        lengthHeader: lengthHeader,
        options: options,
      );
    } catch (e) {
      rethrow;
    }
  }

  // ============================================================================
  // Cache Management
  // ============================================================================

  /// Clear all cached responses
  Future<void> clearCache() async {
    if (_cacheStore != null) {
      await _cacheStore.clean();
    }
  }

  /// Clear specific cached response by key
  Future<void> clearCacheByKey(String key) async {
    if (_cacheStore != null) {
      await _cacheStore.delete(key);
    }
  }

  /// Clear cache for specific path
  Future<void> clearCacheByPath(String path) async {
    if (_cacheStore != null) {
      final key = CacheOptions.defaultCacheKeyBuilder(
        RequestOptions(path: path),
      );
      await _cacheStore.delete(key);
    }
  }
}

// ============================================================================
// Custom Curl Logger Interceptor
// ============================================================================

/// Custom Curl Logger that uses debugPrint instead of print
class CustomCurlLoggerInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final curl = _cURLRepresentation(options);
    // debugPrint(
    //   '╔╣ CURL Request ╠══════════════════════════════════════════════════',
    // );
    // debugPrint(curl);
    // debugPrint(
    //   '╚═════════════════════════════════════════════════════════════════',
    // );
    // Also log to dart:developer for better filtering in DevTools
    developer.log(curl, name: 'DIO_CURL', time: DateTime.now());
    handler.next(options);
  }

  String _cURLRepresentation(RequestOptions options) {
    final components = ['curl --location'];

    // Add method
    if (options.method.toUpperCase() != 'GET') {
      components.add('-X ${options.method}');
    }

    // Add headers (INCLUDING Cookie this time!)
    options.headers.forEach((key, value) {
      // Escape single quotes in header values
      final escapedValue = value.toString().replaceAll("'", "'\\''");
      components.add("--header '$key: $escapedValue'");
    });

    // Add data with proper JSON formatting
    if (options.data != null) {
      if (options.data is FormData) {
        components.add('--data-binary [FormData]');
      } else {
        // Convert data to proper JSON string
        String jsonData;
        if (options.data is Map || options.data is List) {
          // Use dart:convert to properly encode JSON
          jsonData = _jsonEncode(options.data);
        } else {
          jsonData = options.data.toString();
        }
        // Escape single quotes for shell
        final escapedData = jsonData.replaceAll("'", "'\\''");
        components.add("--data '$escapedData'");
      }
    }

    // Add URL
    final uri = options.uri.toString();
    components.add("'$uri'");

    return components.join(' \\\n');
  }

  /// Simple JSON encoder (without importing dart:convert in this file)
  String _jsonEncode(dynamic data) {
    if (data == null) return 'null';
    if (data is String) return '"${data.replaceAll('"', r'\"')}"';
    if (data is num || data is bool) return data.toString();
    if (data is List) {
      final items = data.map((e) => _jsonEncode(e)).join(',');
      return '[$items]';
    }
    if (data is Map) {
      final pairs = data.entries
          .map((e) => '"${e.key}":${_jsonEncode(e.value)}')
          .join(',');
      return '{$pairs}';
    }
    return data.toString();
  }
}

// ============================================================================
// Retry Interceptor
// ============================================================================

/// Interceptor for retrying failed requests with exponential backoff
class RetryInterceptor extends Interceptor {
  RetryInterceptor(
    this._networkInfo, {
    this.maxRetries = ApiConstants.maxRetryAttempts,
    this.initialDelay = ApiConstants.initialRetryDelay,
    this.maxDelay = ApiConstants.maxRetryDelay,
    this.delayMultiplier = ApiConstants.retryDelayMultiplier,
  });

  final NetworkInfo _networkInfo;
  final int maxRetries;
  final Duration initialDelay;
  final Duration maxDelay;
  final double delayMultiplier;

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    // Get retry count from request extra
    final retries = err.requestOptions.extra['retries'] as int? ?? 0;

    // Check if we should retry
    if (retries >= maxRetries || !_shouldRetry(err)) {
      handler.next(err);
      return;
    }

    // Check network connectivity before retrying
    final isConnected = await _networkInfo.isConnected;
    if (!isConnected) {
      handler.next(err);
      return;
    }

    // Calculate delay with exponential backoff
    final delayMs =
        (initialDelay.inMilliseconds * (delayMultiplier * (retries + 1)))
            .toInt();
    final delay = Duration(
      milliseconds: delayMs.clamp(
        initialDelay.inMilliseconds,
        maxDelay.inMilliseconds,
      ),
    );

    // Wait before retry
    await Future<void>.delayed(delay);

    // Increment retry count
    err.requestOptions.extra['retries'] = retries + 1;

    // Retry the request
    try {
      final dio = Dio();
      final response = await dio.fetch<dynamic>(err.requestOptions);
      handler.resolve(response);
    } on DioException catch (e) {
      handler.next(e);
    }
  }

  /// Determine if error should trigger a retry
  bool _shouldRetry(DioException error) {
    // Retry on connection errors
    if (error.type == DioExceptionType.connectionTimeout ||
        error.type == DioExceptionType.receiveTimeout ||
        error.type == DioExceptionType.connectionError) {
      return true;
    }

    // Retry on 5xx server errors (except 501)
    final statusCode = error.response?.statusCode;
    if (statusCode != null && statusCode >= 500 && statusCode != 501) {
      return true;
    }

    // Retry on 408 Request Timeout
    if (statusCode == 408) {
      return true;
    }

    // Retry on 429 Too Many Requests (with delay from header)
    if (statusCode == 429) {
      return true;
    }

    return false;
  }
}

// ============================================================================
// Riverpod Providers
// ============================================================================

/// Provider for cache store
@riverpod
Future<CacheStore> cacheStore(Ref ref) async {
  final directory = await getTemporaryDirectory();
  return HiveCacheStore(directory.path, hiveBoxName: 'dio_cache');
}

/// Provider for cache options
@riverpod
Future<CacheOptions> cacheOptions(Ref ref) async {
  final store = await ref.watch(cacheStoreProvider.future);

  return CacheOptions(
    store: store,
    maxStale: const Duration(days: 7), // Keep cache for 7 days
    hitCacheOnErrorExcept: [401, 403], // Use cache on error except auth errors
    priority: CachePriority.high,
    cipher: null, // No encryption for now
    keyBuilder: CacheOptions.defaultCacheKeyBuilder,
    allowPostMethod: false, // Don't cache POST requests by default
  );
}

/// Provider for Dio instance with all interceptors
@riverpod
Future<Dio> dio(Ref ref) async {
  final dio = Dio();


  // Base configuration
  dio
    ..options = BaseOptions(
      baseUrl: ApiConstants.apiBaseUrl,
      connectTimeout: ApiConstants.connectionTimeout,
      receiveTimeout: ApiConstants.receiveTimeout,
      sendTimeout: ApiConstants.sendTimeout,
      headers: {
        'Content-Type': ApiConstants.contentTypeJson,
        'Accept': ApiConstants.acceptJson,
        'Accept-Language': ApiConstants.acceptLanguageVi,
      },
      responseType: ResponseType.json,
      validateStatus: (status) {
        // Accept all status codes and handle errors in interceptor
        return status != null && status < 500;
      },
    )
    // Add interceptors in order
    // 1. Custom Curl interceptor (logs cURL commands)
    // Uses debugPrint and developer.log for better visibility
    ..interceptors.add(CustomCurlLoggerInterceptor())
    // 2. Logging interceptor
    ..interceptors.add(ref.watch(loggingInterceptorProvider))
    // 3. Auth interceptor (add tokens to requests)
    ..interceptors.add(await ref.watch(authInterceptorProvider(dio).future))
    // 4. Cache interceptor
    ..interceptors.add(
      DioCacheInterceptor(
        options: await ref.watch(cacheOptionsProvider.future),
      ),
    )
    // 5. Retry interceptor
    ..interceptors.add(RetryInterceptor(ref.watch(networkInfoProvider)))
    // 6. Error transformer (last to transform all errors)
    ..interceptors.add(ref.watch(errorTransformerInterceptorProvider));

  return dio;
}

/// Provider for DioClient
@riverpod
Future<DioClient> dioClient(Ref ref) async {
  final dio = await ref.watch(dioProvider.future);
  final cacheStore = await ref.watch(cacheStoreProvider.future);

  return DioClient(dio, cacheStore);
}

// ============================================================================
// Helper Classes
// ============================================================================

/// Options for API requests with custom cache policy
class ApiRequestOptions {
  const ApiRequestOptions({
    this.cachePolicy,
    this.cacheDuration,
    this.forceRefresh = false,
  });

  final CachePolicy? cachePolicy;
  final Duration? cacheDuration;
  final bool forceRefresh;

  /// Options with cache enabled
  static const cached = ApiRequestOptions(cachePolicy: CachePolicy.forceCache);

  /// Options with network-first strategy
  static const networkFirst = ApiRequestOptions(
    cachePolicy: CachePolicy.refreshForceCache,
  );

  /// Options to force refresh from network
  static const forceNetwork = ApiRequestOptions(
    cachePolicy: CachePolicy.refresh,
    forceRefresh: true,
  );

  /// Convert to Dio Options
  Options toDioOptions() {
    return Options(
      extra: <String, dynamic>{
        if (cachePolicy != null) CacheResponse.cacheKey: cachePolicy!.index,
        if (cacheDuration != null) 'maxStale': cacheDuration,
        if (forceRefresh) 'policy': CachePolicy.refresh.index,
      },
    );
  }
}

/// Offline request queue item
class QueuedRequest {
  QueuedRequest({
    required this.method,
    required this.path,
    this.data,
    this.queryParameters,
    required this.timestamp,
  });

  factory QueuedRequest.fromJson(Map<String, dynamic> json) {
    return QueuedRequest(
      method: json['method'] as String,
      path: json['path'] as String,
      data: json['data'],
      queryParameters: json['queryParameters'] as Map<String, dynamic>?,
      timestamp: DateTime.parse(json['timestamp'] as String),
    );
  }

  final String method;
  final String path;
  final dynamic data;
  final Map<String, dynamic>? queryParameters;
  final DateTime timestamp;

  Map<String, dynamic> toJson() => <String, dynamic>{
    'method': method,
    'path': path,
    'data': data,
    'queryParameters': queryParameters,
    'timestamp': timestamp.toIso8601String(),
  };
}
