import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:frontend_eccp_mobile/app/core/constants/enum/network_error_code_enum.dart';
import 'package:frontend_eccp_mobile/app/core/constants/enum/network_error_message_enum.dart';
import 'package:frontend_eccp_mobile/app/core/constants/globalkey.dart';
import 'package:frontend_eccp_mobile/app/core/di/injection.dart';
import 'package:frontend_eccp_mobile/app/core/navigation/app_routes.dart';
import 'package:frontend_eccp_mobile/app/core/network/api_endpoint.dart';
import 'package:frontend_eccp_mobile/app/core/services/bottom_sheet_service.dart';
import 'package:frontend_eccp_mobile/app/core/services/crashlytics_service.dart';
import 'package:frontend_eccp_mobile/app/core/utils/session_manager.dart';
import 'package:frontend_eccp_mobile/app/core/utils/token_manager.dart';
import 'package:frontend_eccp_mobile/modules/auth/refresh_token/services/auth_service.dart';
import 'package:go_router/go_router.dart';

class ErrorInterceptor extends Interceptor {
  ErrorInterceptor(this.dio);

  final Dio dio;
  final AuthService _authService = getIt<AuthService>();

  bool _isRefreshing = false;
  final List<void Function(String)> _queue = [];

  @override
  Future<void> onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    final statusCode = err.response?.statusCode;
    final isNoAuth = err.requestOptions.extra['noAuth'] == true;

    String? backendMessage;
    final data = err.response?.data;
    if (data is Map<String, dynamic>) {
      backendMessage = data['message']?.toString();
    }

    final code = _mapErrorCode(err);

    if (_shouldSendToCrashlytics(code, statusCode)) {
      await CrashlyticsService.recordError(
        err,
        err.stackTrace,
        reason: backendMessage ?? code.name,
        fatal: true,
        context: {
          'url': err.requestOptions.uri.toString(),
          'method': err.requestOptions.method,
          'status_code': statusCode ?? -1,
          'error_code': code.name,
          'response': data?.toString() ?? '-',
        },
      );
    }

    if (isNoAuth) {
      return handler.reject(
        _withMapped(
          err,
          backendMessage ?? _mapMessage(code).value,
        ),
      );
    }

    if (statusCode == 401) {
      await _handle401(err, handler);
      return;
    }

    if (code == NetworkErrorCode.noInternet) {
      if (err.requestOptions.extra['noInternetShown'] != true) {
        err.requestOptions.extra['noInternetShown'] = true;
        err.requestOptions.extra['handledGlobally'] = true;

        await BottomSheetService.showNoInternet();
      }

      return handler.reject(
        _withMapped(err, NetworkErrorMessage.noInternet.value),
      );
    }

    if (code == NetworkErrorCode.serverError) {
      if (err.requestOptions.extra['serverErrorShown'] != true) {
        err.requestOptions.extra['serverErrorShown'] = true;
        err.requestOptions.extra['handledGlobally'] = true;

        await BottomSheetService.showServerError();
      }

      return handler.reject(
        _withMapped(err, NetworkErrorMessage.serverError.value),
      );
    }

    if (code == NetworkErrorCode.notFound) {
      if (err.requestOptions.extra['serverErrorShown'] != true) {
        err.requestOptions.extra['serverErrorShown'] = true;
        err.requestOptions.extra['handledGlobally'] = true;

        await BottomSheetService.showNotFound();
      }

      return handler.reject(
        _withMapped(err, NetworkErrorMessage.notFound.value),
      );
    }

    return handler.reject(
      _withMapped(
        err,
        backendMessage ?? _mapMessage(code).value,
      ),
    );
  }

  Future<void> _handle401(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    final requestOptions = err.requestOptions;

    if (requestOptions.path.contains(ApiEndpoint.refreshToken) ||
        requestOptions.path.contains(ApiEndpoint.logout)) {
      await _handleLogout();
      return handler.reject(err);
    }

    if (_isRefreshing) {
      _queue.add((token) async {
        requestOptions.headers['Authorization'] = 'Bearer $token';
        final response = await dio.fetch<dynamic>(requestOptions);
        handler.resolve(response);
      });
      return;
    }

    _isRefreshing = true;

    try {
      final newToken = await _authService.refreshToken();

      await SessionManager.saveToken(newToken.accessToken);
      await SessionManager.saveRefreshToken(newToken.refreshToken);

      for (final retry in _queue) {
        retry(newToken.accessToken);
      }
      _queue.clear();

      requestOptions.headers['Authorization'] =
          'Bearer ${newToken.accessToken}';

      final response = await dio.fetch<dynamic>(requestOptions);

      handler.resolve(response);
    } catch (e) {
      await _handleLogout();
      handler.reject(err);
    } finally {
      _isRefreshing = false;
    }
  }

  Future<void> _handleLogout() async {
    try {
      await TokenManager.clearToken();
      dio.options.headers.remove('Authorization');

      navigatorKey.currentContext?.go(AppRoutes.login);
    } catch (e) {
      debugPrint('Logout error: $e');
    }
  }

  bool _shouldSendToCrashlytics(
    NetworkErrorCode code,
    int? statusCode,
  ) {
    if (code == NetworkErrorCode.noInternet) return false;
    if (code == NetworkErrorCode.notFound) return true;
    if (statusCode == 401) return false;
    if (statusCode != null && statusCode < 500) return false;
    return true;
  }

  NetworkErrorCode _mapErrorCode(DioException e) {
    final statusCode = e.response?.statusCode;

    switch (e.type) {
      case DioExceptionType.badResponse:
        if (statusCode == 404) return NetworkErrorCode.notFound;
        if (statusCode == 401) return NetworkErrorCode.unauthorized;
        if (statusCode != null && statusCode >= 500) {
          return NetworkErrorCode.serverError;
        }
        return NetworkErrorCode.unknown;

      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
      case DioExceptionType.connectionError:
        return NetworkErrorCode.noInternet;

      case DioExceptionType.cancel:
        return NetworkErrorCode.requestCancelled;

      case DioExceptionType.badCertificate:
        return NetworkErrorCode.badCertificate;

      case DioExceptionType.unknown:
        return NetworkErrorCode.unknown;
    }
  }

  NetworkErrorMessage _mapMessage(NetworkErrorCode code) {
    switch (code) {
      case NetworkErrorCode.unauthorized:
        return NetworkErrorMessage.unauthorized;
      case NetworkErrorCode.noInternet:
        return NetworkErrorMessage.noInternet;
      case NetworkErrorCode.serverError:
        return NetworkErrorMessage.serverError;
      case NetworkErrorCode.notFound:
        return NetworkErrorMessage.notFound;
      case NetworkErrorCode.requestCancelled:
        return NetworkErrorMessage.requestCancelled;
      case NetworkErrorCode.badCertificate:
        return NetworkErrorMessage.badCertificate;
      case NetworkErrorCode.unknown:
        return NetworkErrorMessage.unknown;
    }
  }

  DioException _withMapped(DioException err, String message) {
    return DioException(
      requestOptions: err.requestOptions,
      response: err.response,
      type: err.type,
      error: message,
    );
  }
}
