import 'dart:async';

import 'package:dio/dio.dart';
import '../../base/app_navigator.dart';
import '../../configs/api_paths.dart';
import '../../configs/constants.dart';
import '../../services/logout_service.dart';
import '../../services/token_refresh_service.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import '../dio_extra_keys.dart';
import '../dio_http_service.dart';

class AuthInterceptor extends Interceptor {
  static const _kAuthHandledKey = '__auth_handled__';
  static Completer<bool>? _refreshCompleter;
  static Completer<void>? _logoutCompleter;

  static final List<String> _skipRefreshTokenPaths = [
    APIPaths.getUserInfo,
    APIPaths.login,
    APIPaths.refreshToken,
    APIPaths.logout,
    'assets',
  ];

  @override
  Future<void> onResponse(
    Response response,
    ResponseInterceptorHandler handler,
  ) async {
    if (_isTokenInvalid(response.data)) {
      final request = response.requestOptions;
      final authException = DioException(
        requestOptions: request,
        response: response,
        type: DioExceptionType.badResponse,
        error: ErrorCodes.authTokenInvalid,
        message: ErrorCodes.tokenInvalidMessage,
      );
      if (_shouldSkipRefreshToken(request)) {
        handler.reject(authException);
        return;
      }
      request.extra[_kAuthHandledKey] = true;
      await _handleAuthError(
        response.data,
        originalRequest: request,
        resolve: (retried) async => handler.resolve(retried),
        reject: (error) async => handler.reject(error),
        originalError: authException,
      );
      return;
    }
    handler.next(response);
  }

  @override
  Future<void> onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    final alreadyHandled = err.requestOptions.extra[_kAuthHandledKey] == true;
    final data = err.response?.data;
    final statusCode = err.response?.statusCode;
    final isAuthException = (statusCode == 401 || statusCode == 403 || _isTokenInvalid(data));
    if (alreadyHandled) {
      handler.next(err);
      return;
    }

    if (_shouldSkipRefreshToken(err.requestOptions)) {
      handler.next(err);
      return;
    }

    if (isAuthException) {
      err.requestOptions.extra[_kAuthHandledKey] = true;
      await _handleAuthError(
        data,
        originalRequest: err.requestOptions,
        resolve: (retried) async => handler.resolve(retried),
        reject: (error) async => handler.reject(error),
        originalError: err,
      );
      return;
    }
    handler.next(err);
  }

  bool _isTokenInvalid(dynamic data) {
    if (data is Map<String, dynamic>) {
      final code = data['error_code'] ?? data['errorCode'];
      return ErrorCodes.tokenInvalidCodes.contains(code);
    }
    return false;
  }

  bool _shouldSkipRefreshToken(RequestOptions options) {
    final path = options.path;
    if (path.isEmpty) return false;
    for (final candidate in _skipRefreshTokenPaths) {
      if (path.contains(candidate)) {
        return true;
      }
    }
    return false;
  }

  Future<void> _handleAuthError(
    dynamic data, {
    required RequestOptions originalRequest,
    required Future<void> Function(Response response) resolve,
    required Future<void> Function(DioException error) reject,
    required DioException originalError,
  }) async {
    final refreshSucceeded = await _ensureTokenRefreshed(data);
    if (refreshSucceeded) {
      try {
        final retryOptions = originalRequest.copyWith();
        retryOptions.extra.remove(_kAuthHandledKey);
        final dio = DioHttpService().dio;
        final Response retried = await dio.fetch(retryOptions);
        await resolve(retried);
        return;
      } catch (e) {
        if (e is DioException) {
          if (_isAuthException(e)) {
            await _ensureLogout(e.response?.data ?? data);
            _markAuthFailure(e.requestOptions);
          }
          e.requestOptions.extra[kExtraSkipApiErrorHandling] = true;
          await reject(e);
          return;
        }
        await reject(errFrom(e, originalRequest));
        return;
      }
    }

    _markAuthFailure(originalRequest);
    originalError.requestOptions.extra[kExtraSkipApiErrorHandling] = true;
    await reject(originalError);
  }

  Future<bool> _ensureTokenRefreshed(dynamic data) async {
    if (_logoutCompleter != null) {
      await _logoutCompleter!.future;
      return false;
    }
    final existing = _refreshCompleter;
    if (existing != null) {
      return existing.future;
    }
    final completer = Completer<bool>();
    _refreshCompleter = completer;
    try {
      final refreshService = TokenRefreshService();
      await refreshService.refreshToken((success) async {
        if (!success) {
          await _ensureLogout(data);
          if (!completer.isCompleted) completer.complete(false);
          return;
        }
        if (!completer.isCompleted) completer.complete(true);
      });
      return await completer.future;
    } catch (_) {
      await _ensureLogout(data);
      if (!completer.isCompleted) completer.complete(false);
      return false;
    } finally {
      if (identical(_refreshCompleter, completer)) {
        _refreshCompleter = null;
      }
    }
  }

  Future<void> _ensureLogout(dynamic data) async {
    final existing = _logoutCompleter;
    if (existing != null) {
      await existing.future;
      return;
    }
    final completer = Completer<void>();
    _logoutCompleter = completer;
    try {
      await _performLogout(data);
    } finally {
      if (!completer.isCompleted) completer.complete();
      _logoutCompleter = null;
    }
  }

  bool _isAuthException(DioException exception) {
    final status = exception.response?.statusCode;
    return status == 401 ||
        status == 403 ||
        _isTokenInvalid(exception.response?.data);
  }

  DioException errFrom(Object e, RequestOptions req) {
    if (e is DioException) return e;
    return DioException(requestOptions: req, error: e);
  }

  Future<void> _performLogout(dynamic data) async {
    String? message;
    if (data is Map<String, dynamic>) {
      message =
          data['error_message']?.toString() ??
          data['errorMessage']?.toString() ??
          data['message']?.toString();
    }
    final logged = DataPreference.instance.logged;
    if (logged) {
      await AppNavigator.showAuthAlertAndGoLogin(
        message ?? ErrorCodes.tokenInvalidMessage,
      );
    }
    await LogoutService.logout();
    await DataPreference.instance.clearData();
  }

  void _markAuthFailure(RequestOptions options) {
    options.extra[_kAuthHandledKey] = true;
    options.extra[kExtraSkipApiErrorHandling] = true;
  }
}
